Fix tvOS soft-lock in import views when no rows are focusable

When all playlists/subscriptions were imported, every row collapsed to a
non-focusable checkmark and the Add All toolbar item disappeared, leaving
the view with no focusable element. The Menu button then closed the app
instead of popping the navigation stack.

Wrap the import destinations in TVSidebarDetailContainer for visual
consistency and add a Done toolbar item (cancellationAction) that is
always present on tvOS, reachable from any list row via swipe-up.
This commit is contained in:
Arkadiusz Fal
2026-05-06 22:17:08 +02:00
parent 38242edf0c
commit 5c7429abf3
4 changed files with 58 additions and 0 deletions

View File

@@ -65,4 +65,20 @@ struct TVSidebarDetailContainer<Content: View, BottomAction: View>: View {
}
}
}
struct TVDismissBottomButton: View {
var title: String = String(localized: "common.done")
var systemImage: String = "chevron.backward"
@Environment(\.dismiss) private var dismiss
var body: some View {
Button {
dismiss()
} label: {
Label(title, systemImage: systemImage)
}
.buttonStyle(TVSettingsButtonStyle())
}
}
#endif

View File

@@ -239,14 +239,32 @@ private struct EditRemoteServerContent: View {
if isLoggedIn && (instance.type == .invidious || instance.type == .piped) {
Section {
NavigationLink {
#if os(tvOS)
TVSidebarDetailContainer(
systemImage: "person.2",
title: String(localized: "sources.import.subscriptions")
) {
ImportSubscriptionsView(instance: instance)
}
#else
ImportSubscriptionsView(instance: instance)
#endif
} label: {
Label(String(localized: "sources.import.subscriptions"), systemImage: "person.2")
}
.accessibilityIdentifier("sources.import.subscriptions")
NavigationLink {
#if os(tvOS)
TVSidebarDetailContainer(
systemImage: "list.bullet.rectangle",
title: String(localized: "sources.import.playlists")
) {
ImportPlaylistsView(instance: instance)
}
#else
ImportPlaylistsView(instance: instance)
#endif
} label: {
Label(String(localized: "sources.import.playlists"), systemImage: "list.bullet.rectangle")
}

View File

@@ -11,6 +11,7 @@ struct ImportPlaylistsView: View {
let instance: Instance
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.dismiss) private var dismiss
@State private var playlists: [Playlist] = []
@State private var importedPlaylistIDs: Set<String> = []
@@ -51,12 +52,23 @@ struct ImportPlaylistsView: View {
var body: some View {
content
#if !os(tvOS)
.navigationTitle(String(localized: "import.playlists.title"))
#endif
.accessibilityIdentifier(AccessibilityID.view)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.toolbar {
#if os(tvOS)
ToolbarItem(placement: .cancellationAction) {
Button {
dismiss()
} label: {
Label(String(localized: "common.done"), systemImage: "chevron.backward")
}
}
#endif
if !unimportedPlaylists.isEmpty && importingPlaylistID == nil {
ToolbarItem(placement: .primaryAction) {
Button {

View File

@@ -11,6 +11,7 @@ struct ImportSubscriptionsView: View {
let instance: Instance
@Environment(\.appEnvironment) private var appEnvironment
@Environment(\.dismiss) private var dismiss
@State private var channels: [Channel] = []
@State private var subscribedChannelIDs: Set<String> = []
@@ -42,12 +43,23 @@ struct ImportSubscriptionsView: View {
var body: some View {
content
#if !os(tvOS)
.navigationTitle(String(localized: "import.subscriptions.title"))
#endif
.accessibilityIdentifier(AccessibilityID.view)
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.toolbar {
#if os(tvOS)
ToolbarItem(placement: .cancellationAction) {
Button {
dismiss()
} label: {
Label(String(localized: "common.done"), systemImage: "chevron.backward")
}
}
#endif
if !unsubscribedChannels.isEmpty {
ToolbarItem(placement: .primaryAction) {
Button {