diff --git a/JellyfinPlayer tvOS/Views/BasicAppSettingsView.swift b/JellyfinPlayer tvOS/Views/BasicAppSettingsView.swift new file mode 100644 index 00000000..2ff8fe9b --- /dev/null +++ b/JellyfinPlayer tvOS/Views/BasicAppSettingsView.swift @@ -0,0 +1,52 @@ +// + /* + * 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 Defaults +import Stinsen +import SwiftUI + +struct BasicAppSettingsView: View { + + @EnvironmentObject var basicAppSettingsRouter: BasicAppSettingsCoordinator.Router + @ObservedObject var viewModel: BasicAppSettingsViewModel + @State var resetTapped: Bool = false + + @Default(.appAppearance) var appAppearance + + var body: some View { + Form { + Section { + Picker(NSLocalizedString("Appearance", comment: ""), selection: $appAppearance) { + ForEach(self.viewModel.appearances, id: \.self) { appearance in + Text(appearance.localizedName).tag(appearance.rawValue) + } + }.onChange(of: appAppearance, perform: { _ in + UIApplication.shared.windows.first?.overrideUserInterfaceStyle = appAppearance.style + }) + } header: { + Text("Accessibility") + } + + Button { + resetTapped = true + } label: { + Text("Reset") + } + } + .alert("Reset", isPresented: $resetTapped, actions: { + Button(role: .destructive) { + viewModel.reset() + basicAppSettingsRouter.dismissCoordinator() + } label: { + Text("Reset") + } + }) + .navigationTitle("Settings") + } +} diff --git a/JellyfinPlayer tvOS/Views/ServerListView.swift b/JellyfinPlayer tvOS/Views/ServerListView.swift index c1a5fd05..518750b5 100644 --- a/JellyfinPlayer tvOS/Views/ServerListView.swift +++ b/JellyfinPlayer tvOS/Views/ServerListView.swift @@ -91,7 +91,7 @@ struct ServerListView: View { } @ViewBuilder - private var toolbarContent: some View { + private var trailingToolbarContent: some View { if viewModel.servers.isEmpty { EmptyView() } else { @@ -100,6 +100,13 @@ struct ServerListView: View { } label: { Image(systemName: "plus.circle.fill") } + .contextMenu { + Button { + serverListRouter.route(to: \.basicAppSettings) + } label: { + Text("Settings") + } + } } } @@ -108,7 +115,7 @@ struct ServerListView: View { .navigationTitle("Servers") .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { - toolbarContent + trailingToolbarContent } } .onAppear { diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 798012e5..2815b38a 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -206,8 +206,6 @@ 62C29EA326D1030F00C1D2E7 /* ConnectToServerCoodinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */; }; 62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */; }; 62C29EA826D103D500C1D2E7 /* LibraryListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */; }; - 62CB3F4B2685BB77003D0A6F /* DefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */; }; - 62CB3F4C2685BB77003D0A6F /* DefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */; }; 62E632DA267D2BC40063E547 /* LatestMediaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */; }; 62E632DC267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */; }; 62E632DD267D2E130063E547 /* LibrarySearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */; }; @@ -329,6 +327,9 @@ E1D4BF882719D27100A11E64 /* Bitrates.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF862719D27100A11E64 /* Bitrates.swift */; }; E1D4BF8A2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */; }; E1D4BF8B2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */; }; + E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; }; + E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; + E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; }; E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; @@ -515,7 +516,6 @@ 62C29EA226D1030F00C1D2E7 /* ConnectToServerCoodinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerCoodinator.swift; sourceTree = ""; }; 62C29EA526D1036A00C1D2E7 /* HomeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCoordinator.swift; sourceTree = ""; }; 62C29EA726D103D500C1D2E7 /* LibraryListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListCoordinator.swift; sourceTree = ""; }; - 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultsExtension.swift; sourceTree = ""; }; 62E632D9267D2BC40063E547 /* LatestMediaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaViewModel.swift; sourceTree = ""; }; 62E632DB267D2E130063E547 /* LibrarySearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchViewModel.swift; sourceTree = ""; }; 62E632DF267D30CA0063E547 /* LibraryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryViewModel.swift; sourceTree = ""; }; @@ -582,6 +582,7 @@ E1D4BF832719D25A00A11E64 /* TrackLanguage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackLanguage.swift; sourceTree = ""; }; E1D4BF862719D27100A11E64 /* Bitrates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bitrates.swift; sourceTree = ""; }; E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsCoordinator.swift; sourceTree = ""; }; + E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = ""; }; E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = ""; }; E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; @@ -1026,7 +1027,6 @@ 5389277B263CC3DB0035E14B /* BlurHashDecode.swift */, 6267B3D526710B8900A7371D /* CollectionExtensions.swift */, E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */, - 62CB3F4A2685BB77003D0A6F /* DefaultsExtension.swift */, 6267B3D92671138200A7371D /* ImageExtensions.swift */, E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */, 621338922660107500A81A2A /* StringExtensions.swift */, @@ -1119,6 +1119,7 @@ isa = PBXGroup; children = ( 53ABFDEA2679753200886593 /* ConnectToServerView.swift */, + E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */, 531690EB267ABF46005D8AB9 /* ContinueWatchingView.swift */, 531690E6267ABD79005D8AB9 /* HomeView.swift */, E193D54E271942C000900D82 /* ItemView */, @@ -1714,7 +1715,6 @@ 53ABFDE7267974EF00886593 /* ConnectToServerViewModel.swift in Sources */, 5398514626B64DBB00101B49 /* SearchablePickerView.swift in Sources */, 53ABFDEE26799DCD00886593 /* ImageView.swift in Sources */, - 62CB3F4C2685BB77003D0A6F /* DefaultsExtension.swift in Sources */, 62E632E4267D3BA60063E547 /* MovieItemViewModel.swift in Sources */, 53649AB2269D019100A2D8B7 /* LogManager.swift in Sources */, E13DD3D6271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, @@ -1741,6 +1741,7 @@ E193D53B27193F9200900D82 /* SettingsCoordinator.swift in Sources */, 536D3D74267BA8170004248C /* BackgroundManager.swift in Sources */, 535870632669D21600D05A09 /* JellyfinPlayer_tvOSApp.swift in Sources */, + E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */, E193D547271941C500900D82 /* UserListView.swift in Sources */, E1D4BF7F2719D1DD00A11E64 /* BasicAppSettingsViewModel.swift in Sources */, E193D53227193F7B00900D82 /* ConnectToServerCoodinator.swift in Sources */, @@ -1783,7 +1784,6 @@ E13DD3EC27178A54009D4DAF /* UserSignInViewModel.swift in Sources */, 625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */, 536D3D78267BD5C30004248C /* ViewModel.swift in Sources */, - 62CB3F4B2685BB77003D0A6F /* DefaultsExtension.swift in Sources */, E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */, E13DD3E527177D15009D4DAF /* ServerListView.swift in Sources */, E18845F826DEA9C900B0C5B7 /* ItemViewBody.swift in Sources */, @@ -1891,6 +1891,7 @@ E1FCD09A26C4F35A007C8DCF /* ErrorMessage.swift in Sources */, 628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */, E13DD3F72717E87D009D4DAF /* SwiftfinNotificationCenter.swift in Sources */, + E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */, 6220D0AF26D5EABE00B8E046 /* ViewExtensions.swift in Sources */, E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */, @@ -1898,6 +1899,7 @@ E13DD3CA27164B80009D4DAF /* SwiftfinStore.swift in Sources */, 62EC353226766849000E9F2D /* SessionManager.swift in Sources */, 536D3D79267BD5D00004248C /* ViewModel.swift in Sources */, + E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/JellyfinPlayer/Views/ConnectToServerView.swift b/JellyfinPlayer/Views/ConnectToServerView.swift index 1e262c44..eb546920 100644 --- a/JellyfinPlayer/Views/ConnectToServerView.swift +++ b/JellyfinPlayer/Views/ConnectToServerView.swift @@ -44,8 +44,10 @@ struct ConnectToServerView: View { if viewModel.searching { HStack(alignment: .center, spacing: 5) { Spacer() - ProgressView() - Text("Searching") + // Oct. 15, 2021 + // There is a bug where ProgressView() won't appear sometimes when searching, + // dots were used instead but ProgressView() is preferred + Text("Searching...") .foregroundColor(.secondary) Spacer() } @@ -61,6 +63,7 @@ struct ConnectToServerView: View { } else { ForEach(viewModel.discoveredServers.sorted(by: { $0.name < $1.name }), id: \.id) { discoveredServer in Button { + uri = discoveredServer.url.absoluteString viewModel.connectToServer(uri: discoveredServer.url.absoluteString) } label: { VStack(alignment: .leading, spacing: 5) { diff --git a/Shared/Extensions/DefaultsExtension.swift b/Shared/Extensions/DefaultsExtension.swift deleted file mode 100644 index 30cb48c9..00000000 --- a/Shared/Extensions/DefaultsExtension.swift +++ /dev/null @@ -1,22 +0,0 @@ -// - /* - * 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 Defaults - -extension Defaults.Keys { - static let inNetworkBandwidth = Key("InNetworkBandwidth", default: 40_000_000) - static let outOfNetworkBandwidth = Key("OutOfNetworkBandwidth", default: 40_000_000) - static let isAutoSelectSubtitles = Key("isAutoSelectSubtitles", default: false) - static let autoSelectSubtitlesLangCode = Key("AutoSelectSubtitlesLangCode", default: "Auto") - static let autoSelectAudioLangCode = Key("AutoSelectAudioLangCode", default: "Auto") - static let appAppearance = Key("appAppearance", default: .system) - static let videoPlayerJumpForward = Key("videoPlayerJumpForward", default: .thirty) - static let videoPlayerJumpBackward = Key("videoPlayerJumpBackward", default: .thirty) -} diff --git a/Shared/Singleton/SessionManager.swift b/Shared/Singleton/SessionManager.swift index d448014a..d803e8ac 100644 --- a/Shared/Singleton/SessionManager.swift +++ b/Shared/Singleton/SessionManager.swift @@ -179,12 +179,16 @@ final class SessionManager { } func purge() { + // Delete all servers let servers = fetchServers() for server in servers { delete(server: server) } + // Delete UserDefaults + SwiftfinStore.Defaults.suite.removeAll() + SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didPurge, object: nil) } @@ -224,7 +228,7 @@ final class SessionManager { let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String var deviceName = UIDevice.current.name deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current) - deviceName = String(deviceName.unicodeScalars.filter {CharacterSet.urlQueryAllowed.contains($0) }) + deviceName = String(deviceName.unicodeScalars.filter { CharacterSet.urlQueryAllowed.contains($0) }) let platform: String #if os(tvOS) diff --git a/Shared/SwiftfinStore/SwiftfinStore.swift b/Shared/SwiftfinStore/SwiftfinStore.swift index 66967aac..676c01c3 100644 --- a/Shared/SwiftfinStore/SwiftfinStore.swift +++ b/Shared/SwiftfinStore/SwiftfinStore.swift @@ -156,6 +156,7 @@ enum SwiftfinStore { }() } +// MARK: LocalizedError extension SwiftfinStore.Errors: LocalizedError { var title: String { diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index 3907fdf2..53ce5f84 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -22,4 +22,13 @@ extension SwiftfinStore { extension Defaults.Keys { static let lastServerUserID = Defaults.Key("lastServerUserID", suite: SwiftfinStore.Defaults.suite) + + static let inNetworkBandwidth = Key("InNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.suite) + static let outOfNetworkBandwidth = Key("OutOfNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.suite) + static let isAutoSelectSubtitles = Key("isAutoSelectSubtitles", default: false, suite: SwiftfinStore.Defaults.suite) + static let autoSelectSubtitlesLangCode = Key("AutoSelectSubtitlesLangCode", default: "Auto", suite: SwiftfinStore.Defaults.suite) + static let autoSelectAudioLangCode = Key("AutoSelectAudioLangCode", default: "Auto", suite: SwiftfinStore.Defaults.suite) + static let appAppearance = Key("appAppearance", default: .system, suite: SwiftfinStore.Defaults.suite) + static let videoPlayerJumpForward = Key("videoPlayerJumpForward", default: .thirty, suite: SwiftfinStore.Defaults.suite) + static let videoPlayerJumpBackward = Key("videoPlayerJumpBackward", default: .thirty, suite: SwiftfinStore.Defaults.suite) } diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index 2ea3b086..ac03ec61 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -50,10 +50,11 @@ final class ConnectToServerViewModel: ViewModel { } func discoverServers() { + discoveredServers.removeAll() searching = true // Timeout after 5 seconds - DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { self.searching = false } @@ -61,7 +62,6 @@ final class ConnectToServerViewModel: ViewModel { if let server = server { discoveredServers.insert(server) } - searching = false } }