diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index d8714f74..b624e4c0 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -6,7 +6,7 @@ final class NavigationModel: ObservableObject { case watchNow, subscriptions, popular, trending, playlists, channel(String), playlist(String), recentlyOpened(String), search } - @Published var tabSelection: TabSelection = .watchNow + @Published var tabSelection: TabSelection! = .watchNow @Published var showingVideo = false @Published var video: Video? @@ -32,15 +32,13 @@ final class NavigationModel: ObservableObject { showingVideo = true } - var tabSelectionOptionalBinding: Binding { - Binding( + var tabSelectionBinding: Binding { + Binding( get: { - self.tabSelection + self.tabSelection ?? .watchNow }, set: { newValue in - if newValue != nil { - self.tabSelection = newValue! - } + self.tabSelection = newValue } ) } diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index 642585cf..206909d9 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -104,6 +104,8 @@ 37732FF02703A26300F04329 /* ValidationStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FEF2703A26300F04329 /* ValidationStatusView.swift */; }; 37732FF12703A26300F04329 /* ValidationStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FEF2703A26300F04329 /* ValidationStatusView.swift */; }; 37732FF22703A26300F04329 /* ValidationStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FEF2703A26300F04329 /* ValidationStatusView.swift */; }; + 37732FF42703D32400F04329 /* Sidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FF32703D32400F04329 /* Sidebar.swift */; }; + 37732FF52703D32400F04329 /* Sidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FF32703D32400F04329 /* Sidebar.swift */; }; 377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; }; 377A20AA2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; }; 377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; }; @@ -331,6 +333,7 @@ 376B2E0626F920D600B1D64D /* SignInRequiredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInRequiredView.swift; sourceTree = ""; }; 376CD21526FBE18D001E1AC1 /* Instance+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Instance+Fixtures.swift"; sourceTree = ""; }; 37732FEF2703A26300F04329 /* ValidationStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidationStatusView.swift; sourceTree = ""; }; + 37732FF32703D32400F04329 /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = ""; }; 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.swift; sourceTree = ""; }; 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSection.swift; sourceTree = ""; }; 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSectionBody.swift; sourceTree = ""; }; @@ -478,6 +481,7 @@ 37BA794626DC2E56002A0235 /* AppSidebarSubscriptions.swift */, 37D4B0C32671614700C925CA /* AppTabNavigation.swift */, 37BD07B42698AA4D003EBB87 /* ContentView.swift */, + 37732FF32703D32400F04329 /* Sidebar.swift */, ); path = Navigation; sourceTree = ""; @@ -1115,6 +1119,7 @@ 373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */, 37B81B0226D2CAE700675966 /* PlaybackBar.swift in Sources */, 372915E62687E3B900F5A35B /* Defaults.swift in Sources */, + 37732FF42703D32400F04329 /* Sidebar.swift in Sources */, 37D4B19726717E1500C925CA /* Video.swift in Sources */, 37484C2926FC83FF00287258 /* AccountFormView.swift in Sources */, 371F2F1A269B43D300E4A7AB /* NavigationModel.swift in Sources */, @@ -1182,6 +1187,7 @@ 37B81AFD26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */, 37A9965B26D6F8CA006E3224 /* VideosCellsHorizontal.swift in Sources */, 3761AC1026F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */, + 37732FF52703D32400F04329 /* Sidebar.swift in Sources */, 379775942689365600DD52A8 /* Array+Next.swift in Sources */, 3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */, 37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */, diff --git a/Shared/Navigation/AppSidebarNavigation.swift b/Shared/Navigation/AppSidebarNavigation.swift index 7d408860..90079f4e 100644 --- a/Shared/Navigation/AppSidebarNavigation.swift +++ b/Shared/Navigation/AppSidebarNavigation.swift @@ -4,27 +4,11 @@ import SwiftUI #endif struct AppSidebarNavigation: View { - enum SidebarGroup: String, Identifiable { - case main - - var id: RawValue { - rawValue - } - } - @EnvironmentObject private var api - @EnvironmentObject private var instances @EnvironmentObject private var navigation - @EnvironmentObject private var playlists - @EnvironmentObject private var recents - @EnvironmentObject private var subscriptions @State private var didApplyPrimaryViewWorkAround = false - var selection: Binding { - navigation.tabSelectionOptionalBinding - } - var body: some View { #if os(iOS) content.introspectViewController { viewController in @@ -48,7 +32,7 @@ struct AppSidebarNavigation: View { var content: some View { NavigationView { - sidebar + Sidebar() .toolbar { toolbarContent } .frame(minWidth: sidebarMinWidth) @@ -57,90 +41,6 @@ struct AppSidebarNavigation: View { .environment(\.navigationStyle, .sidebar) } - var sidebar: some View { - ScrollViewReader { scrollView in - List { - ForEach(sidebarGroups) { group in - sidebarGroupContent(group) - .id(group) - } - - .onChange(of: navigation.sidebarSectionChanged) { _ in - scrollScrollViewToItem(scrollView: scrollView, for: navigation.tabSelection) - } - } - .listStyle(.sidebar) - } - .toolbar { - toolbarContent - } - } - - var sidebarGroups: [SidebarGroup] { - [.main] - } - - func sidebarGroupContent(_ group: SidebarGroup) -> some View { - switch group { - case .main: - return Group { - mainNavigationLinks - - AppSidebarRecents(selection: selection) - .id("recentlyOpened") - - if api.signedIn { - AppSidebarSubscriptions(selection: selection) - AppSidebarPlaylists(selection: selection) - } - } - } - } - - var mainNavigationLinks: some View { - Section("Videos") { - NavigationLink(destination: LazyView(WatchNowView()), tag: TabSelection.watchNow, selection: selection) { - Label("Watch Now", systemImage: "play.circle") - .accessibility(label: Text("Watch Now")) - } - - if api.signedIn { - NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: selection) { - Label("Subscriptions", systemImage: "star.circle") - .accessibility(label: Text("Subscriptions")) - } - } - - NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: selection) { - Label("Popular", systemImage: "chart.bar") - .accessibility(label: Text("Popular")) - } - - NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: selection) { - Label("Trending", systemImage: "chart.line.uptrend.xyaxis") - .accessibility(label: Text("Trending")) - } - - NavigationLink(destination: LazyView(SearchView()), tag: TabSelection.search, selection: selection) { - Label("Search", systemImage: "magnifyingglass") - .accessibility(label: Text("Search")) - } - .keyboardShortcut("f") - } - } - - func scrollScrollViewToItem(scrollView: ScrollViewProxy, for selection: TabSelection) { - if case let .channel(id) = selection { - if subscriptions.isSubscribing(id) { - scrollView.scrollTo(id) - } else { - scrollView.scrollTo("recentlyOpened") - } - } else if case let .playlist(id) = selection { - scrollView.scrollTo(id) - } - } - var toolbarContent: some ToolbarContent { Group { #if os(iOS) @@ -171,12 +71,6 @@ struct AppSidebarNavigation: View { #endif } - #if os(macOS) - private func toggleSidebar() { - NSApp.keyWindow?.contentViewController?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) - } - #endif - static func symbolSystemImage(_ name: String) -> String { let firstLetter = name.first?.lowercased() let regex = #"^[a-z0-9]$"# diff --git a/Shared/Navigation/AppSidebarPlaylists.swift b/Shared/Navigation/AppSidebarPlaylists.swift index 1f71b5dc..e68c35a1 100644 --- a/Shared/Navigation/AppSidebarPlaylists.swift +++ b/Shared/Navigation/AppSidebarPlaylists.swift @@ -4,12 +4,10 @@ struct AppSidebarPlaylists: View { @EnvironmentObject private var navigation @EnvironmentObject private var playlists - @Binding var selection: TabSelection? - var body: some View { Section(header: Text("Playlists")) { ForEach(playlists.playlists.sorted { $0.title.lowercased() < $1.title.lowercased() }) { playlist in - NavigationLink(tag: TabSelection.playlist(playlist.id), selection: $selection) { + NavigationLink(tag: TabSelection.playlist(playlist.id), selection: $navigation.tabSelection) { LazyView(PlaylistVideosView(playlist)) } label: { Label(playlist.title, systemImage: AppSidebarNavigation.symbolSystemImage(playlist.title)) diff --git a/Shared/Navigation/AppSidebarRecents.swift b/Shared/Navigation/AppSidebarRecents.swift index be95aa72..5261cadd 100644 --- a/Shared/Navigation/AppSidebarRecents.swift +++ b/Shared/Navigation/AppSidebarRecents.swift @@ -2,8 +2,6 @@ import Defaults import SwiftUI struct AppSidebarRecents: View { - @Binding var selection: TabSelection? - @EnvironmentObject private var navigation @EnvironmentObject private var recents @@ -17,11 +15,11 @@ struct AppSidebarRecents: View { Group { switch recent.type { case .channel: - RecentNavigationLink(recent: recent, selection: $selection) { - LazyView(ChannelVideosView(channel: Channel(id: recent.id, name: recent.title))) + RecentNavigationLink(recent: recent) { + LazyView(ChannelVideosView(channel: recent.channel!)) } case .query: - RecentNavigationLink(recent: recent, selection: $selection, systemImage: "magnifyingglass") { + RecentNavigationLink(recent: recent, systemImage: "magnifyingglass") { LazyView(SearchView(recent.query!)) } } @@ -44,28 +42,25 @@ struct AppSidebarRecents: View { } struct RecentNavigationLink: View { + @EnvironmentObject private var navigation @EnvironmentObject private var recents var recent: RecentItem - @Binding var selection: TabSelection? - var systemImage: String? let destination: DestinationContent init( recent: RecentItem, - selection: Binding, systemImage: String? = nil, @ViewBuilder destination: () -> DestinationContent ) { self.recent = recent - _selection = selection self.systemImage = systemImage self.destination = destination() } var body: some View { - NavigationLink(tag: TabSelection.recentlyOpened(recent.tag), selection: $selection) { + NavigationLink(tag: TabSelection.recentlyOpened(recent.tag), selection: $navigation.tabSelection) { destination } label: { HStack { diff --git a/Shared/Navigation/AppSidebarSubscriptions.swift b/Shared/Navigation/AppSidebarSubscriptions.swift index b7f3c39d..5da81841 100644 --- a/Shared/Navigation/AppSidebarSubscriptions.swift +++ b/Shared/Navigation/AppSidebarSubscriptions.swift @@ -5,12 +5,10 @@ struct AppSidebarSubscriptions: View { @EnvironmentObject private var navigation @EnvironmentObject private var subscriptions - @Binding var selection: TabSelection? - var body: some View { Section(header: Text("Subscriptions")) { ForEach(subscriptions.all) { channel in - NavigationLink(tag: TabSelection.channel(channel.id), selection: $selection) { + NavigationLink(tag: TabSelection.channel(channel.id), selection: $navigation.tabSelection) { LazyView(ChannelVideosView(channel: channel)) } label: { Label(channel.name, systemImage: AppSidebarNavigation.symbolSystemImage(channel.name)) @@ -21,6 +19,7 @@ struct AppSidebarSubscriptions: View { } } .modifier(UnsubscribeAlertModifier()) + .id("channel\(channel.id)") } } .onAppear { diff --git a/Shared/Navigation/AppTabNavigation.swift b/Shared/Navigation/AppTabNavigation.swift index a6f32139..459c1975 100644 --- a/Shared/Navigation/AppTabNavigation.swift +++ b/Shared/Navigation/AppTabNavigation.swift @@ -7,7 +7,7 @@ struct AppTabNavigation: View { @EnvironmentObject private var recents var body: some View { - TabView(selection: $navigation.tabSelection) { + TabView(selection: navigation.tabSelectionBinding) { NavigationView { LazyView(WatchNowView()) .toolbar { toolbarContent } diff --git a/Shared/Navigation/Sidebar.swift b/Shared/Navigation/Sidebar.swift new file mode 100644 index 00000000..81f64835 --- /dev/null +++ b/Shared/Navigation/Sidebar.swift @@ -0,0 +1,66 @@ +import SwiftUI + +struct Sidebar: View { + @EnvironmentObject private var api + @EnvironmentObject private var navigation + + var body: some View { + ScrollViewReader { scrollView in + List { + mainNavigationLinks + + AppSidebarRecents() + .id("recentlyOpened") + + if api.signedIn { + AppSidebarSubscriptions() + AppSidebarPlaylists() + } + } + .onChange(of: navigation.sidebarSectionChanged) { _ in + scrollScrollViewToItem(scrollView: scrollView, for: navigation.tabSelection) + } + .listStyle(.sidebar) + } + } + + var mainNavigationLinks: some View { + Section("Videos") { + NavigationLink(destination: LazyView(WatchNowView()), tag: TabSelection.watchNow, selection: $navigation.tabSelection) { + Label("Watch Now", systemImage: "play.circle") + .accessibility(label: Text("Watch Now")) + } + + if api.signedIn { + NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) { + Label("Subscriptions", systemImage: "star.circle") + .accessibility(label: Text("Subscriptions")) + } + } + + NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) { + Label("Popular", systemImage: "chart.bar") + .accessibility(label: Text("Popular")) + } + + NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) { + Label("Trending", systemImage: "chart.line.uptrend.xyaxis") + .accessibility(label: Text("Trending")) + } + + NavigationLink(destination: LazyView(SearchView()), tag: TabSelection.search, selection: $navigation.tabSelection) { + Label("Search", systemImage: "magnifyingglass") + .accessibility(label: Text("Search")) + } + .keyboardShortcut("f") + } + } + + func scrollScrollViewToItem(scrollView: ScrollViewProxy, for selection: TabSelection) { + if case .recentlyOpened = selection { + scrollView.scrollTo("recentlyOpened") + } else if case let .playlist(id) = selection { + scrollView.scrollTo(id) + } + } +} diff --git a/Shared/Views/VideoContextMenuView.swift b/Shared/Views/VideoContextMenuView.swift index 14a0b405..916f406e 100644 --- a/Shared/Views/VideoContextMenuView.swift +++ b/Shared/Views/VideoContextMenuView.swift @@ -11,62 +11,70 @@ struct VideoContextMenuView: View { let video: Video var body: some View { - Section { - openChannelButton + openChannelButton - subscriptionButton + subscriptionButton - if case let .playlist(id) = navigation.tabSelection { - removeFromPlaylistButton(playlistID: id) - } + if navigation.tabSelection != .playlists { + addToPlaylistButton + } else if let playlist = playlists.currentPlaylist { + removeFromPlaylistButton(playlistID: playlist.id) + } - if navigation.tabSelection == .playlists { - removeFromPlaylistButton(playlistID: playlists.currentPlaylist!.id) - } else { - addToPlaylistButton - } + if case let .playlist(id) = navigation.tabSelection { + removeFromPlaylistButton(playlistID: id) } } var openChannelButton: some View { - Button("\(video.author) Channel") { + Button { let recent = RecentItem(from: video.channel) recents.add(recent) - navigation.tabSelection = .recentlyOpened(recent.tag) navigation.isChannelOpen = true navigation.sidebarSectionChanged.toggle() + navigation.tabSelection = .recentlyOpened(recent.tag) + } label: { + Label("\(video.author) Channel", systemImage: "rectangle.stack.fill.badge.person.crop") } } var subscriptionButton: some View { Group { if subscriptions.isSubscribing(video.channel.id) { - Button("Unsubscribe", role: .destructive) { + Button(role: .destructive) { #if os(tvOS) subscriptions.unsubscribe(video.channel.id) #else navigation.presentUnsubscribeAlert(video.channel) #endif + } label: { + Label("Unsubscribe", systemImage: "xmark.circle") } } else { - Button("Subscribe") { + Button { subscriptions.subscribe(video.channel.id) { navigation.sidebarSectionChanged.toggle() } + } label: { + Label("Subscribe", systemImage: "star.circle") } } } } var addToPlaylistButton: some View { - Button("Add to playlist...") { + Button { navigation.presentAddToPlaylist(video) + } label: { + Label("Add to playlist...", systemImage: "text.badge.plus") } } func removeFromPlaylistButton(playlistID: String) -> some View { - Button("Remove from playlist", role: .destructive) { + Button(role: .destructive) { playlists.removeVideoFromPlaylist(videoIndexID: video.indexID!, playlistID: playlistID) + } label: { + Label("Remove from playlist", systemImage: "text.badge.minus") } } } diff --git a/tvOS/TVNavigationView.swift b/tvOS/TVNavigationView.swift index d06f6518..64a06e64 100644 --- a/tvOS/TVNavigationView.swift +++ b/tvOS/TVNavigationView.swift @@ -8,7 +8,7 @@ struct TVNavigationView: View { @EnvironmentObject private var search var body: some View { - TabView(selection: $navigation.tabSelection) { + TabView(selection: navigation.tabSelectionBinding) { WatchNowView() .tabItem { Text("Watch Now") } .tag(TabSelection.watchNow)