mirror of
https://github.com/yattee/yattee.git
synced 2026-05-13 10:55:03 +00:00
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:
@@ -16,6 +16,7 @@ extension SettingsManager {
|
|||||||
/// NOT synced to iCloud - local-only storage.
|
/// NOT synced to iCloud - local-only storage.
|
||||||
var customMPVOptions: [String: String] {
|
var customMPVOptions: [String: String] {
|
||||||
get {
|
get {
|
||||||
|
if let cached = _customMPVOptions { return cached }
|
||||||
guard let data = localDefaults.data(forKey: "customMPVOptions"),
|
guard let data = localDefaults.data(forKey: "customMPVOptions"),
|
||||||
let options = try? JSONDecoder().decode([String: String].self, from: data) else {
|
let options = try? JSONDecoder().decode([String: String].self, from: data) else {
|
||||||
return [:]
|
return [:]
|
||||||
@@ -23,6 +24,7 @@ extension SettingsManager {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
|
_customMPVOptions = newValue
|
||||||
if let data = try? JSONEncoder().encode(newValue) {
|
if let data = try? JSONEncoder().encode(newValue) {
|
||||||
localDefaults.set(data, forKey: "customMPVOptions")
|
localDefaults.set(data, forKey: "customMPVOptions")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ final class SettingsManager {
|
|||||||
// Top Shelf (tvOS)
|
// Top Shelf (tvOS)
|
||||||
var _topShelfSections: [TopShelfSection]?
|
var _topShelfSections: [TopShelfSection]?
|
||||||
|
|
||||||
|
// Custom MPV options (local-only)
|
||||||
|
var _customMPVOptions: [String: String]?
|
||||||
|
|
||||||
// Tab bar settings (compact size class only - iOS)
|
// Tab bar settings (compact size class only - iOS)
|
||||||
var _tabBarItemOrder: [TabBarItem]?
|
var _tabBarItemOrder: [TabBarItem]?
|
||||||
var _tabBarItemVisibility: [TabBarItem: Bool]?
|
var _tabBarItemVisibility: [TabBarItem: Bool]?
|
||||||
|
|||||||
@@ -212,7 +212,12 @@ struct AdvancedSettingsView: View {
|
|||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
|
TVSidebarDetailContainer(
|
||||||
|
systemImage: "slider.horizontal.3",
|
||||||
|
title: String(localized: "settings.advanced.mpv.options")
|
||||||
|
) {
|
||||||
MPVOptionsSettingsView()
|
MPVOptionsSettingsView()
|
||||||
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label(String(localized: "settings.advanced.mpv.options"), systemImage: "slider.horizontal.3")
|
Label(String(localized: "settings.advanced.mpv.options"), systemImage: "slider.horizontal.3")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,21 @@ struct MPVOptionsSettingsView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
SettingsFormContainer {
|
SettingsFormContainer {
|
||||||
if let settings = appEnvironment?.settingsManager {
|
if let settings = appEnvironment?.settingsManager {
|
||||||
|
#if os(tvOS)
|
||||||
|
CustomOptionsSection(
|
||||||
|
settings: settings,
|
||||||
|
showingAddSheet: $showingAddSheet,
|
||||||
|
editingOption: $editingOption
|
||||||
|
)
|
||||||
|
DefaultOptionsSection()
|
||||||
|
#else
|
||||||
DefaultOptionsSection()
|
DefaultOptionsSection()
|
||||||
CustomOptionsSection(
|
CustomOptionsSection(
|
||||||
settings: settings,
|
settings: settings,
|
||||||
showingAddSheet: $showingAddSheet,
|
showingAddSheet: $showingAddSheet,
|
||||||
editingOption: $editingOption
|
editingOption: $editingOption
|
||||||
)
|
)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(String(localized: "settings.mpvOptions.title"))
|
.navigationTitle(String(localized: "settings.mpvOptions.title"))
|
||||||
@@ -69,6 +78,7 @@ private struct DefaultOptionsSection: View {
|
|||||||
Text(option.value)
|
Text(option.value)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
|
.focusable()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@@ -263,6 +273,44 @@ private struct AddMPVOptionSheet: View {
|
|||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(minWidth: 420)
|
.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
|
#else
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
@@ -400,6 +448,59 @@ private struct EditMPVOptionSheet: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
optionValue = initialValue
|
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
|
#else
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
|
|||||||
Reference in New Issue
Block a user