Some More Cleanup, Reset User Settings (#1060)

This commit is contained in:
Ethan Pippin 2024-05-16 22:10:40 -06:00 committed by GitHub
parent 66c26553ad
commit 8d6167c00b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 116 additions and 93 deletions

View File

@ -65,7 +65,7 @@ struct CaseIterablePicker<Element: CaseIterable & Displayable & Hashable>: View
extension CaseIterablePicker { extension CaseIterablePicker {
init(title: String, selection: Binding<Element?>) { init(_ title: String, selection: Binding<Element?>) {
self.init( self.init(
selection: selection, selection: selection,
label: { Text($0.displayTitle) }, label: { Text($0.displayTitle) },
@ -75,7 +75,7 @@ extension CaseIterablePicker {
) )
} }
init(title: String, selection: Binding<Element>) { init(_ title: String, selection: Binding<Element>) {
let binding = Binding<Element?> { let binding = Binding<Element?> {
selection.wrappedValue selection.wrappedValue
} set: { newValue, _ in } set: { newValue, _ in

View File

@ -140,9 +140,9 @@ extension Defaults.Keys {
enum Library { enum Library {
static let cinematicBackground: Key<Bool> = UserKey("Customization.Library.cinematicBackground", default: true) static let cinematicBackground: Key<Bool> = UserKey("libraryCinematicBackground", default: true)
static let enabledDrawerFilters: Key<[ItemFilterType]> = UserKey( static let enabledDrawerFilters: Key<[ItemFilterType]> = UserKey(
"Library.enabledDrawerFilters", "libraryEnabledDrawerFilters",
default: ItemFilterType.allCases default: ItemFilterType.allCases
) )
static let displayType: Key<LibraryDisplayType> = UserKey("libraryViewType", default: .grid) static let displayType: Key<LibraryDisplayType> = UserKey("libraryViewType", default: .grid)
@ -158,7 +158,7 @@ extension Defaults.Keys {
enum Search { enum Search {
static let enabledDrawerFilters: Key<[ItemFilterType]> = UserKey( static let enabledDrawerFilters: Key<[ItemFilterType]> = UserKey(
"Search.enabledDrawerFilters", "searchEnabledDrawerFilters",
default: ItemFilterType.allCases default: ItemFilterType.allCases
) )
} }

View File

@ -11,8 +11,6 @@ import CoreStore
import Foundation import Foundation
import SwiftUI import SwiftUI
// TODO: observation
/// A property wrapper for a stored `AnyData` object. /// A property wrapper for a stored `AnyData` object.
@propertyWrapper @propertyWrapper
struct StoredValue<Value: Codable>: DynamicProperty { struct StoredValue<Value: Codable>: DynamicProperty {
@ -116,6 +114,9 @@ extension StoredValue {
// Stored value doesn't exist but we want to observe it. // Stored value doesn't exist but we want to observe it.
// Create default and get new publisher // Create default and get new publisher
// TODO: this still store unnecessary data if never changed,
// observe if changes were made and delete on deinit
do { do {
try AnyStoredData.store( try AnyStoredData.store(
value: key.defaultValue(), value: key.defaultValue(),

View File

@ -12,7 +12,7 @@ import JellyfinAPI
// Note: Temporary values to avoid refactoring or // Note: Temporary values to avoid refactoring or
// reduce complexity at local sites. // reduce complexity at local sites.
// //
// Values can be cleaned up at any time so and are // Values can be cleaned up at any time and are
// meant to have a short lifetime. // meant to have a short lifetime.
extension StoredValues.Keys { extension StoredValues.Keys {

View File

@ -92,7 +92,7 @@ extension StoredValues.Keys {
static func libraryDisplayType(parentID: String?) -> Key<LibraryDisplayType> { static func libraryDisplayType(parentID: String?) -> Key<LibraryDisplayType> {
CurrentUserKey( CurrentUserKey(
parentID, parentID,
domain: "libraryDisplayType", domain: "setting-libraryDisplayType",
default: Defaults[.Customization.Library.displayType] default: Defaults[.Customization.Library.displayType]
) )
} }
@ -100,7 +100,7 @@ extension StoredValues.Keys {
static func libraryListColumnCount(parentID: String?) -> Key<Int> { static func libraryListColumnCount(parentID: String?) -> Key<Int> {
CurrentUserKey( CurrentUserKey(
parentID, parentID,
domain: "libraryListColumnCount", domain: "setting-libraryListColumnCount",
default: Defaults[.Customization.Library.listColumnCount] default: Defaults[.Customization.Library.listColumnCount]
) )
} }
@ -108,7 +108,7 @@ extension StoredValues.Keys {
static func libraryPosterType(parentID: String?) -> Key<PosterDisplayType> { static func libraryPosterType(parentID: String?) -> Key<PosterDisplayType> {
CurrentUserKey( CurrentUserKey(
parentID, parentID,
domain: "libraryPosterType", domain: "setting-libraryPosterType",
default: Defaults[.Customization.Library.posterType] default: Defaults[.Customization.Library.posterType]
) )
} }
@ -119,7 +119,7 @@ extension StoredValues.Keys {
static func libraryFilters(parentID: String?) -> Key<ItemFilterCollection> { static func libraryFilters(parentID: String?) -> Key<ItemFilterCollection> {
CurrentUserKey( CurrentUserKey(
parentID, parentID,
domain: "libraryFilters", domain: "setting-libraryFilters",
default: ItemFilterCollection.default default: ItemFilterCollection.default
) )
} }

View File

@ -115,7 +115,7 @@ extension UserState {
try SwiftfinStore.dataStack.perform { transaction in try SwiftfinStore.dataStack.perform { transaction in
let userData = try transaction.fetchAll( let userData = try transaction.fetchAll(
From<AnyStoredData>() From<AnyStoredData>()
.where(\.$ownerID == id) .where(combineByAnd: Where(\.$ownerID == id), Where("%K BEGINSWITH %@", "domain", "setting"))
) )
transaction.delete(userData) transaction.delete(userData)

View File

@ -44,7 +44,7 @@ struct ChevronButton: View {
extension ChevronButton { extension ChevronButton {
init(title: String, subtitle: String? = nil) { init(_ title: String, subtitle: String? = nil) {
self.init( self.init(
title: title, title: title,
subtitle: subtitle, subtitle: subtitle,

View File

@ -51,7 +51,7 @@ struct AppSettingsView: View {
// ) // )
// } // }
// //
// ChevronButton(title: "Logs") // ChevronButton("Logs")
// .onSelect { // .onSelect {
// router.route(to: \.log) // router.route(to: \.log)
// } // }

View File

@ -62,7 +62,7 @@ struct CustomizeViewsSettings: View {
Section { Section {
ChevronButton(title: "Indicators") ChevronButton("Indicators")
.onSelect { .onSelect {
router.route(to: \.indicatorSettings) router.route(to: \.indicatorSettings)
} }

View File

@ -41,7 +41,7 @@ struct SettingsView: View {
} }
ChevronButton( ChevronButton(
title: L10n.server, L10n.server,
subtitle: viewModel.userSession.server.name subtitle: viewModel.userSession.server.name
) )
.onSelect { .onSelect {
@ -60,7 +60,7 @@ struct SettingsView: View {
InlineEnumToggle(title: "Video Player Type", selection: $videoPlayerType) InlineEnumToggle(title: "Video Player Type", selection: $videoPlayerType)
ChevronButton(title: L10n.videoPlayer) ChevronButton(L10n.videoPlayer)
.onSelect { .onSelect {
router.route(to: \.videoPlayerSettings) router.route(to: \.videoPlayerSettings)
} }
@ -70,12 +70,12 @@ struct SettingsView: View {
Section { Section {
ChevronButton(title: L10n.customize) ChevronButton(L10n.customize)
.onSelect { .onSelect {
router.route(to: \.customizeViewsSettings) router.route(to: \.customizeViewsSettings)
} }
ChevronButton(title: L10n.experimental) ChevronButton(L10n.experimental)
.onSelect { .onSelect {
router.route(to: \.experimentalSettings) router.route(to: \.experimentalSettings)
} }
@ -86,7 +86,7 @@ struct SettingsView: View {
Section { Section {
ChevronButton(title: "Logs") ChevronButton("Logs")
.onSelect { .onSelect {
router.route(to: \.log) router.route(to: \.log)
} }

View File

@ -44,7 +44,7 @@ struct VideoPlayerSettingsView: View {
Section { Section {
ChevronButton( ChevronButton(
title: "Resume Offset", "Resume Offset",
subtitle: resumeOffset.secondLabel subtitle: resumeOffset.secondLabel
) )
.onSelect { .onSelect {
@ -55,7 +55,7 @@ struct VideoPlayerSettingsView: View {
} }
Section { Section {
ChevronButton(title: L10n.subtitleFont, subtitle: subtitleFontName) ChevronButton(L10n.subtitleFont, subtitle: subtitleFontName)
.onSelect { .onSelect {
router.route(to: \.fontPicker, $subtitleFontName) router.route(to: \.fontPicker, $subtitleFontName)
} }

View File

@ -3773,13 +3773,11 @@
E18E021E2887492B0022598C /* RowDivider.swift in Sources */, E18E021E2887492B0022598C /* RowDivider.swift in Sources */,
E1DC983E296DEB9B00982F06 /* UnwatchedIndicator.swift in Sources */, E1DC983E296DEB9B00982F06 /* UnwatchedIndicator.swift in Sources */,
E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */, E107BB9427880A8F00354E07 /* CollectionItemViewModel.swift in Sources */,
E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */,
E1D37F592B9CEF4B00343D2B /* DeviceProfile+SwiftfinProfile.swift in Sources */, E1D37F592B9CEF4B00343D2B /* DeviceProfile+SwiftfinProfile.swift in Sources */,
53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */, 53ABFDE9267974EF00886593 /* HomeViewModel.swift in Sources */,
E1575E99293E7B1E001665B1 /* UIColor.swift in Sources */, E1575E99293E7B1E001665B1 /* UIColor.swift in Sources */,
E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */, E1575E92293E7B1E001665B1 /* CGSize.swift in Sources */,
E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */, E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */,
E1575E7E293E77B5001665B1 /* ItemFilterCollection.swift in Sources */,
C46DD8EF2A8FB56E0046A504 /* LiveBottomBarView.swift in Sources */, C46DD8EF2A8FB56E0046A504 /* LiveBottomBarView.swift in Sources */,
C46DD8EA2A8FB45C0046A504 /* LiveOverlay.swift in Sources */, C46DD8EA2A8FB45C0046A504 /* LiveOverlay.swift in Sources */,
E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */, E11E376D293E9CC1009EF240 /* VideoPlayerCoordinator.swift in Sources */,
@ -4307,7 +4305,6 @@
E1D3044428D1991900587289 /* LibraryViewTypeToggle.swift in Sources */, E1D3044428D1991900587289 /* LibraryViewTypeToggle.swift in Sources */,
C45C36542A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift in Sources */, C45C36542A8B1F2C003DAE46 /* LiveVideoPlayerManager.swift in Sources */,
E148128B28C15526003B8787 /* ItemSortBy.swift in Sources */, E148128B28C15526003B8787 /* ItemSortBy.swift in Sources */,
E148128B28C15526003B8787 /* ItemSortBy.swift in Sources */,
E10231412BCF8A3C009D71FC /* ChannelLibraryView.swift in Sources */, E10231412BCF8A3C009D71FC /* ChannelLibraryView.swift in Sources */,
E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */, E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */,
E1A3E4CB2BB74EFD005C59F8 /* EpisodeHStack.swift in Sources */, E1A3E4CB2BB74EFD005C59F8 /* EpisodeHStack.swift in Sources */,

View File

@ -1,5 +1,5 @@
{ {
"originHash" : "558c2e760c073dbad0a2bfbade5ccfa1b2962fdd8ab5f658d9bbfc4310623441", "originHash" : "68a42015b2d42a14d418b6e13427443de55c970d5b3764bbc969e1b3f8c3a78b",
"pins" : [ "pins" : [
{ {
"identity" : "blurhashkit", "identity" : "blurhashkit",
@ -34,7 +34,7 @@
"location" : "https://github.com/LePips/CollectionVGrid", "location" : "https://github.com/LePips/CollectionVGrid",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "b50b5241df5fc1d71e5a09f6a87731c67c2a79e5" "revision" : "7204e5f717ea571efb4600ecb71c2412e0dec921"
} }
}, },
{ {
@ -114,8 +114,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke", "location" : "https://github.com/kean/Nuke",
"state" : { "state" : {
"revision" : "4625c73ea00a9fb4b4f3e28d95d0021a44af7e59", "revision" : "8e431251dea0081b6ab154dab61a6ec74e4b6577",
"version" : "12.5.0" "version" : "12.6.0"
} }
}, },
{ {
@ -123,8 +123,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Pulse", "location" : "https://github.com/kean/Pulse",
"state" : { "state" : {
"revision" : "d647e99f06abc94d63579e335ad4ce368195c149", "revision" : "4f34c4f91cda623a7627e6d5e35dbbbb514b8daa",
"version" : "4.0.5" "version" : "4.1.1"
} }
}, },
{ {
@ -195,8 +195,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect", "location" : "https://github.com/siteline/SwiftUI-Introspect",
"state" : { "state" : {
"revision" : "0cd2a5a5895306bc21d54a2254302d24a9a571e4", "revision" : "7dc5b287f8040e4ad5038739850b758e78f77808",
"version" : "1.1.3" "version" : "1.1.4"
} }
}, },
{ {

View File

@ -44,7 +44,7 @@ struct ChevronButton: View {
extension ChevronButton { extension ChevronButton {
init(title: String, subtitle: String? = nil) { init(_ title: String, subtitle: String? = nil) {
self.init( self.init(
title: title, title: title,
subtitle: subtitle, subtitle: subtitle,

View File

@ -39,7 +39,7 @@ struct AboutAppView: View {
trailing: "\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))" trailing: "\(UIApplication.appVersion ?? .emptyDash) (\(UIApplication.bundleVersion ?? .emptyDash))"
) )
ChevronButton(title: L10n.sourceCode) ChevronButton(L10n.sourceCode)
.leadingView { .leadingView {
Image(.logoGithub) Image(.logoGithub)
.resizable() .resizable()
@ -51,7 +51,7 @@ struct AboutAppView: View {
UIApplication.shared.open(.swiftfinGithub) UIApplication.shared.open(.swiftfinGithub)
} }
ChevronButton(title: L10n.bugsAndFeatures) ChevronButton(L10n.bugsAndFeatures)
.leadingView { .leadingView {
Image(systemName: "plus.circle.fill") Image(systemName: "plus.circle.fill")
.resizable() .resizable()
@ -65,7 +65,7 @@ struct AboutAppView: View {
UIApplication.shared.open(.swiftfinGithubIssues) UIApplication.shared.open(.swiftfinGithubIssues)
} }
ChevronButton(title: L10n.settings) ChevronButton(L10n.settings)
.leadingView { .leadingView {
Image(systemName: "gearshape.fill") Image(systemName: "gearshape.fill")
.resizable() .resizable()

View File

@ -37,21 +37,21 @@ struct AppSettingsView: View {
var body: some View { var body: some View {
Form { Form {
ChevronButton(title: L10n.about) ChevronButton(L10n.about)
.onSelect { .onSelect {
router.route(to: \.about, viewModel) router.route(to: \.about, viewModel)
} }
Section(L10n.accessibility) { Section(L10n.accessibility) {
ChevronButton(title: L10n.appIcon) ChevronButton(L10n.appIcon)
.onSelect { .onSelect {
router.route(to: \.appIconSelector, viewModel) router.route(to: \.appIconSelector, viewModel)
} }
if !selectUserUseSplashscreen { if !selectUserUseSplashscreen {
CaseIterablePicker( CaseIterablePicker(
title: L10n.appearance, L10n.appearance,
selection: $appearance selection: $appearance
) )
} }
@ -85,7 +85,7 @@ struct AppSettingsView: View {
SignOutIntervalSection() SignOutIntervalSection()
ChevronButton(title: L10n.logs) ChevronButton(L10n.logs)
.onSelect { .onSelect {
router.route(to: \.log) router.route(to: \.log)
} }

View File

@ -24,7 +24,7 @@ struct MediaSourceInfoView: View {
{ {
Section(L10n.video) { Section(L10n.video) {
ForEach(videoStreams, id: \.self) { stream in ForEach(videoStreams, id: \.self) { stream in
ChevronButton(title: stream.displayTitle ?? .emptyDash) ChevronButton(stream.displayTitle ?? .emptyDash)
.onSelect { .onSelect {
router.route(to: \.mediaStreamInfo, stream) router.route(to: \.mediaStreamInfo, stream)
} }
@ -37,7 +37,7 @@ struct MediaSourceInfoView: View {
{ {
Section(L10n.audio) { Section(L10n.audio) {
ForEach(audioStreams, id: \.self) { stream in ForEach(audioStreams, id: \.self) { stream in
ChevronButton(title: stream.displayTitle ?? .emptyDash) ChevronButton(stream.displayTitle ?? .emptyDash)
.onSelect { .onSelect {
router.route(to: \.mediaStreamInfo, stream) router.route(to: \.mediaStreamInfo, stream)
} }
@ -50,7 +50,7 @@ struct MediaSourceInfoView: View {
{ {
Section(L10n.subtitle) { Section(L10n.subtitle) {
ForEach(subtitleStreams, id: \.self) { stream in ForEach(subtitleStreams, id: \.self) { stream in
ChevronButton(title: stream.displayTitle ?? .emptyDash) ChevronButton(stream.displayTitle ?? .emptyDash)
.onSelect { .onSelect {
router.route(to: \.mediaStreamInfo, stream) router.route(to: \.mediaStreamInfo, stream)
} }

View File

@ -68,7 +68,7 @@ struct CustomizeViewsSettings: View {
if UIDevice.isPhone { if UIDevice.isPhone {
Section { Section {
CaseIterablePicker(title: L10n.items, selection: $itemViewType) CaseIterablePicker(L10n.items, selection: $itemViewType)
} }
if itemViewType == .cinematic { if itemViewType == .cinematic {
@ -91,12 +91,12 @@ struct CustomizeViewsSettings: View {
Section { Section {
ChevronButton(title: L10n.library) ChevronButton(L10n.library)
.onSelect { .onSelect {
router.route(to: \.itemFilterDrawerSelector, $libraryEnabledDrawerFilters) router.route(to: \.itemFilterDrawerSelector, $libraryEnabledDrawerFilters)
} }
ChevronButton(title: L10n.search) ChevronButton(L10n.search)
.onSelect { .onSelect {
router.route(to: \.itemFilterDrawerSelector, $searchEnabledDrawerFilters) router.route(to: \.itemFilterDrawerSelector, $searchEnabledDrawerFilters)
} }
@ -114,28 +114,28 @@ struct CustomizeViewsSettings: View {
Section(L10n.posters) { Section(L10n.posters) {
ChevronButton(title: L10n.indicators) ChevronButton(L10n.indicators)
.onSelect { .onSelect {
router.route(to: \.indicatorSettings) router.route(to: \.indicatorSettings)
} }
Toggle(L10n.showPosterLabels, isOn: $showPosterLabels) Toggle(L10n.showPosterLabels, isOn: $showPosterLabels)
CaseIterablePicker(title: L10n.next, selection: $nextUpPosterType) CaseIterablePicker(L10n.next, selection: $nextUpPosterType)
CaseIterablePicker(title: L10n.recentlyAdded, selection: $recentlyAddedPosterType) CaseIterablePicker(L10n.recentlyAdded, selection: $recentlyAddedPosterType)
CaseIterablePicker(title: L10n.latestWithString(L10n.library), selection: $latestInLibraryPosterType) CaseIterablePicker(L10n.latestWithString(L10n.library), selection: $latestInLibraryPosterType)
CaseIterablePicker(title: L10n.recommended, selection: $similarPosterType) CaseIterablePicker(L10n.recommended, selection: $similarPosterType)
CaseIterablePicker(title: L10n.search, selection: $searchPosterType) CaseIterablePicker(L10n.search, selection: $searchPosterType)
} }
Section("Libraries") { Section("Libraries") {
CaseIterablePicker(title: L10n.library, selection: $libraryDisplayType) CaseIterablePicker(L10n.library, selection: $libraryDisplayType)
CaseIterablePicker(title: L10n.posters, selection: $libraryPosterType) CaseIterablePicker(L10n.posters, selection: $libraryPosterType)
if libraryDisplayType == .list, UIDevice.isPad { if libraryDisplayType == .list, UIDevice.isPad {
BasicStepper( BasicStepper(

View File

@ -38,23 +38,23 @@ struct GestureSettingsView: View {
Section { Section {
CaseIterablePicker(title: "Horizontal Pan", selection: $horizontalPanGesture) CaseIterablePicker("Horizontal Pan", selection: $horizontalPanGesture)
.disabled(horizontalSwipeGesture != .none && horizontalPanGesture == .none) .disabled(horizontalSwipeGesture != .none && horizontalPanGesture == .none)
CaseIterablePicker(title: "Horizontal Swipe", selection: $horizontalSwipeGesture) CaseIterablePicker("Horizontal Swipe", selection: $horizontalSwipeGesture)
.disabled(horizontalPanGesture != .none && horizontalSwipeGesture == .none) .disabled(horizontalPanGesture != .none && horizontalSwipeGesture == .none)
CaseIterablePicker(title: "Long Press", selection: $longPressGesture) CaseIterablePicker("Long Press", selection: $longPressGesture)
CaseIterablePicker(title: "Multi Tap", selection: $multiTapGesture) CaseIterablePicker("Multi Tap", selection: $multiTapGesture)
CaseIterablePicker(title: "Double Touch", selection: $doubleTouchGesture) CaseIterablePicker("Double Touch", selection: $doubleTouchGesture)
CaseIterablePicker(title: "Pinch", selection: $pinchGesture) CaseIterablePicker("Pinch", selection: $pinchGesture)
CaseIterablePicker(title: "Left Vertical Pan", selection: $verticalPanGestureLeft) CaseIterablePicker("Left Vertical Pan", selection: $verticalPanGestureLeft)
CaseIterablePicker(title: "Right Vertical Pan", selection: $verticalPanGestureRight) CaseIterablePicker("Right Vertical Pan", selection: $verticalPanGestureRight)
} }
} }
.navigationTitle("Gestures") .navigationTitle("Gestures")

View File

@ -39,7 +39,7 @@ struct SettingsView: View {
// TODO: admin users go to dashboard instead // TODO: admin users go to dashboard instead
ChevronButton( ChevronButton(
title: L10n.server, L10n.server,
subtitle: viewModel.userSession.server.name subtitle: viewModel.userSession.server.name
) )
.onSelect { .onSelect {
@ -58,30 +58,30 @@ struct SettingsView: View {
Section(L10n.videoPlayer) { Section(L10n.videoPlayer) {
CaseIterablePicker( CaseIterablePicker(
title: L10n.videoPlayerType, L10n.videoPlayerType,
selection: $videoPlayerType selection: $videoPlayerType
) )
ChevronButton(title: L10n.nativePlayer) ChevronButton(L10n.nativePlayer)
.onSelect { .onSelect {
router.route(to: \.nativePlayerSettings) router.route(to: \.nativePlayerSettings)
} }
ChevronButton(title: L10n.videoPlayer) ChevronButton(L10n.videoPlayer)
.onSelect { .onSelect {
router.route(to: \.videoPlayerSettings) router.route(to: \.videoPlayerSettings)
} }
} }
Section(L10n.accessibility) { Section(L10n.accessibility) {
CaseIterablePicker(title: L10n.appearance, selection: $appearance) CaseIterablePicker(L10n.appearance, selection: $appearance)
ChevronButton(title: L10n.customize) ChevronButton(L10n.customize)
.onSelect { .onSelect {
router.route(to: \.customizeViewsSettings) router.route(to: \.customizeViewsSettings)
} }
ChevronButton(title: L10n.experimental) ChevronButton(L10n.experimental)
.onSelect { .onSelect {
router.route(to: \.experimentalSettings) router.route(to: \.experimentalSettings)
} }
@ -93,14 +93,14 @@ struct SettingsView: View {
Text(L10n.accentColorDescription) Text(L10n.accentColorDescription)
} }
ChevronButton(title: L10n.logs) ChevronButton(L10n.logs)
.onSelect { .onSelect {
router.route(to: \.log) router.route(to: \.log)
} }
#if DEBUG #if DEBUG
ChevronButton(title: "Debug") ChevronButton("Debug")
.onSelect { .onSelect {
router.route(to: \.debugSettings) router.route(to: \.debugSettings)
} }

View File

@ -91,7 +91,7 @@ struct ResetUserPasswordView: View {
} }
.foregroundStyle(.red, .red.opacity(0.2)) .foregroundStyle(.red, .red.opacity(0.2))
} else { } else {
ListRowButton("Reset") { ListRowButton("Save") {
focusedPassword = nil focusedPassword = nil
viewModel.send(.reset(current: currentPassword, new: confirmNewPassword)) viewModel.send(.reset(current: currentPassword, new: confirmNewPassword))
} }

View File

@ -106,7 +106,7 @@ struct UserLocalSecurityView: View {
List { List {
Section { Section {
CaseIterablePicker(title: "Security", selection: $signInPolicy) CaseIterablePicker("Security", selection: $signInPolicy)
} footer: { } footer: {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
Text( Text(

View File

@ -17,6 +17,9 @@ struct UserProfileSettingsView: View {
@ObservedObject @ObservedObject
var viewModel: SettingsViewModel var viewModel: SettingsViewModel
@State
private var isPresentingConfirmReset: Bool = false
@ViewBuilder @ViewBuilder
private var imageView: some View { private var imageView: some View {
ImageView( ImageView(
@ -63,23 +66,45 @@ struct UserProfileSettingsView: View {
} }
Section { Section {
ChevronButton(title: L10n.quickConnect) ChevronButton(L10n.quickConnect)
.onSelect { .onSelect {
router.route(to: \.quickConnect) router.route(to: \.quickConnect)
} }
ChevronButton(title: "Password") ChevronButton("Password")
.onSelect { .onSelect {
router.route(to: \.resetUserPassword) router.route(to: \.resetUserPassword)
} }
} }
Section { Section {
ChevronButton(title: "Local Security") ChevronButton("Security")
.onSelect { .onSelect {
router.route(to: \.localSecurity) router.route(to: \.localSecurity)
} }
} }
Section {
// TODO: move under future "Storage" tab
// when downloads implemented
Button("Reset Settings") {
isPresentingConfirmReset = true
}
.foregroundStyle(.red)
} footer: {
Text("Reset Swiftfin user settings")
}
}
.alert("Reset Settings", isPresented: $isPresentingConfirmReset) {
Button("Reset", role: .destructive) {
do {
try viewModel.userSession.user.deleteSettings()
} catch {
viewModel.logger.error("Unable to reset user settings: \(error.localizedDescription)")
}
}
} message: {
Text("Are you sure you want to reset all user settings?")
} }
} }
} }

View File

@ -65,14 +65,14 @@ struct VideoPlayerSettingsView: View {
var body: some View { var body: some View {
Form { Form {
ChevronButton(title: L10n.gestures) ChevronButton(L10n.gestures)
.onSelect { .onSelect {
router.route(to: \.gestureSettings) router.route(to: \.gestureSettings)
} }
CaseIterablePicker(title: L10n.jumpBackwardLength, selection: $jumpBackwardLength) CaseIterablePicker(L10n.jumpBackwardLength, selection: $jumpBackwardLength)
CaseIterablePicker(title: L10n.jumpForwardLength, selection: $jumpForwardLength) CaseIterablePicker(L10n.jumpForwardLength, selection: $jumpForwardLength)
Section { Section {
@ -91,7 +91,7 @@ struct VideoPlayerSettingsView: View {
Section(L10n.buttons) { Section(L10n.buttons) {
CaseIterablePicker(title: L10n.playbackButtons, selection: $playbackButtonType) CaseIterablePicker(L10n.playbackButtons, selection: $playbackButtonType)
Toggle(isOn: $showJumpButtons) { Toggle(isOn: $showJumpButtons) {
HStack { HStack {
@ -100,12 +100,12 @@ struct VideoPlayerSettingsView: View {
} }
} }
ChevronButton(title: L10n.barButtons) ChevronButton(L10n.barButtons)
.onSelect { .onSelect {
router.route(to: \.actionButtonSelector, $barActionButtons) router.route(to: \.actionButtonSelector, $barActionButtons)
} }
ChevronButton(title: L10n.menuButtons) ChevronButton(L10n.menuButtons)
.onSelect { .onSelect {
router.route(to: \.actionButtonSelector, $menuActionButtons) router.route(to: \.actionButtonSelector, $menuActionButtons)
} }
@ -119,12 +119,12 @@ struct VideoPlayerSettingsView: View {
Text(L10n.sliderColor) Text(L10n.sliderColor)
} }
CaseIterablePicker(title: L10n.sliderType, selection: $sliderType) CaseIterablePicker(L10n.sliderType, selection: $sliderType)
} }
Section { Section {
ChevronButton(title: L10n.subtitleFont, subtitle: subtitleFontName) ChevronButton(L10n.subtitleFont, subtitle: subtitleFontName)
.onSelect { .onSelect {
router.route(to: \.fontPicker, $subtitleFontName) router.route(to: \.fontPicker, $subtitleFontName)
} }
@ -150,9 +150,9 @@ struct VideoPlayerSettingsView: View {
Toggle(L10n.scrubCurrentTime, isOn: $showCurrentTimeWhileScrubbing) Toggle(L10n.scrubCurrentTime, isOn: $showCurrentTimeWhileScrubbing)
CaseIterablePicker(title: L10n.timestampType, selection: $timestampType) CaseIterablePicker(L10n.timestampType, selection: $timestampType)
CaseIterablePicker(title: L10n.trailingValue, selection: $trailingTimestampType) CaseIterablePicker(L10n.trailingValue, selection: $trailingTimestampType)
} }
Section(L10n.transition) { Section(L10n.transition) {

View File

@ -43,7 +43,7 @@ extension UserSignInView {
List { List {
Section { Section {
CaseIterablePicker(title: "Security", selection: $updateSignInPolicy) CaseIterablePicker("Security", selection: $updateSignInPolicy)
} footer: { } footer: {
// TODO: descriptions of each section // TODO: descriptions of each section

View File

@ -34,7 +34,7 @@ struct PlaybackSettingsView: View {
Form { Form {
Section { Section {
ChevronButton(title: L10n.videoPlayer) ChevronButton(L10n.videoPlayer)
.onSelect { .onSelect {
router.route(to: \.videoPlayerSettings) router.route(to: \.videoPlayerSettings)
} }
@ -67,7 +67,7 @@ struct PlaybackSettingsView: View {
if viewModel.videoStreams.isNotEmpty { if viewModel.videoStreams.isNotEmpty {
Section(L10n.video) { Section(L10n.video) {
ForEach(viewModel.videoStreams, id: \.displayTitle) { mediaStream in ForEach(viewModel.videoStreams, id: \.displayTitle) { mediaStream in
ChevronButton(title: mediaStream.displayTitle ?? .emptyDash) ChevronButton(mediaStream.displayTitle ?? .emptyDash)
.onSelect { .onSelect {
router.route(to: \.mediaStreamInfo, mediaStream) router.route(to: \.mediaStreamInfo, mediaStream)
} }
@ -78,7 +78,7 @@ struct PlaybackSettingsView: View {
if viewModel.audioStreams.isNotEmpty { if viewModel.audioStreams.isNotEmpty {
Section(L10n.audio) { Section(L10n.audio) {
ForEach(viewModel.audioStreams, id: \.displayTitle) { mediaStream in ForEach(viewModel.audioStreams, id: \.displayTitle) { mediaStream in
ChevronButton(title: mediaStream.displayTitle ?? .emptyDash) ChevronButton(mediaStream.displayTitle ?? .emptyDash)
.onSelect { .onSelect {
router.route(to: \.mediaStreamInfo, mediaStream) router.route(to: \.mediaStreamInfo, mediaStream)
} }
@ -89,7 +89,7 @@ struct PlaybackSettingsView: View {
if viewModel.subtitleStreams.isNotEmpty { if viewModel.subtitleStreams.isNotEmpty {
Section(L10n.subtitle) { Section(L10n.subtitle) {
ForEach(viewModel.subtitleStreams, id: \.displayTitle) { mediaStream in ForEach(viewModel.subtitleStreams, id: \.displayTitle) { mediaStream in
ChevronButton(title: mediaStream.displayTitle ?? .emptyDash) ChevronButton(mediaStream.displayTitle ?? .emptyDash)
.onSelect { .onSelect {
router.route(to: \.mediaStreamInfo, mediaStream) router.route(to: \.mediaStreamInfo, mediaStream)
} }