Improve tvOS settings layout: use navigation instead of sheets, fix focus clipping

- Replace sheets with navigationDestination for Add/Edit Source on tvOS
  (tvOS sheets have fixed size that doesn't fit the content)
- Fix focused cell clipping by replacing TVSettingsContainer's frame-based
  layout with safeAreaInset, matching the main settings view pattern
- Use standard List with .listStyle(.grouped) for Sources on tvOS
- Add sidebar icons and titles to TVSettingsContainer for all settings
  subviews, utilizing the left column space
- Remove redundant large navigation titles on tvOS (shown in sidebar)
- Move Edit Source Save button from toolbar into form above Delete button
  for better tvOS focus navigation
This commit is contained in:
Arkadiusz Fal
2026-04-13 18:33:44 +02:00
parent b9a6d76ab3
commit 4b245ec176
12 changed files with 269 additions and 84 deletions

View File

@@ -77,31 +77,11 @@ private struct EditRemoteServerContent: View {
}
var body: some View {
#if os(tvOS)
formContent
.accessibilityIdentifier("editSource.view")
#else
NavigationStack {
#if os(tvOS)
VStack(spacing: 0) {
HStack {
Button(String(localized: "common.cancel")) {
dismiss()
}
.buttonStyle(TVToolbarButtonStyle())
Spacer()
Text(String(localized: "sources.editSource"))
.font(.title2)
.fontWeight(.semibold)
Spacer()
Button(String(localized: "common.save")) {
saveChanges()
}
.buttonStyle(TVToolbarButtonStyle())
}
.padding(.horizontal, 48)
.padding(.vertical, 24)
formContent
.accessibilityIdentifier("editSource.view")
}
#else
formContent
.navigationTitle(String(localized: "sources.editSource"))
#if os(iOS)
@@ -120,8 +100,8 @@ private struct EditRemoteServerContent: View {
}
}
.accessibilityIdentifier("editSource.view")
#endif
}
#endif
}
private var formContent: some View {
@@ -313,6 +293,17 @@ private struct EditRemoteServerContent: View {
testResultSection(result)
}
#if os(tvOS)
Section {
Button {
saveChanges()
} label: {
Label(String(localized: "common.save"), systemImage: "checkmark.circle")
}
.buttonStyle(TVSettingsButtonStyle())
}
#endif
Section {
Button(role: .destructive) {
showingDeleteConfirmation = true
@@ -529,35 +520,14 @@ private struct EditFileSourceContent: View {
}
var body: some View {
NavigationStack {
#if os(tvOS)
VStack(spacing: 0) {
HStack {
Button(String(localized: "common.cancel")) {
dismiss()
}
.buttonStyle(TVToolbarButtonStyle())
Spacer()
Text(String(localized: "sources.editSource"))
.font(.title2)
.fontWeight(.semibold)
Spacer()
Button(String(localized: "common.save")) {
saveChanges()
}
.disabled(name.isEmpty)
.buttonStyle(TVToolbarButtonStyle())
}
.padding(.horizontal, 48)
.padding(.vertical, 24)
formContent
}
#if os(tvOS)
formContent
.onAppear {
hasExistingPassword = appEnvironment?.mediaSourcesManager.password(for: source) != nil
smbProtocolVersion = source.smbProtocolVersion ?? .auto
}
#else
#else
NavigationStack {
formContent
.navigationTitle(String(localized: "sources.editSource"))
#if os(iOS)
@@ -581,8 +551,8 @@ private struct EditFileSourceContent: View {
hasExistingPassword = appEnvironment?.mediaSourcesManager.password(for: source) != nil
smbProtocolVersion = source.smbProtocolVersion ?? .auto
}
#endif
}
#endif
}
private var formContent: some View {
@@ -718,6 +688,18 @@ private struct EditFileSourceContent: View {
}
}
#if os(tvOS)
Section {
Button {
saveChanges()
} label: {
Label(String(localized: "common.save"), systemImage: "checkmark.circle")
}
.disabled(name.isEmpty)
.buttonStyle(TVSettingsButtonStyle())
}
#endif
Section {
Button(role: .destructive) {
showingDeleteConfirmation = true