From acef7b88060bed224b6deb8c578b6164b6955fbc Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 21:30:47 -0600 Subject: [PATCH 01/14] Bring over iOS app settings --- .../Views/BasicAppSettingsView.swift | 154 +++++++++--------- 1 file changed, 81 insertions(+), 73 deletions(-) diff --git a/Swiftfin tvOS/Views/BasicAppSettingsView.swift b/Swiftfin tvOS/Views/BasicAppSettingsView.swift index 3cd265ab5..069f5c423 100644 --- a/Swiftfin tvOS/Views/BasicAppSettingsView.swift +++ b/Swiftfin tvOS/Views/BasicAppSettingsView.swift @@ -10,81 +10,89 @@ import Defaults import Stinsen import SwiftUI -#warning("TODO: implement") - struct AppSettingsView: View { + @Default(.selectUserUseSplashscreen) + private var selectUserUseSplashscreen + @Default(.selectUserAllServersSplashscreen) + private var selectUserAllServersSplashscreen + + @Default(.appAppearance) + private var appearance + + @EnvironmentObject + private var router: AppSettingsCoordinator.Router + + @ObservedObject + var viewModel = SettingsViewModel() + + @State + private var resetUserSettingsSelected: Bool = false + @State + private var removeAllServersSelected: Bool = false + var body: some View { - Text("TODO") + SplitFormWindowView() + .descriptionView { + Image(.jellyfinBlobBlue) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: 400) + } + .contentView { + + ChevronButton(L10n.about) + .onSelect { +// router.route(to: \.about, viewModel) + } + + Section(L10n.accessibility) { + + ChevronButton(L10n.appIcon) + .onSelect { +// router.route(to: \.appIconSelector, viewModel) + } + + if !selectUserUseSplashscreen { + CaseIterablePicker( + L10n.appearance, + selection: $appearance + ) + } + } + + Section { + + Toggle("Use splashscreen", isOn: $selectUserUseSplashscreen) + + if selectUserUseSplashscreen { + Picker(L10n.servers, selection: $selectUserAllServersSplashscreen) { + + Section { + Label(L10n.random, systemImage: "dice.fill") + .tag(SelectUserServerSelection.all) + } + + ForEach(viewModel.servers) { server in + Text(server.name) + .tag(SelectUserServerSelection.server(id: server.id)) + } + } + } + } header: { + Text("Splashscreen") + } footer: { + if selectUserUseSplashscreen { + Text("When All Servers is selected, use the splashscreen from a single server or a random server") + } + } + +// SignOutIntervalSection() + + ChevronButton(L10n.logs) + .onSelect { + router.route(to: \.log) + } + } } } - -// struct BasicAppSettingsView: View { -// -// @EnvironmentObject -// private var router: BasicAppSettingsCoordinator.Router -// -// @ObservedObject -// var viewModel: SettingsViewModel -// -// @State -// private var resetUserSettingsSelected: Bool = false -// @State -// private var removeAllServersSelected: Bool = false -// -// var body: some View { -// SplitFormWindowView() -// .descriptionView { -// Image(.jellyfinBlobBlue) -// .resizable() -// .aspectRatio(contentMode: .fit) -// .frame(maxWidth: 400) -// } -// .contentView { -// -// Section { -// -// Button { -// TextPairView( -// leading: L10n.version, -// trailing: "\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))" -// ) -// } -// -// ChevronButton(L10n.logs) -// .onSelect { -// router.route(to: \.log) -// } -// } -// -// Section { -// -// Button { -// resetUserSettingsSelected = true -// } label: { -// L10n.resetUserSettings.text -// } -// -// Button { -// removeAllServersSelected = true -// } label: { -// Text(L10n.removeAllServers) -// } -// } -// } -// .withDescriptionTopPadding() -// .navigationTitle(L10n.settings) -// .alert(L10n.resetUserSettings, isPresented: $resetUserSettingsSelected) { -// Button(L10n.reset, role: .destructive) { -//// viewModel.resetUserSettings() -// } -// } message: { -// Text(L10n.resetAllSettings) -// } -// .alert(L10n.removeAllServers, isPresented: $removeAllServersSelected) { -// Button(L10n.reset, role: .destructive) { -//// viewModel.removeAllServers() -// } -// } -// } -// } From 2038a2c45141cdd2a559ec79638d0d4a2ea7779c Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 21:31:15 -0600 Subject: [PATCH 02/14] Re-enable advanced settings menu option --- .../Components/SelectUserBottomBar.swift | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift b/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift index ece7b9b16..2f61a93da 100644 --- a/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift +++ b/Swiftfin tvOS/Views/SelectUserView/Components/SelectUserBottomBar.swift @@ -12,6 +12,11 @@ extension SelectUserView { struct SelectUserBottomBar: View { + // MARK: - State & Environment Objects + + @EnvironmentObject + private var router: SelectUserCoordinator.Router + @Binding private var isEditing: Bool @@ -21,6 +26,8 @@ extension SelectUserView { @ObservedObject private var viewModel: SelectUserViewModel + // MARK: - Variables + private let areUsersSelected: Bool private let userCount: Int @@ -35,13 +42,12 @@ extension SelectUserView { Button(L10n.editUsers, systemImage: "person.crop.circle") { isEditing.toggle() } - // TODO: Advanced settings on tvOS? - // - // Divider() - // - // Button(L10n.advanced, systemImage: "gearshape.fill") { - // router.route(to: \.advancedSettings) - // } + + Divider() + + Button(L10n.advanced, systemImage: "gearshape.fill") { + router.route(to: \.advancedSettings) + } } label: { Label(L10n.advanced, systemImage: "gearshape.fill") .font(.body.weight(.semibold)) From 92a7a8f914ba7025ae1501a920ed55207bee0175 Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 21:31:29 -0600 Subject: [PATCH 03/14] Conditionally show splash screen --- Swiftfin tvOS/Views/SelectUserView/SelectUserView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Swiftfin tvOS/Views/SelectUserView/SelectUserView.swift b/Swiftfin tvOS/Views/SelectUserView/SelectUserView.swift index c96a54a74..2ed0dc831 100644 --- a/Swiftfin tvOS/Views/SelectUserView/SelectUserView.swift +++ b/Swiftfin tvOS/Views/SelectUserView/SelectUserView.swift @@ -28,6 +28,8 @@ struct SelectUserView: View { @Default(.selectUserServerSelection) private var serverSelection + @Default(.selectUserUseSplashscreen) + private var selectUserUseSplashscreen // MARK: - Environment Variable @@ -286,7 +288,7 @@ struct SelectUserView: View { } .animation(.linear(duration: 0.1), value: scrollViewOffset) .background { - if let splashScreenImageSource { + if let splashScreenImageSource, selectUserUseSplashscreen { ZStack { ImageView(splashScreenImageSource) .aspectRatio(contentMode: .fill) From 9e0611d5ccec17fb61248453db1c9f7184746a4a Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 22:06:05 -0600 Subject: [PATCH 04/14] Disable app appearance setting --- Swiftfin tvOS/Views/BasicAppSettingsView.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Swiftfin tvOS/Views/BasicAppSettingsView.swift b/Swiftfin tvOS/Views/BasicAppSettingsView.swift index 069f5c423..7873197db 100644 --- a/Swiftfin tvOS/Views/BasicAppSettingsView.swift +++ b/Swiftfin tvOS/Views/BasicAppSettingsView.swift @@ -53,12 +53,12 @@ struct AppSettingsView: View { // router.route(to: \.appIconSelector, viewModel) } - if !selectUserUseSplashscreen { - CaseIterablePicker( - L10n.appearance, - selection: $appearance - ) - } +// if !selectUserUseSplashscreen { +// CaseIterablePicker( +// L10n.appearance, +// selection: $appearance +// ) +// } } Section { From bebbda5adab14f367acb7b1f4de1a04c2776e3d8 Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 23:32:16 -0600 Subject: [PATCH 05/14] cleanup --- Shared/Coordinators/AppSettingsCoordinator.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Shared/Coordinators/AppSettingsCoordinator.swift b/Shared/Coordinators/AppSettingsCoordinator.swift index 3c897e21c..d6909253a 100644 --- a/Shared/Coordinators/AppSettingsCoordinator.swift +++ b/Shared/Coordinators/AppSettingsCoordinator.swift @@ -17,18 +17,14 @@ final class AppSettingsCoordinator: NavigationCoordinatable { @Root var start = makeStart + @Route(.modal) + var log = makeLog + #if os(iOS) @Route(.push) var about = makeAbout @Route(.push) var appIconSelector = makeAppIconSelector - @Route(.push) - var log = makeLog - #endif - - #if os(tvOS) - @Route(.modal) - var log = makeLog #endif init() {} From 4d3554efc425ce0f497e2c0c89624c36409cff5f Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 23:32:40 -0600 Subject: [PATCH 06/14] File rename --- .../{BasicAppSettingsView.swift => AppSettingsView.swift} | 0 Swiftfin.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) rename Swiftfin tvOS/Views/{BasicAppSettingsView.swift => AppSettingsView.swift} (100%) diff --git a/Swiftfin tvOS/Views/BasicAppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView.swift similarity index 100% rename from Swiftfin tvOS/Views/BasicAppSettingsView.swift rename to Swiftfin tvOS/Views/AppSettingsView.swift diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index fd6addffa..e5e9d5fea 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -1067,7 +1067,7 @@ E1D4BF812719D22800A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; }; E1D4BF8A2719D3D000A11E64 /* AppSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF892719D3D000A11E64 /* AppSettingsCoordinator.swift */; }; E1D4BF8B2719D3D000A11E64 /* AppSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF892719D3D000A11E64 /* AppSettingsCoordinator.swift */; }; - E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; }; + E1D4BF8F271A079A00A11E64 /* AppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* AppSettingsView.swift */; }; E1D5C39628DF90C100CDBEFB /* Slider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D5C39528DF90C100CDBEFB /* Slider.swift */; }; E1D5C39928DF914700CDBEFB /* CapsuleSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D5C39828DF914700CDBEFB /* CapsuleSlider.swift */; }; E1D5C39B28DF993400CDBEFB /* ThumbSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D5C39A28DF993400CDBEFB /* ThumbSlider.swift */; }; @@ -1944,7 +1944,7 @@ E1D4BF7B2719D05000A11E64 /* AppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsView.swift; sourceTree = ""; }; E1D4BF802719D22800A11E64 /* AppAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAppearance.swift; sourceTree = ""; }; E1D4BF892719D3D000A11E64 /* AppSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsCoordinator.swift; sourceTree = ""; }; - E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = ""; }; + E1D4BF8E271A079A00A11E64 /* AppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettingsView.swift; sourceTree = ""; }; E1D5C39528DF90C100CDBEFB /* Slider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Slider.swift; sourceTree = ""; }; E1D5C39828DF914700CDBEFB /* CapsuleSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsuleSlider.swift; sourceTree = ""; }; E1D5C39A28DF993400CDBEFB /* ThumbSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbSlider.swift; sourceTree = ""; }; @@ -3998,7 +3998,7 @@ isa = PBXGroup; children = ( E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */, - E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */, + E1D4BF8E271A079A00A11E64 /* AppSettingsView.swift */, E10231522BCF8AF8009D71FC /* ChannelLibraryView */, 4E4DAC3A2D11F54300E13FF9 /* ConnectToServerView */, E154967B296CBB1A00C4EF88 /* FontPickerView.swift */, @@ -5784,7 +5784,7 @@ E10231582BCF8AF8009D71FC /* WideChannelGridItem.swift in Sources */, E15D4F082B1B12C300442DB8 /* Backport.swift in Sources */, BDA623532D0D0854009A157F /* SelectUserBottomBar.swift in Sources */, - E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */, + E1D4BF8F271A079A00A11E64 /* AppSettingsView.swift in Sources */, E1575E9A293E7B1E001665B1 /* Array.swift in Sources */, E1575E8D293E7B1E001665B1 /* URLComponents.swift in Sources */, E187A60329AB28F0008387E6 /* RotateContentView.swift in Sources */, From f74d0b172696918137b2de378645b1e953f546c0 Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 23:37:08 -0600 Subject: [PATCH 07/14] Change how version is displayed --- Swiftfin tvOS/Views/AppSettingsView.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Swiftfin tvOS/Views/AppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView.swift index 7873197db..ff3d4ad8f 100644 --- a/Swiftfin tvOS/Views/AppSettingsView.swift +++ b/Swiftfin tvOS/Views/AppSettingsView.swift @@ -41,10 +41,10 @@ struct AppSettingsView: View { } .contentView { - ChevronButton(L10n.about) - .onSelect { -// router.route(to: \.about, viewModel) - } + TextPairView( + leading: L10n.version, + trailing: "\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))" + ) Section(L10n.accessibility) { From 84f30059993685437c36b42608c60e44e2c93c1e Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Mon, 3 Feb 2025 23:37:33 -0600 Subject: [PATCH 08/14] Disable app icon option due to not working --- Swiftfin tvOS/Views/AppSettingsView.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Swiftfin tvOS/Views/AppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView.swift index ff3d4ad8f..6f3b56229 100644 --- a/Swiftfin tvOS/Views/AppSettingsView.swift +++ b/Swiftfin tvOS/Views/AppSettingsView.swift @@ -48,10 +48,11 @@ struct AppSettingsView: View { Section(L10n.accessibility) { - ChevronButton(L10n.appIcon) - .onSelect { + // TODO: supposedly supported but not working +// ChevronButton(L10n.appIcon) +// .onSelect { // router.route(to: \.appIconSelector, viewModel) - } +// } // if !selectUserUseSplashscreen { // CaseIterablePicker( From 25331313dfcb65ef14101cf350500becade66a3b Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Wed, 5 Feb 2025 20:31:50 -0600 Subject: [PATCH 09/14] comment --- Swiftfin tvOS/Views/AppSettingsView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Swiftfin tvOS/Views/AppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView.swift index 6f3b56229..13da60613 100644 --- a/Swiftfin tvOS/Views/AppSettingsView.swift +++ b/Swiftfin tvOS/Views/AppSettingsView.swift @@ -54,6 +54,7 @@ struct AppSettingsView: View { // router.route(to: \.appIconSelector, viewModel) // } + // Disabled until tvOS can alter app appearance // if !selectUserUseSplashscreen { // CaseIterablePicker( // L10n.appearance, From 5a30482ea909f6c87851b7492ab1f3c0ee5e431c Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Wed, 5 Feb 2025 20:52:48 -0600 Subject: [PATCH 10/14] Bring over signout interval section --- .../AppSettingsView.swift | 2 +- .../Components/SignOutIntervalSection.swift | 72 +++++++++++++++++++ Swiftfin.xcodeproj/project.pbxproj | 22 +++++- 3 files changed, 94 insertions(+), 2 deletions(-) rename Swiftfin tvOS/Views/{ => AppSettingsView}/AppSettingsView.swift (98%) create mode 100644 Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift diff --git a/Swiftfin tvOS/Views/AppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift similarity index 98% rename from Swiftfin tvOS/Views/AppSettingsView.swift rename to Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift index 13da60613..9da82a282 100644 --- a/Swiftfin tvOS/Views/AppSettingsView.swift +++ b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift @@ -89,7 +89,7 @@ struct AppSettingsView: View { } } -// SignOutIntervalSection() + SignOutIntervalSection() ChevronButton(L10n.logs) .onSelect { diff --git a/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift b/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift new file mode 100644 index 000000000..ebb477ed6 --- /dev/null +++ b/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift @@ -0,0 +1,72 @@ +// +// 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) 2025 Jellyfin & Jellyfin Contributors +// + +import Defaults +import SwiftUI + +extension AppSettingsView { + + struct SignOutIntervalSection: View { + + @Default(.backgroundSignOutInterval) + private var backgroundSignOutInterval + @Default(.signOutOnBackground) + private var signOutOnBackground + @Default(.signOutOnClose) + private var signOutOnClose + + @State + private var isEditingBackgroundSignOutInterval: Bool = false + + var body: some View { + Section { + Toggle("Sign out on close", isOn: $signOutOnClose) + } footer: { + Text("Signs out the last user when Swiftfin has been force closed") + } + + // TODO: need to consider date picker options to re-enable +// Section { +// Toggle("Sign out on background", isOn: $signOutOnBackground) +// +// if signOutOnBackground { +// HStack { +// Text("Duration") +// +// Spacer() +// +// Button { +// isEditingBackgroundSignOutInterval.toggle() +// } label: { +// HStack { +// Text(backgroundSignOutInterval, format: .hourMinute) +// .foregroundStyle(.secondary) +// +// Image(systemName: "chevron.right") +// .font(.body.weight(.semibold)) +// .foregroundStyle(.secondary) +// .rotationEffect(isEditingBackgroundSignOutInterval ? .degrees(90) : .zero) +// .animation(.linear(duration: 0.075), value: isEditingBackgroundSignOutInterval) +// } +// } +// .foregroundStyle(.primary, .secondary) +// } +// +// if isEditingBackgroundSignOutInterval { +// HourMinutePicker(interval: $backgroundSignOutInterval) +// } +// } +// } footer: { +// Text( +// "Signs out the last user when Swiftfin has been in the background without media playback after some time" +// ) +// } +// .animation(.linear(duration: 0.15), value: isEditingBackgroundSignOutInterval) + } + } +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index e5e9d5fea..5bbabb062 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -396,6 +396,7 @@ BD39577C2C113FAA0078CEF8 /* TimestampSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */; }; BD39577E2C1140810078CEF8 /* TransitionSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD39577D2C1140810078CEF8 /* TransitionSection.swift */; }; BDA623532D0D0854009A157F /* SelectUserBottomBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA623522D0D0854009A157F /* SelectUserBottomBar.swift */; }; + BDF8BB6C2D5456B400628ADB /* SignOutIntervalSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF8BB6B2D5456B400628ADB /* SignOutIntervalSection.swift */; }; BDFF67B02D2CA59A009A9A3A /* UserLocalSecurityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */; }; BDFF67B22D2CA59A009A9A3A /* UserProfileSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.swift */; }; BDFF67B32D2CA99D009A9A3A /* UserProfileRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BE1CED2BDB68CD008176A9 /* UserProfileRow.swift */; }; @@ -1533,6 +1534,7 @@ BD39577B2C113FAA0078CEF8 /* TimestampSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampSection.swift; sourceTree = ""; }; BD39577D2C1140810078CEF8 /* TransitionSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionSection.swift; sourceTree = ""; }; BDA623522D0D0854009A157F /* SelectUserBottomBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectUserBottomBar.swift; sourceTree = ""; }; + BDF8BB6B2D5456B400628ADB /* SignOutIntervalSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutIntervalSection.swift; sourceTree = ""; }; BDFF67AD2D2CA59A009A9A3A /* UserLocalSecurityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserLocalSecurityView.swift; sourceTree = ""; }; BDFF67AE2D2CA59A009A9A3A /* UserProfileSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileSettingsView.swift; sourceTree = ""; }; C44FA6DE2AACD19C00EDEB56 /* LiveSmallPlaybackButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiveSmallPlaybackButton.swift; sourceTree = ""; }; @@ -3624,6 +3626,23 @@ path = Sections; sourceTree = ""; }; + BDF8BB692D54566400628ADB /* AppSettingsView */ = { + isa = PBXGroup; + children = ( + BDF8BB6A2D5456AA00628ADB /* Components */, + E1D4BF8E271A079A00A11E64 /* AppSettingsView.swift */, + ); + path = AppSettingsView; + sourceTree = ""; + }; + BDF8BB6A2D5456AA00628ADB /* Components */ = { + isa = PBXGroup; + children = ( + BDF8BB6B2D5456B400628ADB /* SignOutIntervalSection.swift */, + ); + path = Components; + sourceTree = ""; + }; BDFF67AF2D2CA59A009A9A3A /* UserProfileSettingsView */ = { isa = PBXGroup; children = ( @@ -3997,8 +4016,8 @@ E12186E02718F23B0010884C /* Views */ = { isa = PBXGroup; children = ( + BDF8BB692D54566400628ADB /* AppSettingsView */, E1763A752BF3FF01004DF6AB /* AppLoadingView.swift */, - E1D4BF8E271A079A00A11E64 /* AppSettingsView.swift */, E10231522BCF8AF8009D71FC /* ChannelLibraryView */, 4E4DAC3A2D11F54300E13FF9 /* ConnectToServerView */, E154967B296CBB1A00C4EF88 /* FontPickerView.swift */, @@ -5668,6 +5687,7 @@ 4EF18B282CB9936D00343666 /* ListColumnsPickerView.swift in Sources */, E11BDF7B2B85529D0045C54A /* SupportedCaseIterable.swift in Sources */, BDFF67B02D2CA59A009A9A3A /* UserLocalSecurityView.swift in Sources */, + BDF8BB6C2D5456B400628ADB /* SignOutIntervalSection.swift in Sources */, BDFF67B22D2CA59A009A9A3A /* UserProfileSettingsView.swift in Sources */, 4E204E592C574FD9004D22A2 /* CustomizeSettingsCoordinator.swift in Sources */, E1575E8C293E7B1E001665B1 /* UIScreen.swift in Sources */, From d509ceb41686b686e41a15dc456be5f1837eff72 Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Wed, 5 Feb 2025 21:04:57 -0600 Subject: [PATCH 11/14] Enforce sign-out on close --- Swiftfin tvOS/App/SwiftfinApp.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Swiftfin tvOS/App/SwiftfinApp.swift b/Swiftfin tvOS/App/SwiftfinApp.swift index 361cd2f07..b55f27d7d 100644 --- a/Swiftfin tvOS/App/SwiftfinApp.swift +++ b/Swiftfin tvOS/App/SwiftfinApp.swift @@ -52,6 +52,11 @@ struct SwiftfinApp: App { // UIKit UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.label] + + // don't keep last user id + if Defaults[.signOutOnClose] { + Defaults[.lastSignedInUserID] = .signedOut + } } var body: some Scene { From b85dfa904ffe34aa94e8280aeb823325434cbaf8 Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Thu, 6 Feb 2025 17:23:04 -0600 Subject: [PATCH 12/14] Revert change --- Shared/Coordinators/AppSettingsCoordinator.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Shared/Coordinators/AppSettingsCoordinator.swift b/Shared/Coordinators/AppSettingsCoordinator.swift index d6909253a..3c897e21c 100644 --- a/Shared/Coordinators/AppSettingsCoordinator.swift +++ b/Shared/Coordinators/AppSettingsCoordinator.swift @@ -17,14 +17,18 @@ final class AppSettingsCoordinator: NavigationCoordinatable { @Root var start = makeStart - @Route(.modal) - var log = makeLog - #if os(iOS) @Route(.push) var about = makeAbout @Route(.push) var appIconSelector = makeAppIconSelector + @Route(.push) + var log = makeLog + #endif + + #if os(tvOS) + @Route(.modal) + var log = makeLog #endif init() {} From 4c00aef9eb8f8d22e76d0efe676cd20a31865177 Mon Sep 17 00:00:00 2001 From: chickdan <=> Date: Thu, 6 Feb 2025 17:35:17 -0600 Subject: [PATCH 13/14] localizations --- Shared/Strings/Strings.swift | 16 +++++++++++++ .../AppSettingsView/AppSettingsView.swift | 6 ++--- .../Components/SignOutIntervalSection.swift | 10 ++++---- .../AppSettingsView/AppSettingsView.swift | 6 ++--- .../Components/SignOutIntervalSection.swift | 10 ++++---- Translations/en.lproj/Localizable.strings | 24 +++++++++++++++++++ 6 files changed, 56 insertions(+), 16 deletions(-) diff --git a/Shared/Strings/Strings.swift b/Shared/Strings/Strings.swift index e6db15e74..2239cd641 100644 --- a/Shared/Strings/Strings.swift +++ b/Shared/Strings/Strings.swift @@ -516,6 +516,8 @@ internal enum L10n { internal static func duplicateUserSaved(_ p1: Any) -> String { return L10n.tr("Localizable", "duplicateUserSaved", String(describing: p1), fallback: "%@ is already saved") } + /// Duration + internal static let duration = L10n.tr("Localizable", "duration", fallback: "Duration") /// DVD internal static let dvd = L10n.tr("Localizable", "dvd", fallback: "DVD") /// Edit @@ -1202,6 +1204,14 @@ internal enum L10n { internal static func signInToServer(_ p1: UnsafePointer) -> String { return L10n.tr("Localizable", "signInToServer", p1, fallback: "Sign In to %s") } + /// Sign out on background + internal static let signoutBackground = L10n.tr("Localizable", "signoutBackground", fallback: "Sign out on background") + /// Signs out the last user when Swiftfin has been in the background without media playback after some time + internal static let signoutBackgroundFooter = L10n.tr("Localizable", "signoutBackgroundFooter", fallback: "Signs out the last user when Swiftfin has been in the background without media playback after some time") + /// Sign out on close + internal static let signoutClose = L10n.tr("Localizable", "signoutClose", fallback: "Sign out on close") + /// Signs out the last user when Swiftfin has been force closed + internal static let signoutCloseFooter = L10n.tr("Localizable", "signoutCloseFooter", fallback: "Signs out the last user when Swiftfin has been force closed") /// Slider internal static let slider = L10n.tr("Localizable", "slider", fallback: "Slider") /// Slider Color @@ -1222,6 +1232,10 @@ internal enum L10n { internal static let sourceCode = L10n.tr("Localizable", "sourceCode", fallback: "Source Code") /// Special Features internal static let specialFeatures = L10n.tr("Localizable", "specialFeatures", fallback: "Special Features") + /// Splashscreen + internal static let splashscreen = L10n.tr("Localizable", "splashscreen", fallback: "Splashscreen") + /// When All Servers is selected, use the splashscreen from a single server or a random server + internal static let splashscreenFooter = L10n.tr("Localizable", "splashscreenFooter", fallback: "When All Servers is selected, use the splashscreen from a single server or a random server") /// Sports internal static let sports = L10n.tr("Localizable", "sports", fallback: "Sports") /// Start Time @@ -1402,6 +1416,8 @@ internal enum L10n { } /// Users internal static let users = L10n.tr("Localizable", "users", fallback: "Users") + /// Use splashscreen + internal static let useSplashscreen = L10n.tr("Localizable", "useSplashscreen", fallback: "Use splashscreen") /// Version internal static let version = L10n.tr("Localizable", "version", fallback: "Version") /// Video diff --git a/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift index 9da82a282..31656c6b6 100644 --- a/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift +++ b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift @@ -65,7 +65,7 @@ struct AppSettingsView: View { Section { - Toggle("Use splashscreen", isOn: $selectUserUseSplashscreen) + Toggle(L10n.useSplashscreen, isOn: $selectUserUseSplashscreen) if selectUserUseSplashscreen { Picker(L10n.servers, selection: $selectUserAllServersSplashscreen) { @@ -82,10 +82,10 @@ struct AppSettingsView: View { } } } header: { - Text("Splashscreen") + Text(L10n.splashscreen) } footer: { if selectUserUseSplashscreen { - Text("When All Servers is selected, use the splashscreen from a single server or a random server") + Text(L10n.splashscreenFooter) } } diff --git a/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift b/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift index ebb477ed6..be5be8818 100644 --- a/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift +++ b/Swiftfin tvOS/Views/AppSettingsView/Components/SignOutIntervalSection.swift @@ -25,18 +25,18 @@ extension AppSettingsView { var body: some View { Section { - Toggle("Sign out on close", isOn: $signOutOnClose) + Toggle(L10n.signoutClose, isOn: $signOutOnClose) } footer: { - Text("Signs out the last user when Swiftfin has been force closed") + Text(L10n.signoutCloseFooter) } // TODO: need to consider date picker options to re-enable // Section { -// Toggle("Sign out on background", isOn: $signOutOnBackground) +// Toggle(L10n.signoutBackground, isOn: $signOutOnBackground) // // if signOutOnBackground { // HStack { -// Text("Duration") +// Text(L10n.duration) // // Spacer() // @@ -63,7 +63,7 @@ extension AppSettingsView { // } // } footer: { // Text( -// "Signs out the last user when Swiftfin has been in the background without media playback after some time" +// L10n.signoutBackgroundFooter // ) // } // .animation(.linear(duration: 0.15), value: isEditingBackgroundSignOutInterval) diff --git a/Swiftfin/Views/AppSettingsView/AppSettingsView.swift b/Swiftfin/Views/AppSettingsView/AppSettingsView.swift index f6d3988d0..a23656b42 100644 --- a/Swiftfin/Views/AppSettingsView/AppSettingsView.swift +++ b/Swiftfin/Views/AppSettingsView/AppSettingsView.swift @@ -59,7 +59,7 @@ struct AppSettingsView: View { Section { - Toggle("Use splashscreen", isOn: $selectUserUseSplashscreen) + Toggle(L10n.useSplashscreen, isOn: $selectUserUseSplashscreen) if selectUserUseSplashscreen { Picker(L10n.servers, selection: $selectUserAllServersSplashscreen) { @@ -76,10 +76,10 @@ struct AppSettingsView: View { } } } header: { - Text("Splashscreen") + Text(L10n.splashscreen) } footer: { if selectUserUseSplashscreen { - Text("When All Servers is selected, use the splashscreen from a single server or a random server") + Text(L10n.splashscreenFooter) } } diff --git a/Swiftfin/Views/AppSettingsView/Components/SignOutIntervalSection.swift b/Swiftfin/Views/AppSettingsView/Components/SignOutIntervalSection.swift index 4e94f2c3a..cdc1542f6 100644 --- a/Swiftfin/Views/AppSettingsView/Components/SignOutIntervalSection.swift +++ b/Swiftfin/Views/AppSettingsView/Components/SignOutIntervalSection.swift @@ -25,17 +25,17 @@ extension AppSettingsView { var body: some View { Section { - Toggle("Sign out on close", isOn: $signOutOnClose) + Toggle(L10n.signoutClose, isOn: $signOutOnClose) } footer: { - Text("Signs out the last user when Swiftfin has been force closed") + Text(L10n.signoutCloseFooter) } Section { - Toggle("Sign out on background", isOn: $signOutOnBackground) + Toggle(L10n.signoutBackground, isOn: $signOutOnBackground) if signOutOnBackground { HStack { - Text("Duration") + Text(L10n.duration) Spacer() @@ -62,7 +62,7 @@ extension AppSettingsView { } } footer: { Text( - "Signs out the last user when Swiftfin has been in the background without media playback after some time" + L10n.signoutBackgroundFooter ) } .animation(.linear(duration: 0.15), value: isEditingBackgroundSignOutInterval) diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index a14893e87..2bea7ad19 100644 --- a/Translations/en.lproj/Localizable.strings +++ b/Translations/en.lproj/Localizable.strings @@ -727,6 +727,9 @@ /// %@ is already saved "duplicateUserSaved" = "%@ is already saved"; +/// Duration +"duration" = "Duration"; + /// DVD "dvd" = "DVD"; @@ -1717,6 +1720,18 @@ /// Sign In to %s "signInToServer" = "Sign In to %s"; +/// Sign out on background +"signoutBackground" = "Sign out on background"; + +/// Signs out the last user when Swiftfin has been in the background without media playback after some time +"signoutBackgroundFooter" = "Signs out the last user when Swiftfin has been in the background without media playback after some time"; + +/// Sign out on close +"signoutClose" = "Sign out on close"; + +/// Signs out the last user when Swiftfin has been force closed +"signoutCloseFooter" = "Signs out the last user when Swiftfin has been force closed"; + /// Slider "slider" = "Slider"; @@ -1747,6 +1762,12 @@ /// Special Features "specialFeatures" = "Special Features"; +/// Splashscreen +"splashscreen" = "Splashscreen"; + +/// When All Servers is selected, use the splashscreen from a single server or a random server +"splashscreenFooter" = "When All Servers is selected, use the splashscreen from a single server or a random server"; + /// Sports "sports" = "Sports"; @@ -2011,6 +2032,9 @@ /// Users "users" = "Users"; +/// Use splashscreen +"useSplashscreen" = "Use splashscreen"; + /// Version "version" = "Version"; From f524a84e9451f7f6ae2d9da4c88b8116398d496c Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Thu, 6 Feb 2025 20:29:52 -0700 Subject: [PATCH 14/14] wip --- Shared/ViewModels/SettingsViewModel.swift | 38 +++++++------- .../AppSettingsView/AppSettingsView.swift | 50 +++++++++---------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/Shared/ViewModels/SettingsViewModel.swift b/Shared/ViewModels/SettingsViewModel.swift index 820f07516..109267f4f 100644 --- a/Shared/ViewModels/SettingsViewModel.swift +++ b/Shared/ViewModels/SettingsViewModel.swift @@ -26,30 +26,28 @@ final class SettingsViewModel: ViewModel { override init() { - guard let iconName = UIApplication.shared.alternateIconName else { - currentAppIcon = PrimaryAppIcon.primary - super.init() - return - } - - if let appicon = PrimaryAppIcon.createCase(iconName: iconName) { - currentAppIcon = appicon - } + if let iconName = UIApplication.shared.alternateIconName { + if let appicon = PrimaryAppIcon.createCase(iconName: iconName) { + currentAppIcon = appicon + } - if let appicon = DarkAppIcon.createCase(iconName: iconName) { - currentAppIcon = appicon - } + if let appicon = DarkAppIcon.createCase(iconName: iconName) { + currentAppIcon = appicon + } - if let appicon = InvertedDarkAppIcon.createCase(iconName: iconName) { - currentAppIcon = appicon - } + if let appicon = InvertedDarkAppIcon.createCase(iconName: iconName) { + currentAppIcon = appicon + } - if let appicon = InvertedLightAppIcon.createCase(iconName: iconName) { - currentAppIcon = appicon - } + if let appicon = InvertedLightAppIcon.createCase(iconName: iconName) { + currentAppIcon = appicon + } - if let appicon = LightAppIcon.createCase(iconName: iconName) { - currentAppIcon = appicon + if let appicon = LightAppIcon.createCase(iconName: iconName) { + currentAppIcon = appicon + } + } else { + currentAppIcon = PrimaryAppIcon.primary } super.init() diff --git a/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift index 31656c6b6..582442adf 100644 --- a/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift +++ b/Swiftfin tvOS/Views/AppSettingsView/AppSettingsView.swift @@ -23,8 +23,8 @@ struct AppSettingsView: View { @EnvironmentObject private var router: AppSettingsCoordinator.Router - @ObservedObject - var viewModel = SettingsViewModel() + @StateObject + private var viewModel = SettingsViewModel() @State private var resetUserSettingsSelected: Bool = false @@ -46,40 +46,40 @@ struct AppSettingsView: View { trailing: "\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))" ) - Section(L10n.accessibility) { - - // TODO: supposedly supported but not working -// ChevronButton(L10n.appIcon) -// .onSelect { -// router.route(to: \.appIconSelector, viewModel) -// } - - // Disabled until tvOS can alter app appearance -// if !selectUserUseSplashscreen { -// CaseIterablePicker( -// L10n.appearance, -// selection: $appearance -// ) -// } - } - Section { Toggle(L10n.useSplashscreen, isOn: $selectUserUseSplashscreen) if selectUserUseSplashscreen { - Picker(L10n.servers, selection: $selectUserAllServersSplashscreen) { + Menu { + Picker(L10n.servers, selection: $selectUserAllServersSplashscreen) { - Section { Label(L10n.random, systemImage: "dice.fill") .tag(SelectUserServerSelection.all) - } - ForEach(viewModel.servers) { server in - Text(server.name) - .tag(SelectUserServerSelection.server(id: server.id)) + ForEach(viewModel.servers) { server in + Text(server.name) + .tag(SelectUserServerSelection.server(id: server.id)) + } + } + } label: { + HStack { + Text(L10n.servers) + .frame(maxWidth: .infinity, alignment: .leading) + + if selectUserAllServersSplashscreen == .all { + Label(L10n.random, systemImage: "dice.fill") + } else if let server = viewModel.servers.first( + where: { server in + selectUserAllServersSplashscreen == .server(id: server.id) + } + ) { + Text(server.name) + } } } + .listRowBackground(Color.clear) + .listRowInsets(.zero) } } header: { Text(L10n.splashscreen)