From 48e608e62bd141d4e6a9622b0926f7c7df78973e Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 20 Sep 2023 13:28:32 -0600 Subject: [PATCH] Filter Toggles [iOS] [iPadOS] (#847) Co-authored-by: Joe Kribs --- Shared/Coordinators/SettingsCoordinator.swift | 7 + Shared/Objects/FilterDrawerSelection.swift | 92 +++++++++++++ Shared/Services/SwiftfinDefaults.swift | 13 ++ Swiftfin.xcodeproj/project.pbxproj | 34 ++++- .../FilterDrawerHStack.swift | 49 ++----- Swiftfin/Views/LibraryView.swift | 21 +-- Swiftfin/Views/SearchView.swift | 21 +-- .../SettingsView/CustomizeViewsSettings.swift | 23 +++- .../FilterDrawerButtonSelectorView.swift | 122 ++++++++++++++++++ 9 files changed, 324 insertions(+), 58 deletions(-) create mode 100644 Shared/Objects/FilterDrawerSelection.swift create mode 100644 Swiftfin/Views/SettingsView/FilterDrawerSettingsView/Components/FilterDrawerButtonSelectorView.swift diff --git a/Shared/Coordinators/SettingsCoordinator.swift b/Shared/Coordinators/SettingsCoordinator.swift index 6cade937..9bd1f4f9 100644 --- a/Shared/Coordinators/SettingsCoordinator.swift +++ b/Shared/Coordinators/SettingsCoordinator.swift @@ -34,6 +34,8 @@ final class SettingsCoordinator: NavigationCoordinatable { @Route(.push) var experimentalSettings = makeExperimentalSettings @Route(.push) + var filterDrawerButtonSelector = makeFilterDrawerButtonSelector + @Route(.push) var indicatorSettings = makeIndicatorSettings @Route(.push) var serverDetail = makeServerDetail @@ -115,9 +117,14 @@ final class SettingsCoordinator: NavigationCoordinatable { } #endif + func makeFilterDrawerButtonSelector(selectedButtonsBinding: Binding<[FilterDrawerButtonSelection]>) -> some View { + FilterDrawerButtonSelectorView(selectedButtonsBinding: selectedButtonsBinding) + } + func makeVideoPlayerSettings() -> VideoPlayerSettingsCoordinator { VideoPlayerSettingsCoordinator() } + #endif #if os(tvOS) diff --git a/Shared/Objects/FilterDrawerSelection.swift b/Shared/Objects/FilterDrawerSelection.swift new file mode 100644 index 00000000..023107ac --- /dev/null +++ b/Shared/Objects/FilterDrawerSelection.swift @@ -0,0 +1,92 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import Defaults +import Foundation + +enum FilterDrawerButtonSelection: String, CaseIterable, Defaults.Serializable, Displayable, Identifiable { + + case filters + case genres + case order + case sort + + var displayTitle: String { + switch self { + case .filters: + return L10n.filters + case .genres: + return L10n.genres + case .order: + return L10n.order + case .sort: + return L10n.sort + } + } + + var id: String { + rawValue + } + + var itemFilter: WritableKeyPath { + switch self { + case .filters: + return \.filters + case .genres: + return \.genres + case .order: + return \.sortOrder + case .sort: + return \.sortBy + } + } + + var selectorType: SelectorType { + switch self { + case .filters, .genres: + return .multi + case .order, .sort: + return .single + } + } + + var itemFilterDefault: [ItemFilters.Filter] { + switch self { + case .filters: + return [] + case .genres: + return [] + case .order: + return [APISortOrder.ascending.filter] + case .sort: + return [SortBy.name.filter] + } + } + + func isItemsFilterActive(activeFilters: ItemFilters) -> Bool { + switch self { + case .filters: + return activeFilters.filters != self.itemFilterDefault + case .genres: + return activeFilters.genres != self.itemFilterDefault + case .order: + return activeFilters.sortOrder != self.itemFilterDefault + case .sort: + return activeFilters.sortBy != self.itemFilterDefault + } + } + + static var defaultFilterDrawerButtons: [FilterDrawerButtonSelection] { + [ + .filters, + .genres, + .order, + .sort, + ] + } +} diff --git a/Shared/Services/SwiftfinDefaults.swift b/Shared/Services/SwiftfinDefaults.swift index 81abb0cc..8eb446a1 100644 --- a/Shared/Services/SwiftfinDefaults.swift +++ b/Shared/Services/SwiftfinDefaults.swift @@ -72,6 +72,19 @@ extension Defaults.Keys { static let showFavorites: Key = .init("libraryShowFavorites", default: true, suite: .generalSuite) static let viewType = Key("libraryViewType", default: .grid, suite: .generalSuite) } + + enum Filters { + static let libraryFilterDrawerButtons: Key<[FilterDrawerButtonSelection]> = .init( + "defaultLibraryFilterDrawerButtons", + default: FilterDrawerButtonSelection.defaultFilterDrawerButtons, + suite: .generalSuite + ) + static let searchFilterDrawerButtons: Key<[FilterDrawerButtonSelection]> = .init( + "defaultSearchFilterDrawerButtons", + default: FilterDrawerButtonSelection.defaultFilterDrawerButtons, + suite: .generalSuite + ) + } } enum VideoPlayer { diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 9f9459ef..39a51896 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091B5A872683142E00D78B61 /* ServerDiscovery.swift */; }; 091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091B5A872683142E00D78B61 /* ServerDiscovery.swift */; }; + 4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */; }; + 4E8B34EA2AB91B6E0018F305 /* FilterDrawerSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* FilterDrawerSelection.swift */; }; + 4E8B34EB2AB91B6E0018F305 /* FilterDrawerSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8B34E92AB91B6E0018F305 /* FilterDrawerSelection.swift */; }; + 4EAA35BB2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EAA35BA2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift */; }; 531690E7267ABD79005D8AB9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531690E6267ABD79005D8AB9 /* HomeView.swift */; }; 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; }; 531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531AC8BE26750DE20091C7EB /* ImageView.swift */; }; @@ -622,7 +626,6 @@ E1CCF12E28ABF989006CAC9E /* PosterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CCF12D28ABF989006CAC9E /* PosterType.swift */; }; E1CCF13128AC07EC006CAC9E /* PosterHStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */; }; E1CD13EF28EF364100CB46CA /* DetectOrientationModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */; }; - E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */; }; E1CEFBF727914E6400F60429 /* CustomizeViewsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */; }; E1CFE28028FA606800B7D34C /* ChapterTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CFE27F28FA606800B7D34C /* ChapterTrack.swift */; }; E1D3043228D175CE00587289 /* StaticLibraryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D3043128D175CE00587289 /* StaticLibraryViewModel.swift */; }; @@ -771,6 +774,9 @@ /* Begin PBXFileReference section */ 091B5A872683142E00D78B61 /* ServerDiscovery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerDiscovery.swift; sourceTree = ""; }; + 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; + 4E8B34E92AB91B6E0018F305 /* FilterDrawerSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterDrawerSelection.swift; sourceTree = ""; }; + 4EAA35BA2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterDrawerButtonSelectorView.swift; sourceTree = ""; }; 531690E6267ABD79005D8AB9 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 531690F9267AD6EC005D8AB9 /* PlainNavigationLinkButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlainNavigationLinkButton.swift; sourceTree = ""; }; 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = ""; }; @@ -1193,7 +1199,6 @@ E1CCF12D28ABF989006CAC9E /* PosterType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterType.swift; sourceTree = ""; }; E1CCF13028AC07EC006CAC9E /* PosterHStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PosterHStack.swift; sourceTree = ""; }; E1CD13EE28EF364100CB46CA /* DetectOrientationModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetectOrientationModifier.swift; sourceTree = ""; }; - E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; E1CEFBF627914E6400F60429 /* CustomizeViewsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeViewsSettings.swift; sourceTree = ""; }; E1CFE27F28FA606800B7D34C /* ChapterTrack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterTrack.swift; sourceTree = ""; }; E1D3043128D175CE00587289 /* StaticLibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLibraryViewModel.swift; sourceTree = ""; }; @@ -1375,6 +1380,22 @@ path = ServerDiscovery; sourceTree = ""; }; + 4EAA35B82AB9694000D840DD /* FilterDrawerSettingsView */ = { + isa = PBXGroup; + children = ( + 4EAA35B92AB9694D00D840DD /* Components */, + ); + path = FilterDrawerSettingsView; + sourceTree = ""; + }; + 4EAA35B92AB9694D00D840DD /* Components */ = { + isa = PBXGroup; + children = ( + 4EAA35BA2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift */, + ); + path = Components; + sourceTree = ""; + }; 5310694F2684E7EE00CFFDBA /* VideoPlayer */ = { isa = PBXGroup; children = ( @@ -1503,6 +1524,7 @@ 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */, E17FB55128C119D400311DFE /* Displayable.swift */, E129429728F4785200796AC6 /* EnumPicker.swift */, + 4E8B34E92AB91B6E0018F305 /* FilterDrawerSelection.swift */, E1092F4B29106F9F00163F57 /* GestureAction.swift */, E19169CD272514760085832A /* HTTPScheme.swift */, 535870AC2669D8DD00D05A09 /* ItemFilters.swift */, @@ -2694,7 +2716,8 @@ E1E5D54A2783E26100692DFE /* SettingsView */ = { isa = PBXGroup; children = ( - E1CEFBF427914C7700F60429 /* CustomizeViewsSettings.swift */, + 4EAA35B82AB9694000D840DD /* FilterDrawerSettingsView */, + 4E5E48E42AB59806003F1B48 /* CustomizeViewsSettings.swift */, E175AFF2299AC117004DCF52 /* DebugSettingsView.swift */, E1E5D54B2783E27200692DFE /* ExperimentalSettingsView.swift */, E104C86F296E087200C1C3F9 /* IndicatorSettingsView.swift */, @@ -3279,6 +3302,7 @@ C4BE077A2726EE82003F4AD1 /* LiveTVTabCoordinator.swift in Sources */, E193D553271943D500900D82 /* tvOSMainTabCoordinator.swift in Sources */, E1575E83293E784A001665B1 /* MediaItemViewModel.swift in Sources */, + 4E8B34EB2AB91B6E0018F305 /* FilterDrawerSelection.swift in Sources */, E174121029AE9D94003EF3B5 /* NavigationCoordinatable.swift in Sources */, E154965F296CA2EF00C4EF88 /* DownloadTask.swift in Sources */, E154967E296CCB6C00C4EF88 /* BasicNavigationCoordinator.swift in Sources */, @@ -3300,6 +3324,7 @@ E1B33ECF28EB6EA90073B0FD /* OverlayMenu.swift in Sources */, 6220D0B426D5ED8000B8E046 /* LibraryCoordinator.swift in Sources */, E17AC96D2954E9CA003D2BC2 /* DownloadListView.swift in Sources */, + 4E8B34EA2AB91B6E0018F305 /* FilterDrawerSelection.swift in Sources */, E1A1528828FD229500600579 /* ChevronButton.swift in Sources */, E1B490472967E2E500D3EDCE /* CoreStore.swift in Sources */, 6220D0C026D61C5000B8E046 /* ItemCoordinator.swift in Sources */, @@ -3405,7 +3430,6 @@ E1C8CE5B28FE512400DF5D7B /* CGPoint.swift in Sources */, E18ACA922A15A32F00BB4F35 /* (null) in Sources */, E1E1E24D28DF8A2E000DF5FD /* PreferenceKeys.swift in Sources */, - E1CEFBF527914C7700F60429 /* CustomizeViewsSettings.swift in Sources */, E1C812BC277A8E5D00918266 /* PlaybackSpeed.swift in Sources */, E15756322935642A00976E1F /* Float.swift in Sources */, E139CC1D28EC836F00688DE2 /* ChapterOverlay.swift in Sources */, @@ -3434,6 +3458,7 @@ E1171A1928A2212600FA1AF5 /* QuickConnectView.swift in Sources */, E1DC9819296DD1CD00982F06 /* CinematicBackgroundView.swift in Sources */, E11CEB8D28999B4A003E74C7 /* Font.swift in Sources */, + 4EAA35BB2AB9699B00D840DD /* FilterDrawerButtonSelectorView.swift in Sources */, E139CC1F28EC83E400688DE2 /* Int.swift in Sources */, E11895A9289383BC0042947B /* ScrollViewOffsetModifier.swift in Sources */, E1DA656C28E78C1700592A73 /* MenuPosterHStackModel.swift in Sources */, @@ -3568,6 +3593,7 @@ E1937A3E288F0D3D00CB80AA /* UIScreen.swift in Sources */, C4BE076F2720FEFF003F4AD1 /* PlainNavigationLinkButton.swift in Sources */, E1EBCB46278BD595009FE6E9 /* ItemOverviewView.swift in Sources */, + 4E5E48E52AB59806003F1B48 /* CustomizeViewsSettings.swift in Sources */, E19F6C5D28F5189300C5197E /* MediaStreamInfoView.swift in Sources */, E1D8429329340B8300D1041A /* Utilities.swift in Sources */, E18CE0B428A22EDA0092E7F1 /* RepeatingTimer.swift in Sources */, diff --git a/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift b/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift index 7810263b..724076f2 100644 --- a/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift +++ b/Swiftfin/Components/FilterDrawerHStack/FilterDrawerHStack.swift @@ -6,6 +6,7 @@ // Copyright (c) 2023 Jellyfin & Jellyfin Contributors // +import Defaults import JellyfinAPI import SwiftUI @@ -13,7 +14,7 @@ struct FilterDrawerHStack: View { @ObservedObject private var viewModel: FilterViewModel - + private var filterDrawerButtonSelection: [FilterDrawerButtonSelection] private var onSelect: (FilterCoordinator.Parameters) -> Void var body: some View { @@ -29,55 +30,29 @@ struct FilterDrawerHStack: View { FilterDrawerButton(systemName: "line.3.horizontal.decrease.circle.fill", activated: true) } } - - FilterDrawerButton(title: L10n.genres, activated: viewModel.currentFilters.genres != []) + ForEach(filterDrawerButtonSelection, id: \.self) { button in + FilterDrawerButton(title: button.displayTitle, activated: button.isItemsFilterActive( + activeFilters: viewModel.currentFilters + )) .onSelect { onSelect(.init( - title: L10n.genres, + title: button.displayTitle, viewModel: viewModel, - filter: \.genres, - selectorType: .multi - )) - } - - FilterDrawerButton(title: L10n.filters, activated: viewModel.currentFilters.filters != []) - .onSelect { - onSelect(.init( - title: L10n.filters, - viewModel: viewModel, - filter: \.filters, - selectorType: .multi - )) - } - - FilterDrawerButton(title: L10n.order, activated: viewModel.currentFilters.sortOrder != [APISortOrder.ascending.filter]) - .onSelect { - onSelect(.init( - title: L10n.order, - viewModel: viewModel, - filter: \.sortOrder, - selectorType: .single - )) - } - - FilterDrawerButton(title: L10n.sort, activated: viewModel.currentFilters.sortBy != [SortBy.name.filter]) - .onSelect { - onSelect(.init( - title: L10n.sort, - viewModel: viewModel, - filter: \.sortBy, - selectorType: .single + filter: button.itemFilter, + selectorType: button.selectorType )) } + } } } } extension FilterDrawerHStack { - init(viewModel: FilterViewModel) { + init(viewModel: FilterViewModel, filterDrawerButtonSelection: [FilterDrawerButtonSelection]) { self.init( viewModel: viewModel, + filterDrawerButtonSelection: filterDrawerButtonSelection, onSelect: { _ in } ) } diff --git a/Swiftfin/Views/LibraryView.swift b/Swiftfin/Views/LibraryView.swift index eb883c29..ce378034 100644 --- a/Swiftfin/Views/LibraryView.swift +++ b/Swiftfin/Views/LibraryView.swift @@ -16,6 +16,9 @@ struct LibraryView: View { @Default(.Customization.Library.viewType) private var libraryViewType + @Default(.Customization.Filters.libraryFilterDrawerButtons) + private var filterDrawerButtonSelection + @EnvironmentObject private var router: LibraryCoordinator.Router @@ -67,14 +70,16 @@ struct LibraryView: View { } .navigationTitle(viewModel.parent?.displayTitle ?? "") .navigationBarTitleDisplayMode(.inline) - .navBarDrawer { - ScrollView(.horizontal, showsIndicators: false) { - FilterDrawerHStack(viewModel: viewModel.filterViewModel) - .onSelect { filterCoordinatorParameters in - router.route(to: \.filter, filterCoordinatorParameters) - } - .padding(.horizontal) - .padding(.vertical, 1) + .if(!filterDrawerButtonSelection.isEmpty) { view in + view.navBarDrawer { + ScrollView(.horizontal, showsIndicators: false) { + FilterDrawerHStack(viewModel: viewModel.filterViewModel, filterDrawerButtonSelection: filterDrawerButtonSelection) + .onSelect { filterCoordinatorParameters in + router.route(to: \.filter, filterCoordinatorParameters) + } + .padding(.horizontal) + .padding(.vertical, 1) + } } } .toolbar { diff --git a/Swiftfin/Views/SearchView.swift b/Swiftfin/Views/SearchView.swift index 55257b3f..9c95d482 100644 --- a/Swiftfin/Views/SearchView.swift +++ b/Swiftfin/Views/SearchView.swift @@ -16,6 +16,9 @@ struct SearchView: View { @Default(.Customization.searchPosterType) private var searchPosterType + @Default(.Customization.Filters.searchFilterDrawerButtons) + private var filterDrawerButtonSelection + @EnvironmentObject private var router: SearchCoordinator.Router @@ -105,14 +108,16 @@ struct SearchView: View { } .navigationTitle(L10n.search) .navigationBarTitleDisplayMode(.inline) - .navBarDrawer { - ScrollView(.horizontal, showsIndicators: false) { - FilterDrawerHStack(viewModel: viewModel.filterViewModel) - .onSelect { filterCoordinatorParameters in - router.route(to: \.filter, filterCoordinatorParameters) - } - .padding(.horizontal) - .padding(.vertical, 1) + .if(!filterDrawerButtonSelection.isEmpty) { view in + view.navBarDrawer { + ScrollView(.horizontal, showsIndicators: false) { + FilterDrawerHStack(viewModel: viewModel.filterViewModel, filterDrawerButtonSelection: filterDrawerButtonSelection) + .onSelect { filterCoordinatorParameters in + router.route(to: \.filter, filterCoordinatorParameters) + } + .padding(.horizontal) + .padding(.vertical, 1) + } } } .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: L10n.search) diff --git a/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift b/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift index 431d728d..029f26c9 100644 --- a/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift +++ b/Swiftfin/Views/SettingsView/CustomizeViewsSettings.swift @@ -24,6 +24,11 @@ struct CustomizeViewsSettings: View { @Default(.Customization.shouldShowMissingEpisodes) var shouldShowMissingEpisodes + @Default(.Customization.Filters.libraryFilterDrawerButtons) + var libraryFilterDrawerButtons + @Default(.Customization.Filters.searchFilterDrawerButtons) + var searchFilterDrawerButtons + @Default(.Customization.showPosterLabels) var showPosterLabels @Default(.Customization.nextUpPosterType) @@ -72,12 +77,28 @@ struct CustomizeViewsSettings: View { Section { Toggle(L10n.favorites, isOn: $showFavorites) - Toggle(L10n.randomImage, isOn: $libraryRandomImage) + } header: { L10n.library.text } + Section { + + ChevronButton(title: L10n.library) + .onSelect { + router.route(to: \.filterDrawerButtonSelector, $libraryFilterDrawerButtons) + } + + ChevronButton(title: L10n.search) + .onSelect { + router.route(to: \.filterDrawerButtonSelector, $searchFilterDrawerButtons) + } + + } header: { + L10n.filters.text + } + Section { Toggle(L10n.showMissingSeasons, isOn: $shouldShowMissingSeasons) Toggle(L10n.showMissingEpisodes, isOn: $shouldShowMissingEpisodes) diff --git a/Swiftfin/Views/SettingsView/FilterDrawerSettingsView/Components/FilterDrawerButtonSelectorView.swift b/Swiftfin/Views/SettingsView/FilterDrawerSettingsView/Components/FilterDrawerButtonSelectorView.swift new file mode 100644 index 00000000..62bc33ac --- /dev/null +++ b/Swiftfin/Views/SettingsView/FilterDrawerSettingsView/Components/FilterDrawerButtonSelectorView.swift @@ -0,0 +1,122 @@ +// +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2023 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +// TODO: Look at moving across sections +// TODO: Look at general implementation in SelectorView +struct FilterDrawerButtonSelectorView: View { + + @Binding + var selectedButtonsBinding: [FilterDrawerButtonSelection] + + @Environment(\.editMode) + private var editMode + + @State + private var _selectedButtons: [FilterDrawerButtonSelection] + + private var disabledButtons: [FilterDrawerButtonSelection] { + FilterDrawerButtonSelection.allCases.filter { !_selectedButtons.contains($0) } + } + + var body: some View { + List { + Section { + ForEach(_selectedButtons) { item in + Button { + if !(editMode?.wrappedValue.isEditing ?? true) { + select(item: item) + } + } label: { + HStack { + Text(item.displayTitle) + + Spacer() + + if !(editMode?.wrappedValue.isEditing ?? false) { + Image(systemName: "minus.circle.fill") + .foregroundColor(.red) + } + } + .foregroundColor(.primary) + } + } + .onMove(perform: move) + + if _selectedButtons.isEmpty { + Text("None") + .foregroundColor(.secondary) + } + } header: { + Text("Enabled") + } + + Section { + ForEach(disabledButtons) { item in + Button { + if !(editMode?.wrappedValue.isEditing ?? true) { + select(item: item) + } + } label: { + HStack { + Text(item.displayTitle) + + Spacer() + + if !(editMode?.wrappedValue.isEditing ?? false) { + Image(systemName: "plus.circle.fill") + .foregroundColor(.green) + } + } + .foregroundColor(.primary) + } + } + + if disabledButtons.isEmpty { + Text("None") + .foregroundColor(.secondary) + } + } header: { + Text("Disabled") + } + } + .animation(.linear(duration: 0.2), value: _selectedButtons) + .toolbar { + EditButton() + } + .onChange(of: _selectedButtons) { newValue in + selectedButtonsBinding = newValue + } + } + + func move(from source: IndexSet, to destination: Int) { + _selectedButtons.move(fromOffsets: source, toOffset: destination) + } + + private func select(item: FilterDrawerButtonSelection) { + if _selectedButtons.contains(item) { + _selectedButtons.removeAll(where: { $0.id == item.id }) + } else { + _selectedButtons.append(item) + } + } +} + +extension FilterDrawerButtonSelectorView { + + init(selectedButtonsBinding: Binding<[FilterDrawerButtonSelection]>) { + self.init( + selectedButtonsBinding: selectedButtonsBinding, + _selectedButtons: selectedButtonsBinding.wrappedValue + ) +// self._selectedButtonsBinding = selectedButtonsBinding +// self._selectedButtons = selectedButtonsBinding.wrappedValue + } +}