Improve tvOS settings UI styling and navigation

- Add TVOSPlainToggleStyle for cleaner toggle appearance on tvOS
- Remove focus overlays from settings navigation links and buttons
- Apply plain button and list styles across all settings screens
- Implement custom system controls picker for tvOS to avoid focus overlay
- Update SettingsPickerModifier with platform-specific styling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Arkadiusz Fal
2025-11-09 15:36:41 +01:00
parent 495dcec874
commit e0ca48fd44
13 changed files with 166 additions and 9 deletions

View File

@@ -43,7 +43,7 @@ struct HomeView: View {
AccentButton(imageSystemName: "ellipsis") {
NavigationModel.shared.presentingOpenVideos = true
}
.frame(maxWidth: 40)
.frame(maxWidth: 40)
}
}
#endif
@@ -74,6 +74,7 @@ struct HomeView: View {
#if os(tvOS)
.font(.caption)
.imageScale(.small)
.foregroundColor(.primary)
#endif
#endif
}

View File

@@ -31,7 +31,9 @@ struct AdvancedSettings: View {
List {
advancedSettings
}
#if os(iOS)
#if os(tvOS)
.listStyle(.plain)
#elseif os(iOS)
.sheet(isPresented: $presentingShareSheet) {
ShareSheet(activityItems: filesToShare)
.id("logs-\(filesToShare.count)")
@@ -41,6 +43,8 @@ struct AdvancedSettings: View {
#endif
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1000)
#endif
.navigationTitle("Advanced")

View File

@@ -52,10 +52,14 @@ struct BrowsingSettings: View {
}
#if os(iOS)
.listStyle(.insetGrouped)
#elseif os(tvOS)
.listStyle(.plain)
#endif
#endif
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1200)
#else
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
@@ -111,6 +115,9 @@ struct BrowsingSettings: View {
NavigationLink(destination: LazyView(HomeSettings())) {
Text("Home Settings")
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
#endif
}
}

View File

@@ -29,9 +29,16 @@ struct HistorySettings: View {
List {
sections
}
#if os(tvOS)
.listStyle(.plain)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
#endif
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1000)
#elseif os(iOS)
.listStyle(.insetGrouped)

View File

@@ -22,7 +22,9 @@ struct LocationsSettings: View {
List {
settings
}
#if os(iOS)
#if os(tvOS)
.listStyle(.plain)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
#endif
@@ -42,6 +44,8 @@ struct LocationsSettings: View {
InstanceForm(savedInstanceID: $savedFormInstanceID)
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1000)
#endif
.navigationTitle("Locations")

View File

@@ -53,9 +53,16 @@ struct PlayerControlsSettings: View {
List {
sections
}
#if os(tvOS)
.listStyle(.plain)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
#endif
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1000)
#elseif os(iOS)
.listStyle(.insetGrouped)
@@ -143,6 +150,45 @@ struct PlayerControlsSettings: View {
#endif
}
#if os(tvOS)
// Custom implementation for tvOS to avoid focus overlay
return VStack(alignment: .leading, spacing: 0) {
Text("System controls buttons")
.font(.headline)
.padding(.vertical, 8)
Button(action: { systemControlsCommands = .seek }) {
HStack {
Text(labelText("Seek".localized()))
Spacer()
if systemControlsCommands == .seek {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.padding(.vertical, 4)
Button(action: {
systemControlsCommands = .restartAndAdvanceToNext
player.updateRemoteCommandCenter()
}) {
HStack {
Text(labelText("Restart/Play next".localized()))
Spacer()
if systemControlsCommands == .restartAndAdvanceToNext {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.padding(.vertical, 4)
}
#else
return Picker("System controls buttons", selection: $systemControlsCommands) {
Text(labelText("Seek".localized())).tag(SystemControlsCommands.seek)
Text(labelText("Restart/Play next".localized())).tag(SystemControlsCommands.restartAndAdvanceToNext)
@@ -151,6 +197,7 @@ struct PlayerControlsSettings: View {
player.updateRemoteCommandCenter()
}
.modifier(SettingsPickerModifier())
#endif
}
@ViewBuilder private var controlsLayoutFooter: some View {

View File

@@ -69,9 +69,16 @@ struct PlayerSettings: View {
List {
sections
}
#if os(tvOS)
.listStyle(.plain)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
#endif
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1000)
#elseif os(iOS)
.listStyle(.insetGrouped)

View File

@@ -24,12 +24,19 @@ struct QualitySettings: View {
List {
sections
}
#if os(tvOS)
.listStyle(.plain)
#elseif os(iOS)
.listStyle(.insetGrouped)
#endif
#endif
}
.sheet(isPresented: $presentingProfileForm) {
QualityProfileForm(qualityProfileID: $editedProfileID)
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1000)
#elseif os(iOS)
.listStyle(.insetGrouped)

View File

@@ -164,6 +164,7 @@ struct SettingsView: View {
Text("Not Selected")
}
}
.buttonStyle(.plain)
}
Divider()
}
@@ -175,30 +176,45 @@ struct SettingsView: View {
} label: {
Label("Browsing", systemImage: "list.and.film").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
NavigationLink {
PlayerSettings()
} label: {
Label("Player", systemImage: "play.rectangle").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
NavigationLink {
PlayerControlsSettings()
} label: {
Label("Controls", systemImage: "hand.tap").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
NavigationLink {
QualitySettings()
} label: {
Label("Quality", systemImage: "4k.tv").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
NavigationLink {
HistorySettings()
} label: {
Label("History", systemImage: "clock.arrow.circlepath").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
if !accounts.isEmpty {
NavigationLink {
@@ -206,6 +222,9 @@ struct SettingsView: View {
} label: {
Label("SponsorBlock", systemImage: "dollarsign.circle").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
}
NavigationLink {
@@ -213,12 +232,18 @@ struct SettingsView: View {
} label: {
Label("Locations", systemImage: "globe").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
NavigationLink {
AdvancedSettings()
} label: {
Label("Advanced", systemImage: "wrench.and.screwdriver").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
}
#if os(tvOS)
.padding(.horizontal, 20)
@@ -232,6 +257,9 @@ struct SettingsView: View {
} label: {
Label("Help", systemImage: "questionmark.circle").labelStyle(SettingsLabel())
}
#if os(tvOS)
.buttonStyle(.plain)
#endif
}
#if os(tvOS)
.padding(.horizontal, 20)
@@ -287,6 +315,7 @@ struct SettingsView: View {
Label("Import Settings", systemImage: "square.and.arrow.down")
.labelStyle(SettingsLabel())
}
.buttonStyle(.plain)
.padding(.horizontal, 20)
#else
Button(action: importSettings) {

View File

@@ -23,9 +23,14 @@ struct SponsorBlockSettings: View {
List {
sections
}
#if os(tvOS)
.listStyle(.plain)
#endif
#endif
}
#if os(tvOS)
.buttonStyle(.plain)
.toggleStyle(TVOSPlainToggleStyle())
.frame(maxWidth: 1000)
#else
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)

View File

@@ -0,0 +1,17 @@
import SwiftUI
#if os(tvOS)
struct TVOSPlainToggleStyle: ToggleStyle {
func makeBody(configuration: Configuration) -> some View {
Button(action: { configuration.isOn.toggle() }) {
HStack {
configuration.label
Spacer()
Image(systemName: configuration.isOn ? "checkmark.circle.fill" : "circle")
.foregroundColor(configuration.isOn ? .accentColor : .secondary)
}
}
.buttonStyle(.plain)
}
}
#endif

View File

@@ -3,14 +3,28 @@ import SwiftUI
struct SettingsPickerModifier: ViewModifier {
func body(content: Content) -> some View {
content
#if os(tvOS)
.pickerStyle(.inline)
#endif
#if os(iOS)
.pickerStyle(.automatic)
content
.pickerStyle(.inline)
.onAppear {
// Force refresh to apply button style to picker options
}
#elseif os(iOS)
content
.pickerStyle(.automatic)
#else
.labelsHidden()
content
.labelsHidden()
#endif
}
}
#if os(tvOS)
// Extension to help remove picker row backgrounds
extension View {
func pickerRowStyle() -> some View {
self.buttonStyle(.plain)
.listRowBackground(Color.clear)
}
}
#endif