From e61d1dfe2e17369f07cd1e7e1aafd247d33363a6 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 1 Dec 2021 12:22:19 +0100 Subject: [PATCH] Add settings for selecting visible sections (fixes #16) --- Model/NavigationModel.swift | 2 +- Shared/Defaults.swift | 52 ++++++-- Shared/Navigation/AppSidebarNavigation.swift | 4 + Shared/Navigation/AppTabNavigation.swift | 117 +++++++++--------- Shared/Navigation/ContentView.swift | 10 ++ Shared/Navigation/Sidebar.swift | 31 +++-- Shared/Search/SearchView.swift | 2 +- Shared/Settings/BrowsingSettings.swift | 118 ++++++++++++++----- Shared/Settings/PlaybackSettings.swift | 6 - Shared/Views/FavoriteButton.swift | 29 +++-- tvOS/TVNavigationView.swift | 23 ++-- 11 files changed, 265 insertions(+), 129 deletions(-) diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index 782a2a6a..cf27762d 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -23,7 +23,7 @@ final class NavigationModel: ObservableObject { } } - @Published var tabSelection: TabSelection! = .favorites + @Published var tabSelection: TabSelection! @Published var presentingAddToPlaylist = false @Published var videoToAddToPlaylist: Video! diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index dea023ef..c7c56b9d 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -44,9 +44,7 @@ extension Defaults.Keys { static let trendingCategory = Key("trendingCategory", default: .default) static let trendingCountry = Key("trendingCountry", default: .us) - #if os(iOS) - static let tabNavigationSection = Key("tabNavigationSection", default: .trending) - #endif + static let visibleSections = Key>("visibleSections", default: [.favorites, .subscriptions, .trending, .playlists]) } enum ResolutionSetting: String, CaseIterable, Defaults.Serializable { @@ -83,8 +81,48 @@ enum PlayerSidebarSetting: String, CaseIterable, Defaults.Serializable { } } -#if os(iOS) - enum TabNavigationSectionSetting: String, Defaults.Serializable { - case trending, popular +enum VisibleSection: String, CaseIterable, Comparable, Defaults.Serializable { + case favorites, subscriptions, popular, trending, playlists + + static func from(_ string: String) -> VisibleSection { + allCases.first { $0.rawValue == string }! } -#endif + + var title: String { + rawValue.localizedCapitalized + } + + var tabSelection: TabSelection { + switch self { + case .favorites: + return TabSelection.favorites + case .subscriptions: + return TabSelection.subscriptions + case .popular: + return TabSelection.popular + case .trending: + return TabSelection.trending + case .playlists: + return TabSelection.playlists + } + } + + private var sortOrder: Int { + switch self { + case .favorites: + return 0 + case .subscriptions: + return 1 + case .popular: + return 2 + case .trending: + return 3 + case .playlists: + return 4 + } + } + + static func < (lhs: Self, rhs: Self) -> Bool { + lhs.sortOrder < rhs.sortOrder + } +} diff --git a/Shared/Navigation/AppSidebarNavigation.swift b/Shared/Navigation/AppSidebarNavigation.swift index 2f89a27e..8b7478bf 100644 --- a/Shared/Navigation/AppSidebarNavigation.swift +++ b/Shared/Navigation/AppSidebarNavigation.swift @@ -1,3 +1,4 @@ +import Defaults import SwiftUI #if os(iOS) import Introspect @@ -5,6 +6,9 @@ import SwiftUI struct AppSidebarNavigation: View { @EnvironmentObject private var accounts + + @Default(.visibleSections) private var visibleSections + #if os(iOS) @EnvironmentObject private var navigation @State private var didApplyPrimaryViewWorkAround = false diff --git a/Shared/Navigation/AppTabNavigation.swift b/Shared/Navigation/AppTabNavigation.swift index 3c40f92f..79cd23dd 100644 --- a/Shared/Navigation/AppTabNavigation.swift +++ b/Shared/Navigation/AppTabNavigation.swift @@ -8,69 +8,31 @@ struct AppTabNavigation: View { @EnvironmentObject private var recents @EnvironmentObject private var search - @Default(.tabNavigationSection) private var tabNavigationSection + @Default(.visibleSections) private var visibleSections var body: some View { TabView(selection: navigation.tabSelectionBinding) { - NavigationView { - LazyView(FavoritesView()) - .toolbar { toolbarContent } - } - .tabItem { - Label("Favorites", systemImage: "heart") - .accessibility(label: Text("Favorites")) - } - .tag(TabSelection.favorites) - - if subscriptionsVisible { - NavigationView { - LazyView(SubscriptionsView()) - .toolbar { toolbarContent } - } - .tabItem { - Label("Subscriptions", systemImage: "star.circle.fill") - .accessibility(label: Text("Subscriptions")) - } - .tag(TabSelection.subscriptions) + if visibleSections.contains(.favorites) { + favoritesNavigationView } if subscriptionsVisible { - if accounts.app.supportsPopular { - if tabNavigationSection == .popular { - popularNavigationView - } else { - trendingNavigationView - } - } else { - trendingNavigationView - } - } else { - if accounts.app.supportsPopular { - popularNavigationView - } + subscriptionsNavigationView + } + + if visibleSections.contains(.popular), accounts.app.supportsPopular { + popularNavigationView + } + + if visibleSections.contains(.trending) { trendingNavigationView } - if accounts.app.supportsUserPlaylists { - NavigationView { - LazyView(PlaylistsView()) - .toolbar { toolbarContent } - } - .tabItem { - Label("Playlists", systemImage: "list.and.film") - .accessibility(label: Text("Playlists")) - } - .tag(TabSelection.playlists) + if visibleSections.contains(.playlists), accounts.app.supportsUserPlaylists { + playlistsNavigationView } - NavigationView { - LazyView(SearchView()) - } - .tabItem { - Label("Search", systemImage: "magnifyingglass") - .accessibility(label: Text("Search")) - } - .tag(TabSelection.search) + searchNavigationView } .id(accounts.current?.id ?? "") .environment(\.navigationStyle, .tab) @@ -107,8 +69,33 @@ struct AppTabNavigation: View { ) } + private var favoritesNavigationView: some View { + NavigationView { + LazyView(FavoritesView()) + .toolbar { toolbarContent } + } + .tabItem { + Label("Favorites", systemImage: "heart") + .accessibility(label: Text("Favorites")) + } + .tag(TabSelection.favorites) + } + + private var subscriptionsNavigationView: some View { + NavigationView { + LazyView(SubscriptionsView()) + .toolbar { toolbarContent } + } + .tabItem { + Label("Subscriptions", systemImage: "star.circle.fill") + .accessibility(label: Text("Subscriptions")) + } + .tag(TabSelection.subscriptions) + } + private var subscriptionsVisible: Bool { - accounts.app.supportsSubscriptions && !(accounts.current?.anonymous ?? true) + visibleSections.contains(.subscriptions) && + accounts.app.supportsSubscriptions && !(accounts.current?.anonymous ?? true) } private var popularNavigationView: some View { @@ -135,6 +122,30 @@ struct AppTabNavigation: View { .tag(TabSelection.trending) } + private var playlistsNavigationView: some View { + NavigationView { + LazyView(PlaylistsView()) + .toolbar { toolbarContent } + } + .tabItem { + Label("Playlists", systemImage: "list.and.film") + .accessibility(label: Text("Playlists")) + } + .tag(TabSelection.playlists) + } + + private var searchNavigationView: some View { + NavigationView { + LazyView(SearchView()) + .toolbar { toolbarContent } + } + .tabItem { + Label("Search", systemImage: "magnifyingglass") + .accessibility(label: Text("Search")) + } + .tag(TabSelection.search) + } + private var playerNavigationLink: some View { NavigationLink(isActive: $player.playerNavigationLinkActive, destination: { VideoPlayerView() diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 828755be..becb7f58 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -140,6 +140,16 @@ struct ContentView: View { if !accounts.current.isNil { player.loadHistoryDetails() } + + var section = Defaults[.visibleSections].min()?.tabSelection + + #if os(macOS) + if section == .playlists { + section = .search + } + #endif + + navigation.tabSelection = section ?? .search } func openWelcomeScreenIfAccountEmpty() { diff --git a/Shared/Navigation/Sidebar.swift b/Shared/Navigation/Sidebar.swift index 3b5b7f71..3d45c6ad 100644 --- a/Shared/Navigation/Sidebar.swift +++ b/Shared/Navigation/Sidebar.swift @@ -1,3 +1,4 @@ +import Defaults import SwiftUI struct Sidebar: View { @@ -6,6 +7,8 @@ struct Sidebar: View { @EnvironmentObject private var playlists @EnvironmentObject private var subscriptions + @Default(.visibleSections) private var visibleSections + var body: some View { ScrollViewReader { scrollView in List { @@ -16,11 +19,11 @@ struct Sidebar: View { .id("recentlyOpened") if accounts.api.signedIn { - if accounts.app.supportsSubscriptions { + if visibleSections.contains(.subscriptions), accounts.app.supportsSubscriptions { AppSidebarSubscriptions() } - if accounts.app.supportsUserPlaylists { + if visibleSections.contains(.playlists), accounts.app.supportsUserPlaylists { AppSidebarPlaylists() } } @@ -47,27 +50,33 @@ struct Sidebar: View { var mainNavigationLinks: some View { Section(header: Text("Videos")) { - NavigationLink(destination: LazyView(FavoritesView()), tag: TabSelection.favorites, selection: $navigation.tabSelection) { - Label("Favorites", systemImage: "heart") - .accessibility(label: Text("Favorites")) + if visibleSections.contains(.favorites) { + NavigationLink(destination: LazyView(FavoritesView()), tag: TabSelection.favorites, selection: $navigation.tabSelection) { + Label("Favorites", systemImage: "heart") + .accessibility(label: Text("Favorites")) + } } - if accounts.app.supportsSubscriptions && accounts.signedIn { + if visibleSections.contains(.subscriptions), + accounts.app.supportsSubscriptions && accounts.signedIn + { NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) { Label("Subscriptions", systemImage: "star.circle") .accessibility(label: Text("Subscriptions")) } } - if accounts.app.supportsPopular { + if visibleSections.contains(.popular), accounts.app.supportsPopular { NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) { Label("Popular", systemImage: "arrow.up.right.circle") .accessibility(label: Text("Popular")) } } - NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) { - Label("Trending", systemImage: "chart.bar") - .accessibility(label: Text("Trending")) + if visibleSections.contains(.trending) { + NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) { + Label("Trending", systemImage: "chart.bar") + .accessibility(label: Text("Trending")) + } } NavigationLink(destination: LazyView(SearchView()), tag: TabSelection.search, selection: $navigation.tabSelection) { @@ -78,7 +87,7 @@ struct Sidebar: View { } } - func scrollScrollViewToItem(scrollView: ScrollViewProxy, for selection: TabSelection) { + private func scrollScrollViewToItem(scrollView: ScrollViewProxy, for selection: TabSelection) { if case .recentlyOpened = selection { scrollView.scrollTo("recentlyOpened") } else if case let .playlist(id) = selection { diff --git a/Shared/Search/SearchView.swift b/Shared/Search/SearchView.swift index 1248e92f..4cccae9d 100644 --- a/Shared/Search/SearchView.swift +++ b/Shared/Search/SearchView.swift @@ -176,7 +176,7 @@ struct SearchView: View { .navigationTitle("Search") #endif #if os(iOS) - .navigationBarHidden(true) + .navigationBarHidden(!Defaults[.visibleSections].isEmpty || navigationStyle == .sidebar) #endif } diff --git a/Shared/Settings/BrowsingSettings.swift b/Shared/Settings/BrowsingSettings.swift index 7e9f75f0..8c01cfa4 100644 --- a/Shared/Settings/BrowsingSettings.swift +++ b/Shared/Settings/BrowsingSettings.swift @@ -4,42 +4,98 @@ import SwiftUI struct BrowsingSettings: View { @Default(.channelOnThumbnail) private var channelOnThumbnail @Default(.timeOnThumbnail) private var timeOnThumbnail - #if os(iOS) - @Default(.tabNavigationSection) private var tabNavigationSection - #endif + @Default(.saveHistory) private var saveHistory + @Default(.visibleSections) private var visibleSections var body: some View { - Section(header: SettingsHeader(text: "Browsing"), footer: footer) { - Toggle("Display channel names on thumbnails", isOn: $channelOnThumbnail) - Toggle("Display video length on thumbnails", isOn: $timeOnThumbnail) + Group { + Section(header: SettingsHeader(text: "Browsing")) { + Toggle("Show channel name on thumbnail", isOn: $channelOnThumbnail) + Toggle("Show video length on thumbnail", isOn: $timeOnThumbnail) + Toggle("Save history of played videos", isOn: $saveHistory) + } + Section(header: SettingsHeader(text: "Sections")) { + #if os(macOS) + let list = List(VisibleSection.allCases, id: \.self) { section in + VisibleSectionSelectionRow( + title: section.title, + selected: visibleSections.contains(section) + ) { value in + toggleSection(section, value: value) + } + } - #if os(iOS) - preferredTabPicker - #endif - } - .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) - - #if os(macOS) - Spacer() - #endif - } - - var footer: some View { - #if os(iOS) - Text("This tab will be displayed when there is no space to display all tabs") - #else - EmptyView() - #endif - } - - #if os(iOS) - var preferredTabPicker: some View { - Picker("Preferred tab", selection: $tabNavigationSection) { - Text("Trending").tag(TabNavigationSectionSetting.trending) - Text("Popular").tag(TabNavigationSectionSetting.popular) + Group { + if #available(macOS 12.0, *) { + list + .listStyle(.inset(alternatesRowBackgrounds: true)) + } else { + list + .listStyle(.inset) + } + } + #else + ForEach(VisibleSection.allCases, id: \.self) { section in + VisibleSectionSelectionRow( + title: section.title, + selected: visibleSections.contains(section) + ) { value in + toggleSection(section, value: value) + } + } + #endif } } - #endif + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + } + + func toggleSection(_ section: VisibleSection, value: Bool) { + if value { + visibleSections.insert(section) + } else { + visibleSections.remove(section) + } + } + + struct VisibleSectionSelectionRow: View { + let title: String + let selected: Bool + var action: (Bool) -> Void + + @State private var toggleChecked = false + + var body: some View { + Button(action: { action(!selected) }) { + HStack { + #if os(macOS) + Toggle(isOn: $toggleChecked) { + Text(self.title) + Spacer() + } + .onAppear { + toggleChecked = selected + } + .onChange(of: toggleChecked) { new in + action(new) + } + #else + Text(self.title) + Spacer() + if selected { + Image(systemName: "checkmark") + #if os(iOS) + .foregroundColor(.accentColor) + #endif + } + #endif + } + .contentShape(Rectangle()) + } + #if !os(tvOS) + .buttonStyle(.plain) + #endif + } + } } struct BrowsingSettings_Previews: PreviewProvider { diff --git a/Shared/Settings/PlaybackSettings.swift b/Shared/Settings/PlaybackSettings.swift index 1f678e79..85488743 100644 --- a/Shared/Settings/PlaybackSettings.swift +++ b/Shared/Settings/PlaybackSettings.swift @@ -27,7 +27,6 @@ struct PlaybackSettings: View { } keywordsToggle - saveHistoryToggle } #else Section(header: SettingsHeader(text: "Source")) { @@ -45,7 +44,6 @@ struct PlaybackSettings: View { #endif keywordsToggle - saveHistoryToggle #endif } @@ -109,10 +107,6 @@ struct PlaybackSettings: View { private var keywordsToggle: some View { Toggle("Show video keywords", isOn: $showKeywords) } - - private var saveHistoryToggle: some View { - Toggle("Save history of played videos", isOn: $saveHistory) - } } struct PlaybackSettings_Previews: PreviewProvider { diff --git a/Shared/Views/FavoriteButton.swift b/Shared/Views/FavoriteButton.swift index 71af228e..bd1c10ae 100644 --- a/Shared/Views/FavoriteButton.swift +++ b/Shared/Views/FavoriteButton.swift @@ -1,3 +1,4 @@ +import Defaults import Foundation import SwiftUI @@ -7,19 +8,27 @@ struct FavoriteButton: View { @State private var isFavorite = false + @Default(.visibleSections) private var visibleSections + var body: some View { - Button { - favorites.toggle(item) - isFavorite.toggle() - } label: { - if isFavorite { - Label("Remove from Favorites", systemImage: "heart.fill") + Group { + if visibleSections.contains(.favorites) { + Button { + favorites.toggle(item) + isFavorite.toggle() + } label: { + if isFavorite { + Label("Remove from Favorites", systemImage: "heart.fill") + } else { + Label("Add to Favorites", systemImage: "heart") + } + } + .onAppear { + isFavorite = favorites.contains(item) + } } else { - Label("Add to Favorites", systemImage: "heart") + EmptyView() } } - .onAppear { - isFavorite = favorites.contains(item) - } } } diff --git a/tvOS/TVNavigationView.swift b/tvOS/TVNavigationView.swift index 049b60e6..6b87646a 100644 --- a/tvOS/TVNavigationView.swift +++ b/tvOS/TVNavigationView.swift @@ -8,29 +8,34 @@ struct TVNavigationView: View { @EnvironmentObject private var recents @EnvironmentObject private var search + @Default(.visibleSections) private var visibleSections var body: some View { TabView(selection: navigation.tabSelectionBinding) { - FavoritesView() - .tabItem { Text("Favorites") } - .tag(TabSelection.favorites) + if visibleSections.contains(.favorites) { + FavoritesView() + .tabItem { Text("Favorites") } + .tag(TabSelection.favorites) + } - if accounts.app.supportsSubscriptions { + if visibleSections.contains(.subscriptions), accounts.app.supportsSubscriptions { SubscriptionsView() .tabItem { Text("Subscriptions") } .tag(TabSelection.subscriptions) } - if accounts.app.supportsPopular { + if visibleSections.contains(.popular), accounts.app.supportsPopular { PopularView() .tabItem { Text("Popular") } .tag(TabSelection.popular) } - TrendingView() - .tabItem { Text("Trending") } - .tag(TabSelection.trending) + if visibleSections.contains(.trending) { + TrendingView() + .tabItem { Text("Trending") } + .tag(TabSelection.trending) + } - if accounts.app.supportsUserPlaylists { + if visibleSections.contains(.playlists), accounts.app.supportsUserPlaylists { PlaylistsView() .tabItem { Text("Playlists") } .tag(TabSelection.playlists)