Fix tvOS MPV Options focus and Add/Edit sheet layout

Wrap pushed view in TVSidebarDetailContainer, list custom options
first so focus engages on a row, mark default options focusable so the
list scrolls. Replace toolbar-based Add/Edit sheets with padded VStacks
and inline confirm buttons. Cache customMPVOptions in @Observable
backing storage so writes refresh the list immediately.
This commit is contained in:
Arkadiusz Fal
2026-05-09 18:11:59 +02:00
parent 06ae5ac053
commit d0297a5e89
4 changed files with 112 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ extension SettingsManager {
/// NOT synced to iCloud - local-only storage.
var customMPVOptions: [String: String] {
get {
if let cached = _customMPVOptions { return cached }
guard let data = localDefaults.data(forKey: "customMPVOptions"),
let options = try? JSONDecoder().decode([String: String].self, from: data) else {
return [:]
@@ -23,6 +24,7 @@ extension SettingsManager {
return options
}
set {
_customMPVOptions = newValue
if let data = try? JSONEncoder().encode(newValue) {
localDefaults.set(data, forKey: "customMPVOptions")
}

View File

@@ -117,6 +117,9 @@ final class SettingsManager {
// Top Shelf (tvOS)
var _topShelfSections: [TopShelfSection]?
// Custom MPV options (local-only)
var _customMPVOptions: [String: String]?
// Tab bar settings (compact size class only - iOS)
var _tabBarItemOrder: [TabBarItem]?
var _tabBarItemVisibility: [TabBarItem: Bool]?

View File

@@ -212,7 +212,12 @@ struct AdvancedSettingsView: View {
#if os(tvOS)
NavigationLink {
MPVOptionsSettingsView()
TVSidebarDetailContainer(
systemImage: "slider.horizontal.3",
title: String(localized: "settings.advanced.mpv.options")
) {
MPVOptionsSettingsView()
}
} label: {
Label(String(localized: "settings.advanced.mpv.options"), systemImage: "slider.horizontal.3")
}

View File

@@ -16,12 +16,21 @@ struct MPVOptionsSettingsView: View {
var body: some View {
SettingsFormContainer {
if let settings = appEnvironment?.settingsManager {
#if os(tvOS)
CustomOptionsSection(
settings: settings,
showingAddSheet: $showingAddSheet,
editingOption: $editingOption
)
DefaultOptionsSection()
#else
DefaultOptionsSection()
CustomOptionsSection(
settings: settings,
showingAddSheet: $showingAddSheet,
editingOption: $editingOption
)
#endif
}
}
.navigationTitle(String(localized: "settings.mpvOptions.title"))
@@ -69,6 +78,7 @@ private struct DefaultOptionsSection: View {
Text(option.value)
.foregroundStyle(.secondary)
}
.focusable()
}
}
#else
@@ -263,6 +273,44 @@ private struct AddMPVOptionSheet: View {
}
.padding(20)
.frame(minWidth: 420)
#elseif os(tvOS)
VStack(spacing: 30) {
Text(String(localized: "settings.mpvOptions.addOption.title"))
.font(.title2)
.fontWeight(.semibold)
VStack(spacing: 20) {
TextField(
String(localized: "settings.mpvOptions.optionName"),
text: $optionName
)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
TextField(
String(localized: "settings.mpvOptions.optionValue"),
text: $optionValue
)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
Button {
save()
} label: {
Text(String(localized: "common.add"))
.frame(maxWidth: .infinity)
}
.disabled(!canSave)
Text(String(localized: "settings.mpvOptions.addOption.footer"))
.font(.callout)
.foregroundStyle(.secondary)
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
}
.padding(60)
.frame(maxWidth: 900)
#else
NavigationStack {
Form {
@@ -400,6 +448,59 @@ private struct EditMPVOptionSheet: View {
.onAppear {
optionValue = initialValue
}
#elseif os(tvOS)
VStack(spacing: 30) {
Text(String(localized: "settings.mpvOptions.editOption.title"))
.font(.title2)
.fontWeight(.semibold)
VStack(spacing: 20) {
HStack {
Text(String(localized: "settings.mpvOptions.optionName"))
Spacer()
Text(originalName)
.foregroundStyle(.secondary)
}
.padding(.horizontal, 24)
TextField(
String(localized: "settings.mpvOptions.optionValue"),
text: $optionValue
)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
}
Button {
save()
} label: {
Text(String(localized: "common.save"))
.frame(maxWidth: .infinity)
}
.disabled(!canSave)
Button(role: .destructive) {
showingDeleteConfirmation = true
} label: {
Label(String(localized: "settings.mpvOptions.deleteOption"), systemImage: "trash")
.frame(maxWidth: .infinity)
}
}
.padding(60)
.frame(maxWidth: 900)
.confirmationDialog(
String(localized: "settings.mpvOptions.deleteOption.confirmation"),
isPresented: $showingDeleteConfirmation,
titleVisibility: .visible
) {
Button(String(localized: "common.delete"), role: .destructive) {
delete()
}
Button(String(localized: "common.cancel"), role: .cancel) {}
}
.onAppear {
optionValue = initialValue
}
#else
NavigationStack {
Form {