From 5c7429abf3d9190f96728a47289c694c930b050f Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 6 May 2026 22:17:08 +0200 Subject: [PATCH] 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. --- .../Components/TVSidebarDetailContainer.swift | 16 ++++++++++++++++ Yattee/Views/Settings/EditSourceView.swift | 18 ++++++++++++++++++ .../Views/Settings/ImportPlaylistsView.swift | 12 ++++++++++++ .../Settings/ImportSubscriptionsView.swift | 12 ++++++++++++ 4 files changed, 58 insertions(+) diff --git a/Yattee/Views/Components/TVSidebarDetailContainer.swift b/Yattee/Views/Components/TVSidebarDetailContainer.swift index 980d845f..593ac97b 100644 --- a/Yattee/Views/Components/TVSidebarDetailContainer.swift +++ b/Yattee/Views/Components/TVSidebarDetailContainer.swift @@ -65,4 +65,20 @@ struct TVSidebarDetailContainer: 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 diff --git a/Yattee/Views/Settings/EditSourceView.swift b/Yattee/Views/Settings/EditSourceView.swift index 96f95203..b48f2b26 100644 --- a/Yattee/Views/Settings/EditSourceView.swift +++ b/Yattee/Views/Settings/EditSourceView.swift @@ -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") } diff --git a/Yattee/Views/Settings/ImportPlaylistsView.swift b/Yattee/Views/Settings/ImportPlaylistsView.swift index c39abe67..cc0af712 100644 --- a/Yattee/Views/Settings/ImportPlaylistsView.swift +++ b/Yattee/Views/Settings/ImportPlaylistsView.swift @@ -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 = [] @@ -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 { diff --git a/Yattee/Views/Settings/ImportSubscriptionsView.swift b/Yattee/Views/Settings/ImportSubscriptionsView.swift index b77fd2e4..ee12263f 100644 --- a/Yattee/Views/Settings/ImportSubscriptionsView.swift +++ b/Yattee/Views/Settings/ImportSubscriptionsView.swift @@ -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 = [] @@ -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 {