diff --git a/Model/Applications/VideosAPI.swift b/Model/Applications/VideosAPI.swift index ed3dcd55..8ae486f4 100644 --- a/Model/Applications/VideosAPI.swift +++ b/Model/Applications/VideosAPI.swift @@ -1,3 +1,4 @@ +import AVFoundation import Foundation import Siesta @@ -28,7 +29,7 @@ protocol VideosAPI { func channelPlaylist(_ id: String) -> Resource? func loadDetails(_ item: PlayerQueueItem, completionHandler: @escaping (PlayerQueueItem) -> Void) - func shareURL(_ item: ContentItem) -> URL? + func shareURL(_ item: ContentItem, frontendHost: String?, time: CMTime?) -> URL? } extension VideosAPI { @@ -50,23 +51,33 @@ extension VideosAPI { } } - func shareURL(_ item: ContentItem) -> URL? { - guard let frontendHost = account.instance.frontendHost else { + func shareURL(_ item: ContentItem, frontendHost: String? = nil, time: CMTime? = nil) -> URL? { + guard let frontendHost = frontendHost ?? account.instance.frontendHost else { return nil } var urlComponents = account.instance.urlComponents urlComponents.host = frontendHost + var queryItems = [URLQueryItem]() + switch item.contentType { case .video: urlComponents.path = "/watch" - urlComponents.query = "v=\(item.video.videoID)" + queryItems.append(.init(name: "v", value: item.video.videoID)) case .channel: urlComponents.path = "/channel/\(item.channel.id)" case .playlist: urlComponents.path = "/playlist" - urlComponents.query = "list=\(item.playlist.id)" + queryItems.append(.init(name: "list", value: item.playlist.id)) + } + + if !time.isNil, time!.seconds.isFinite { + queryItems.append(.init(name: "t", value: "\(Int(time!.seconds))s")) + } + + if !queryItems.isEmpty { + urlComponents.queryItems = queryItems } return urlComponents.url! diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 8aac99be..c9a88df7 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -381,7 +381,10 @@ final class PlayerModel: ObservableObject { return } - self.updateNowPlayingInfo() + #if !os(tvOS) + self.updateNowPlayingInfo() + #endif + self.handleSegments(at: self.player.currentTime()) } } diff --git a/Shared/Favorites/FavoriteItemView.swift b/Shared/Favorites/FavoriteItemView.swift index 97be4117..6f883972 100644 --- a/Shared/Favorites/FavoriteItemView.swift +++ b/Shared/Favorites/FavoriteItemView.swift @@ -85,7 +85,7 @@ struct FavoriteItemView: View { var label: String { if case let .playlist(id) = item.section { - return playlistsModel.find(id: id)?.title ?? "Unknown Playlist" + return playlistsModel.find(id: id)?.title ?? "Playlist" } return item.section.label diff --git a/Shared/Player/VideoDetails.swift b/Shared/Player/VideoDetails.swift index d7114e47..daaaac4a 100644 --- a/Shared/Player/VideoDetails.swift +++ b/Shared/Player/VideoDetails.swift @@ -13,6 +13,7 @@ struct VideoDetails: View { @State private var confirmationShown = false @State private var presentingAddToPlaylist = false @State private var presentingShareSheet = false + @State private var shareURL = "" @State private var currentPage = Page.details @@ -254,7 +255,8 @@ struct VideoDetails: View { HStack { ShareButton( contentItem: ContentItem(video: video), - presentingShareSheet: $presentingShareSheet + presentingShareSheet: $presentingShareSheet, + shareURL: $shareURL ) Spacer() @@ -286,7 +288,6 @@ struct VideoDetails: View { .help("Add to Playlist...") } .buttonStyle(.plain) - .foregroundColor(.blue) } } .frame(maxHeight: 35) @@ -300,9 +301,7 @@ struct VideoDetails: View { } #if os(iOS) .sheet(isPresented: $presentingShareSheet) { - if let url = accounts.api.shareURL(contentItem) { - ShareSheet(activityItems: [url]) - } + ShareSheet(activityItems: [shareURL]) } #endif } diff --git a/Shared/Views/ChannelPlaylistView.swift b/Shared/Views/ChannelPlaylistView.swift index ef2b90eb..6edbca3b 100644 --- a/Shared/Views/ChannelPlaylistView.swift +++ b/Shared/Views/ChannelPlaylistView.swift @@ -66,7 +66,7 @@ struct ChannelPlaylistView: View { } #if !os(tvOS) .toolbar { - ToolbarItem(placement: shareButtonPlacement) { + ToolbarItem(placement: .navigation) { ShareButton( contentItem: contentItem, presentingShareSheet: $presentingShareSheet @@ -84,14 +84,6 @@ struct ChannelPlaylistView: View { #endif } - private var shareButtonPlacement: ToolbarItemPlacement { - #if os(iOS) - .navigation - #else - .automatic - #endif - } - private var contentItem: ContentItem { ContentItem(playlist: playlist) } diff --git a/Shared/Views/ChannelVideosView.swift b/Shared/Views/ChannelVideosView.swift index e4c85730..ef988791 100644 --- a/Shared/Views/ChannelVideosView.swift +++ b/Shared/Views/ChannelVideosView.swift @@ -76,7 +76,7 @@ struct ChannelVideosView: View { #endif #if !os(tvOS) .toolbar { - ToolbarItem(placement: shareButtonPlacement) { + ToolbarItem(placement: .navigation) { ShareButton( contentItem: contentItem, presentingShareSheet: $presentingShareSheet @@ -140,14 +140,6 @@ struct ChannelVideosView: View { } } - private var shareButtonPlacement: ToolbarItemPlacement { - #if os(iOS) - .navigation - #else - .automatic - #endif - } - private var contentItem: ContentItem { ContentItem(channel: channel) } diff --git a/Shared/Views/ShareButton.swift b/Shared/Views/ShareButton.swift index 1e358c45..bb0aac77 100644 --- a/Shared/Views/ShareButton.swift +++ b/Shared/Views/ShareButton.swift @@ -3,43 +3,105 @@ import SwiftUI struct ShareButton: View { let contentItem: ContentItem @Binding var presentingShareSheet: Bool + @Binding var shareURL: String @EnvironmentObject private var accounts + @EnvironmentObject private var player + + init( + contentItem: ContentItem, + presentingShareSheet: Binding, + shareURL: Binding? = nil + ) { + self.contentItem = contentItem + _presentingShareSheet = presentingShareSheet + _shareURL = shareURL ?? .constant("") + } var body: some View { - Group { - if let url = shareURL { - Button { - #if os(iOS) - presentingShareSheet = true - #else - NSPasteboard.general.clearContents() - NSPasteboard.general.setString(url, forType: .string) - #endif - } label: { - #if os(iOS) - Label("Share", systemImage: "square.and.arrow.up") - #else - EmptyView() - #endif - } - .keyboardShortcut("c") - .foregroundColor(.blue) - .buttonStyle(.plain) + Menu { + instanceActions + youtubeActions + } label: { + Label("Share", systemImage: "square.and.arrow.up") .labelStyle(.iconOnly) - } else { - EmptyView() + } + .menuStyle(.borderlessButton) + #if os(macOS) + .frame(maxWidth: 35) + #endif + } + + private var instanceActions: some View { + Group { + if let url = accounts.api.shareURL(contentItem) { + Button(labelForShareURL(accounts.app.name)) { + shareAction(url) + } + + if contentItem.contentType == .video { + Button(labelForShareURL(accounts.app.name, withTime: true)) { + shareAction( + accounts.api.shareURL( + contentItem, + time: player.player.currentTime() + )! + ) + } + } } } } - private var shareURL: String? { - accounts.api.shareURL(contentItem)?.absoluteString + private var youtubeActions: some View { + Group { + if let url = accounts.api.shareURL(contentItem, frontendHost: "www.youtube.com") { + Button(labelForShareURL("YouTube")) { + shareAction(url) + } + + if contentItem.contentType == .video { + Button(labelForShareURL("YouTube", withTime: true)) { + shareAction( + accounts.api.shareURL( + contentItem, + frontendHost: "www.youtube.com", + time: player.player.currentTime() + )! + ) + } + } + } + } + } + + private func shareAction(_ url: URL) { + #if os(macOS) + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(url.absoluteString, forType: .string) + #else + shareURL = url.absoluteString + presentingShareSheet = true + #endif + } + + private func labelForShareURL(_ app: String, withTime: Bool = false) -> String { + let time = withTime ? "with time" : "" + + #if os(macOS) + return "Copy \(app) link \(time)" + #else + return "Share \(app) link \(time)" + #endif } } struct ShareButton_Previews: PreviewProvider { static var previews: some View { - ShareButton(contentItem: ContentItem(video: Video.fixture), presentingShareSheet: .constant(false)) + ShareButton( + contentItem: ContentItem(video: Video.fixture), + presentingShareSheet: .constant(false) + ) + .injectFixtureEnvironmentObjects() } } diff --git a/tvOS/EditFavorites.swift b/tvOS/EditFavorites.swift index 14987316..4589e5b4 100644 --- a/tvOS/EditFavorites.swift +++ b/tvOS/EditFavorites.swift @@ -79,7 +79,7 @@ struct EditFavorites: View { func label(_ item: FavoriteItem) -> String { if case let .playlist(id) = item.section { - return playlistsModel.find(id: id)?.title ?? "Unknown Playlist" + return playlistsModel.find(id: id)?.title ?? "Playlist" } return item.section.label