From c27ebe5ad2effaddfcb7711c4ad2d32d7d5e29e1 Mon Sep 17 00:00:00 2001 From: PangMo5 Date: Fri, 25 Jun 2021 18:49:54 +0900 Subject: [PATCH] add SearchablePickerView --- JellyfinPlayer.xcodeproj/project.pbxproj | 6 ++ JellyfinPlayer/SettingsView.swift | 32 ++++----- Shared/Extensions/SearchablePickerView.swift | 73 ++++++++++++++++++++ Shared/ViewModels/SettingsViewModel.swift | 30 +++++--- 4 files changed, 114 insertions(+), 27 deletions(-) create mode 100644 Shared/Extensions/SearchablePickerView.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 3963e0f1..e586b302 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -108,6 +108,8 @@ 621C638026672A30004216EA /* NukeUI in Frameworks */ = {isa = PBXBuildFile; productRef = 621C637F26672A30004216EA /* NukeUI */; }; 6225FCCB2663841E00E067F6 /* ParallaxHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */; }; 6228B1C22670EB010067FD35 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBFD263B596B003A4E83 /* PersistenceController.swift */; }; + 624C21752685CF60007F1390 /* SearchablePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624C21742685CF60007F1390 /* SearchablePickerView.swift */; }; + 624C21762685CF60007F1390 /* SearchablePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 624C21742685CF60007F1390 /* SearchablePickerView.swift */; }; 625CB5682678B6FB00530A6E /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5672678B6FB00530A6E /* SplashView.swift */; }; 625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5692678B71200530A6E /* SplashViewModel.swift */; }; 625CB56C2678C0FD00530A6E /* MainTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB56B2678C0FD00530A6E /* MainTabView.swift */; }; @@ -289,6 +291,7 @@ 621338922660107500A81A2A /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 621338B22660A07800A81A2A /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; 6225FCCA2663841E00E067F6 /* ParallaxHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParallaxHeader.swift; sourceTree = ""; }; + 624C21742685CF60007F1390 /* SearchablePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePickerView.swift; sourceTree = ""; }; 625CB5672678B6FB00530A6E /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; 625CB5692678B71200530A6E /* SplashViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewModel.swift; sourceTree = ""; }; 625CB56B2678C0FD00530A6E /* MainTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabView.swift; sourceTree = ""; }; @@ -599,6 +602,7 @@ 6267B3D92671138200A7371D /* ImageExtensions.swift */, 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */, 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */, + 624C21742685CF60007F1390 /* SearchablePickerView.swift */, ); path = Extensions; sourceTree = ""; @@ -952,6 +956,7 @@ 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, 531690E5267ABD5C005D8AB9 /* MainTabView.swift in Sources */, 5310695B2684E7EE00CFFDBA /* AudioView.swift in Sources */, + 624C21762685CF60007F1390 /* SearchablePickerView.swift in Sources */, 091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */, 53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */, 53ABFDEE26799DCD00886593 /* ImageView.swift in Sources */, @@ -1021,6 +1026,7 @@ 621338B32660A07800A81A2A /* LazyView.swift in Sources */, 531AC8BF26750DE20091C7EB /* ImageView.swift in Sources */, 62E632E0267D30CA0063E547 /* LibraryViewModel.swift in Sources */, + 624C21752685CF60007F1390 /* SearchablePickerView.swift in Sources */, 62EC352F267666A5000E9F2D /* SessionManager.swift in Sources */, 62E632E3267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, 091B5A8A2683142E00D78B61 /* ServerDiscovery.swift in Sources */, diff --git a/JellyfinPlayer/SettingsView.swift b/JellyfinPlayer/SettingsView.swift index c8c4fc1d..920fb24b 100644 --- a/JellyfinPlayer/SettingsView.swift +++ b/JellyfinPlayer/SettingsView.swift @@ -50,22 +50,22 @@ struct SettingsView: View { Section(header: Text("Accessibility")) { Toggle("Automatically show subtitles", isOn: $isAutoSelectSubtitles) - Picker("Subtitles Language preferences", selection: $autoSelectSubtitlesLangcode) { - Text("Auto") - .tag("Auto") - ForEach(viewModel.isoLanguageCodesPair, id: \.1) { - Text($0.0) - .tag($0.1) - } - } - Picker("Audio Language preferences", selection: $autoSelectAudioLangcode) { - Text("Auto") - .tag("Auto") - ForEach(viewModel.isoLanguageCodesPair, id: \.1) { - Text($0.0) - .tag($0.1) - } - } + SearchablePicker(label: "Subtitles Language preferences", + options: viewModel.langs, + optionToString: { $0.name }, + selected:Binding( + get: { viewModel.langs.first(where: { $0.isoCode == autoSelectSubtitlesLangcode }) ?? .auto }, + set: {autoSelectSubtitlesLangcode = $0.isoCode} + ) + ) + SearchablePicker(label: "Audio Language preferences", + options: viewModel.langs, + optionToString: { $0.name }, + selected: Binding( + get: { viewModel.langs.first(where: { $0.isoCode == autoSelectAudioLangcode }) ?? .auto }, + set: { autoSelectAudioLangcode = $0.isoCode} + ) + ) } Section { diff --git a/Shared/Extensions/SearchablePickerView.swift b/Shared/Extensions/SearchablePickerView.swift new file mode 100644 index 00000000..5a9f20b8 --- /dev/null +++ b/Shared/Extensions/SearchablePickerView.swift @@ -0,0 +1,73 @@ +// +/* + * 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 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Foundation +import SwiftUI + +private struct SearchablePickerView: View { + @Environment(\.presentationMode) + var presentationMode + + let options: [Selectable] + let optionToString: (Selectable) -> String + let label: String + + @State var text = "" + @Binding var selected: Selectable + + var body: some View { + VStack { + SearchBar(text: $text) + List(options.filter { + guard !text.isEmpty else { return true } + return optionToString($0).lowercased().contains(text.lowercased()) + }, id: \.self) { selectable in + Button(action: { + selected = selectable + presentationMode.wrappedValue.dismiss() + }) { + HStack { + Text(optionToString(selectable)).foregroundColor(Color.primary) + Spacer() + if selected == selectable { + Image(systemName: "checkmark").foregroundColor(.accentColor) + } + } + } + }.listStyle(GroupedListStyle()) + } + } +} + +struct SearchablePicker: View { + let label: String + let options: [Selectable] + let optionToString: (Selectable) -> String + + @Binding var selected: Selectable + + var body: some View { + NavigationLink(destination: searchablePickerView()) { + HStack { + Text(label) + Spacer() + Text(optionToString(selected)) + .foregroundColor(.gray) + .multilineTextAlignment(.trailing) + } + } + } + + private func searchablePickerView() -> some View { + SearchablePickerView(options: options, + optionToString: optionToString, + label: label, + selected: $selected) + } +} diff --git a/Shared/ViewModels/SettingsViewModel.swift b/Shared/ViewModels/SettingsViewModel.swift index 9db50528..12d03232 100644 --- a/Shared/ViewModels/SettingsViewModel.swift +++ b/Shared/ViewModels/SettingsViewModel.swift @@ -1,11 +1,11 @@ // - /* - * 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 2021 Aiden Vigue & Jellyfin Contributors - */ +/* + * 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 2021 Aiden Vigue & Jellyfin Contributors + */ import Foundation @@ -23,10 +23,17 @@ struct Bitrates: Codable, Hashable { public var value: Int } +struct Lang: Hashable { + var name: String + var isoCode: String + + static let auto = Lang(name: "Auto", isoCode: "Auto") +} + final class SettingsViewModel: ObservableObject { let currentLocale = Locale.current var bitrates: [Bitrates] = [] - var isoLanguageCodesPair = [(name: String, isoCode: String)]() + var langs = [Lang]() init() { let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")! @@ -41,10 +48,11 @@ final class SettingsViewModel: ObservableObject { } catch { print(error) } - - isoLanguageCodesPair = Locale.isoLanguageCodes.compactMap { + + self.langs = Locale.isoLanguageCodes.compactMap { guard let name = currentLocale.localizedString(forLanguageCode: $0) else { return nil } - return (name, $0) + return Lang(name: name, isoCode: $0) }.sorted(by: { $0.name < $1.name }) + self.langs.insert(.auto, at: 0) } }