diff --git a/Yattee/Views/Components/TVSidebarDetailContainer.swift b/Yattee/Views/Components/TVSidebarDetailContainer.swift index 8bf0cf6b..6b483be4 100644 --- a/Yattee/Views/Components/TVSidebarDetailContainer.swift +++ b/Yattee/Views/Components/TVSidebarDetailContainer.swift @@ -9,6 +9,10 @@ #if os(tvOS) import SwiftUI +extension Notification.Name { + static let yatteeTVForcePopDetail = Notification.Name("yatteeTVForcePopDetail") +} + struct TVSidebarDetailContainer: View { let content: Content var systemImage: String? @@ -65,6 +69,9 @@ struct TVSidebarDetailContainer: View { } } } + .onReceive(NotificationCenter.default.publisher(for: .yatteeTVForcePopDetail)) { _ in + dismiss() + } } } #endif diff --git a/Yattee/Views/Navigation/UnifiedTabView.swift b/Yattee/Views/Navigation/UnifiedTabView.swift index 96996664..0e5c6f4e 100644 --- a/Yattee/Views/Navigation/UnifiedTabView.swift +++ b/Yattee/Views/Navigation/UnifiedTabView.swift @@ -614,6 +614,55 @@ struct UnifiedTabView: View { selection = item navigationCoordinator?.selectedSidebarItem = nil } + .onChange(of: selection) { oldValue, _ in + resetPath(for: oldValue) + // Force any pushed TVSidebarDetailContainer (used by Settings + // sub-pages and similar detail screens) to dismiss. Without this, + // tvOS's sidebarAdaptable TabView leaves the previously-pushed + // detail visible until the user manually presses Menu. + NotificationCenter.default.post(name: .yatteeTVForcePopDetail, object: nil) + } + } + + /// Pops the previously-selected tab's NavigationStack to its root so its + /// pushed views don't linger after the user picks another sidebar item. + private func resetPath(for item: SidebarItem) { + switch item { + case .home: + homePath = NavigationPath() + case .search: + searchPath = NavigationPath() + case .subscriptionsFeed: + subscriptionsFeedPath = NavigationPath() + case .bookmarks: + bookmarksPath = NavigationPath() + case .history: + historyPath = NavigationPath() + case .manageChannels: + manageChannelsPath = NavigationPath() + case .playlistsList: + playlistsListPath = NavigationPath() + case .sources: + sourcesPath = NavigationPath() + case .settings: + settingsPath = NavigationPath() + case .openURL: + openURLPath = NavigationPath() + case .remoteControl: + remoteControlPath = NavigationPath() + case .continueWatching: + continueWatchingPath = NavigationPath() + case let .channel(channelID, _, _): + channelPaths[channelID] = NavigationPath() + case let .playlist(id, _): + playlistPaths[id] = NavigationPath() + case let .instance(id, _, _): + instancePaths[id] = NavigationPath() + case let .mediaSource(id, _, _): + mediaSourcePaths[id] = NavigationPath() + case .nowPlaying, .downloads: + break + } } /// Applies the configured startup tab on first appearance. diff --git a/Yattee/Views/Settings/SettingsView.swift b/Yattee/Views/Settings/SettingsView.swift index d78ab584..e4def9ee 100644 --- a/Yattee/Views/Settings/SettingsView.swift +++ b/Yattee/Views/Settings/SettingsView.swift @@ -107,9 +107,8 @@ struct SettingsView: View { #if os(tvOS) private var tvOSSettings: some View { - NavigationStack { - List { - if let appEnvironment { + List { + if let appEnvironment { NavigationLink { SourcesListView() } label: { @@ -201,7 +200,6 @@ struct SettingsView: View { .allowsHitTesting(false) } .accessibilityIdentifier("settings.view") - } } #endif