diff --git a/Model/Applications/VideosApp.swift b/Model/Applications/VideosApp.swift index 03147193..d2e5c857 100644 --- a/Model/Applications/VideosApp.swift +++ b/Model/Applications/VideosApp.swift @@ -1,6 +1,12 @@ import Foundation enum VideosApp: String, CaseIterable { + enum AppType: String { + case local + case youTube + case peerTube + } + case local case invidious case piped @@ -10,6 +16,19 @@ enum VideosApp: String, CaseIterable { rawValue.capitalized } + var appType: AppType { + switch self { + case .local: + return .local + case .invidious: + return .youTube + case .piped: + return .youTube + case .peerTube: + return .peerTube + } + } + var supportsAccounts: Bool { self != .local } diff --git a/Model/FavoriteItem.swift b/Model/FavoriteItem.swift index 1629d3f2..9365ba05 100644 --- a/Model/FavoriteItem.swift +++ b/Model/FavoriteItem.swift @@ -6,9 +6,9 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable { case subscriptions case popular case trending(String, String?) - case channel(String, String) + case channel(String, String, String) case playlist(String) - case channelPlaylist(String, String) + case channelPlaylist(String, String, String) case searchQuery(String, String, String, String) var label: String { @@ -21,9 +21,9 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable { let trendingCountry = Country(rawValue: country)! let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!) return "\(trendingCountry.flag) \(trendingCountry.id) \(trendingCategory?.name ?? "Trending")" - case let .channel(_, name): + case let .channel(_, _, name): return name - case let .channelPlaylist(_, name): + case let .channelPlaylist(_, _, name): return name case let .searchQuery(text, date, duration, order): var label = "Search: \"\(text)\"" diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index 61f4d7a9..0b9f638b 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -237,6 +237,13 @@ final class NavigationModel: ObservableObject { presentingAlert = true } + func hideViewsAboveBrowser() { + player.hide() + presentingChannel = false + presentingPlaylist = false + presentingOpenVideos = false + } + func hideKeyboard() { #if os(iOS) UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) diff --git a/Shared/Home/FavoriteItemView.swift b/Shared/Home/FavoriteItemView.swift index 0fa04df3..3c12c964 100644 --- a/Shared/Home/FavoriteItemView.swift +++ b/Shared/Home/FavoriteItemView.swift @@ -4,32 +4,27 @@ import SwiftUI import UniformTypeIdentifiers struct FavoriteItemView: View { - let item: FavoriteItem + var item: FavoriteItem + @Environment(\.navigationStyle) private var navigationStyle @StateObject private var store = FavoriteResourceObserver() @Default(.favorites) private var favorites - @Binding private var dragging: FavoriteItem? @ObservedObject private var accounts = AccountsModel.shared private var playlists = PlaylistsModel.shared private var favoritesModel = FavoritesModel.shared + private var navigation = NavigationModel.shared - init( - item: FavoriteItem, - dragging: Binding - ) { + init(item: FavoriteItem) { self.item = item - _dragging = dragging } var body: some View { Group { if isVisible { VStack(alignment: .leading, spacing: 2) { - Text(label) - .font(.title3.bold()) - .foregroundColor(.secondary) + itemControl .contextMenu { Button { favoritesModel.remove(item) @@ -46,19 +41,15 @@ struct FavoriteItemView: View { HorizontalCells(items: store.contentItems) } - .contentShape(Rectangle()) - #if os(macOS) - .opacity(dragging?.id == item.id ? 0.5 : 1) - #endif - .onAppear { - resource?.addObserver(store) - if item.section == .subscriptions { - cacheFeed(resource?.loadIfNeeded()) - } else { - resource?.loadIfNeeded() - } + .onAppear { + resource?.addObserver(store) + if item.section == .subscriptions { + cacheFeed(resource?.loadIfNeeded()) + } else { + resource?.loadIfNeeded() } + } } } .onChange(of: accounts.current) { _ in @@ -71,6 +62,103 @@ struct FavoriteItemView: View { } } + var itemControl: some View { + VStack { + #if os(tvOS) + itemButton + #else + if itemIsNavigationLink { + itemNavigationLink + } else { + itemButton + } + #endif + } + } + + var itemButton: some View { + Button(action: itemButtonAction) { + itemLabel + .foregroundColor(.accentColor) + } + .buttonStyle(.plain) + } + + var itemNavigationLink: some View { + NavigationLink(destination: itemNavigationLinkDestination) { + itemLabel + } + } + + var itemIsNavigationLink: Bool { + switch item.section { + case .channel: + return navigationStyle == .tab + case .channelPlaylist: + return navigationStyle == .tab + case .subscriptions: + return navigationStyle == .tab + case .popular: + return navigationStyle == .tab + default: + return false + } + } + + @ViewBuilder var itemNavigationLinkDestination: some View { + Group { + switch item.section { + case let .channel(_, id, name): + ChannelVideosView(channel: .init(id: id, name: name)) + case let .channelPlaylist(_, id, title): + ChannelPlaylistView(playlist: .init(id: id, title: title)) + case .subscriptions: + SubscriptionsView() + case .popular: + PopularView() + default: + EmptyView() + } + } + .modifier(PlayerOverlayModifier()) + } + + func itemButtonAction() { + switch item.section { + case let .channel(_, id, name): + NavigationModel.shared.openChannel(.init(id: id, name: name), navigationStyle: navigationStyle) + case let .channelPlaylist(_, id, title): + NavigationModel.shared.openChannelPlaylist(.init(id: id, title: title), navigationStyle: navigationStyle) + case .subscriptions: + navigation.hideViewsAboveBrowser() + navigation.tabSelection = .subscriptions + case .popular: + navigation.hideViewsAboveBrowser() + navigation.tabSelection = .popular + case let .trending(country, category): + navigation.hideViewsAboveBrowser() + Defaults[.trendingCountry] = .init(rawValue: country) ?? .us + Defaults[.trendingCategory] = category.isNil ? .default : (.init(rawValue: category!) ?? .default) + navigation.tabSelection = .trending + case let .searchQuery(text, _, _, _): + navigation.hideViewsAboveBrowser() + navigation.openSearchQuery(text) + case let .playlist(id): + navigation.tabSelection = .playlist(id) + } + } + + var itemLabel: some View { + HStack { + Text(label) + .font(.title3.bold()) + Image(systemName: "chevron.right") + .imageScale(.small) + } + .lineLimit(1) + .padding(.trailing, 10) + } + private func cacheFeed(_ request: Request?) { request?.onSuccess { response in if let videos: [Video] = response.typedContent() { @@ -85,6 +173,12 @@ struct FavoriteItemView: View { return accounts.app.supportsSubscriptions && accounts.signedIn case .popular: return accounts.app.supportsPopular + case let .channel(appType, _, _): + guard let appType = VideosApp.AppType(rawValue: appType) else { return false } + return accounts.app.appType == appType + case let .channelPlaylist(appType, _, _): + guard let appType = VideosApp.AppType(rawValue: appType) else { return false } + return accounts.app.appType == appType default: return true } @@ -108,10 +202,10 @@ struct FavoriteItemView: View { return accounts.api.trending(country: trendingCountry, category: trendingCategory) - case let .channel(id, _): + case let .channel(_, id, _): return accounts.api.channelVideos(id) - case let .channelPlaylist(id, _): + case let .channelPlaylist(_, id, _): return accounts.api.channelPlaylist(id) case let .playlist(id): @@ -140,3 +234,16 @@ struct FavoriteItemView: View { return item.section.label.localized() } } + +struct FavoriteItemView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + VStack { + FavoriteItemView(item: .init(section: .channel("peerTube", "a", "Search: resistance body upper band workout"))) + .environment(\.navigationStyle, .tab) + FavoriteItemView(item: .init(section: .channel("peerTube", "a", "Marques"))) + .environment(\.navigationStyle, .sidebar) + } + } + } +} diff --git a/Shared/Home/HomeView.swift b/Shared/Home/HomeView.swift index f580e344..249e8d6f 100644 --- a/Shared/Home/HomeView.swift +++ b/Shared/Home/HomeView.swift @@ -6,9 +6,7 @@ import UniformTypeIdentifiers struct HomeView: View { @ObservedObject private var accounts = AccountsModel.shared - @State private var dragging: FavoriteItem? @State private var presentingEditFavorites = false - @State private var favoritesChanged = false @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) @@ -75,11 +73,11 @@ struct HomeView: View { if !accounts.current.isNil, showFavoritesInHome { #if os(tvOS) ForEach(Defaults[.favorites]) { item in - FavoriteItemView(item: item, dragging: $dragging) + FavoriteItemView(item: item) } #else ForEach(favorites) { item in - FavoriteItemView(item: item, dragging: $dragging) + FavoriteItemView(item: item) #if os(macOS) .workaroundForVerticalScrollingBug() #endif diff --git a/Shared/OpenURLHandler.swift b/Shared/OpenURLHandler.swift index 27466e96..3c2fe4c8 100644 --- a/Shared/OpenURLHandler.swift +++ b/Shared/OpenURLHandler.swift @@ -15,13 +15,6 @@ struct OpenURLHandler { var navigationStyle = NavigationStyle.sidebar func handle(_ url: URL) { - if Self.firstHandle { - Self.firstHandle = false - - Delay.by(1) { Self.shared.handle(url) } - return - } - if accounts.current.isNil { accounts.setCurrent(accounts.any) } @@ -52,27 +45,27 @@ struct OpenURLHandler { case .search: handleSearchUrlOpen(parser) case .favorites: - hideViewsAboveBrowser() + navigation.hideViewsAboveBrowser() navigation.tabSelection = .home #if os(macOS) focusMainWindow() #endif case .subscriptions: guard accounts.app.supportsSubscriptions, accounts.signedIn else { return } - hideViewsAboveBrowser() + navigation.hideViewsAboveBrowser() navigation.tabSelection = .subscriptions #if os(macOS) focusMainWindow() #endif case .popular: guard accounts.app.supportsPopular else { return } - hideViewsAboveBrowser() + navigation.hideViewsAboveBrowser() navigation.tabSelection = .popular #if os(macOS) focusMainWindow() #endif case .trending: - hideViewsAboveBrowser() + navigation.hideViewsAboveBrowser() navigation.tabSelection = .trending #if os(macOS) focusMainWindow() @@ -86,13 +79,6 @@ struct OpenURLHandler { } } - private func hideViewsAboveBrowser() { - player.hide() - navigation.presentingChannel = false - navigation.presentingPlaylist = false - navigation.presentingOpenVideos = false - } - private func handleFileURLOpen(_ parser: URLParser) { guard let url = parser.fileURL else { return } diff --git a/Shared/Views/ChannelPlaylistView.swift b/Shared/Views/ChannelPlaylistView.swift index 3a1781d7..e58178e1 100644 --- a/Shared/Views/ChannelPlaylistView.swift +++ b/Shared/Views/ChannelPlaylistView.swift @@ -87,7 +87,7 @@ struct ChannelPlaylistView: View { ShareButton(contentItem: contentItem) if let playlist = presentedPlaylist { - FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title))) + FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title))) } playButton diff --git a/Shared/Views/ChannelVideosView.swift b/Shared/Views/ChannelVideosView.swift index a64feb96..24b22ae8 100644 --- a/Shared/Views/ChannelVideosView.swift +++ b/Shared/Views/ChannelVideosView.swift @@ -231,7 +231,7 @@ struct ChannelVideosView: View { contentTypePicker Section { subscriptionToggleButton - FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name))) + FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, channel.id, channel.name))) } } } label: { diff --git a/Shared/Views/PlaylistVideosView.swift b/Shared/Views/PlaylistVideosView.swift index 48c7a16d..dfd6c577 100644 --- a/Shared/Views/PlaylistVideosView.swift +++ b/Shared/Views/PlaylistVideosView.swift @@ -66,7 +66,7 @@ struct PlaylistVideosView: View { .toolbar { ToolbarItem(placement: playlistButtonsPlacement) { HStack { - FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title))) + FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title))) Button { player.play(videos)