mirror of
https://github.com/yattee/yattee.git
synced 2026-05-12 10:25:02 +00:00
Add Open URL and Remote Control as sidebar items
After disabling home shortcuts on tvOS, Open URL and Remote Control had no entry point. Add them as configurable sidebar main items. Remote Control defaults to visible on tvOS; Open URL defaults to hidden on all platforms.
This commit is contained in:
@@ -14195,6 +14195,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"sidebar.mainItem.openURL" : {
|
||||||
|
"comment" : "Sidebar main navigation item for opening a URL",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Open URL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sidebar.mainItem.remoteControl" : {
|
||||||
|
"comment" : "Sidebar main navigation item for remote control",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Remote Control"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar.mainItem.search" : {
|
"sidebar.mainItem.search" : {
|
||||||
"comment" : "Sidebar main navigation item for search",
|
"comment" : "Sidebar main navigation item for search",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ enum SidebarItem: Hashable, Identifiable {
|
|||||||
case sources
|
case sources
|
||||||
case settings
|
case settings
|
||||||
case nowPlaying
|
case nowPlaying
|
||||||
|
case openURL
|
||||||
|
case remoteControl
|
||||||
|
|
||||||
// MARK: - Dynamic Channel Items
|
// MARK: - Dynamic Channel Items
|
||||||
case channel(channelID: String, name: String, source: ContentSource)
|
case channel(channelID: String, name: String, source: ContentSource)
|
||||||
@@ -49,6 +51,10 @@ enum SidebarItem: Hashable, Identifiable {
|
|||||||
return "settings"
|
return "settings"
|
||||||
case .nowPlaying:
|
case .nowPlaying:
|
||||||
return "now-playing"
|
return "now-playing"
|
||||||
|
case .openURL:
|
||||||
|
return "open-url"
|
||||||
|
case .remoteControl:
|
||||||
|
return "remote-control"
|
||||||
case .channel(let channelID, _, let source):
|
case .channel(let channelID, _, let source):
|
||||||
return "channel-\(source.provider)-\(channelID)"
|
return "channel-\(source.provider)-\(channelID)"
|
||||||
case .playlist(let id, _):
|
case .playlist(let id, _):
|
||||||
@@ -84,6 +90,10 @@ enum SidebarItem: Hashable, Identifiable {
|
|||||||
return String(localized: "tabs.settings")
|
return String(localized: "tabs.settings")
|
||||||
case .nowPlaying:
|
case .nowPlaying:
|
||||||
return String(localized: "sidebar.nowPlaying")
|
return String(localized: "sidebar.nowPlaying")
|
||||||
|
case .openURL:
|
||||||
|
return String(localized: "sidebar.mainItem.openURL")
|
||||||
|
case .remoteControl:
|
||||||
|
return String(localized: "sidebar.mainItem.remoteControl")
|
||||||
case .channel(_, let name, _):
|
case .channel(_, let name, _):
|
||||||
return name
|
return name
|
||||||
case .playlist(_, let title):
|
case .playlist(_, let title):
|
||||||
@@ -117,6 +127,10 @@ enum SidebarItem: Hashable, Identifiable {
|
|||||||
return "gear"
|
return "gear"
|
||||||
case .nowPlaying:
|
case .nowPlaying:
|
||||||
return "play.circle.fill"
|
return "play.circle.fill"
|
||||||
|
case .openURL:
|
||||||
|
return "link"
|
||||||
|
case .remoteControl:
|
||||||
|
return "antenna.radiowaves.left.and.right"
|
||||||
case .channel:
|
case .channel:
|
||||||
return "person.circle"
|
return "person.circle"
|
||||||
case .playlist:
|
case .playlist:
|
||||||
@@ -144,7 +158,7 @@ enum SidebarItem: Hashable, Identifiable {
|
|||||||
/// Returns nil for items that are root views (home, search) which don't push.
|
/// Returns nil for items that are root views (home, search) which don't push.
|
||||||
func navigationDestination() -> NavigationDestination? {
|
func navigationDestination() -> NavigationDestination? {
|
||||||
switch self {
|
switch self {
|
||||||
case .home, .search, .sources, .settings, .nowPlaying:
|
case .home, .search, .sources, .settings, .nowPlaying, .openURL, .remoteControl:
|
||||||
// These are root tabs, not push destinations
|
// These are root tabs, not push destinations
|
||||||
return nil
|
return nil
|
||||||
case .channel(let channelID, _, let source):
|
case .channel(let channelID, _, let source):
|
||||||
@@ -174,7 +188,7 @@ enum SidebarItem: Hashable, Identifiable {
|
|||||||
/// Whether this is a fixed navigation item (always visible).
|
/// Whether this is a fixed navigation item (always visible).
|
||||||
var isFixedNavigation: Bool {
|
var isFixedNavigation: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .home, .search, .sources, .settings, .nowPlaying:
|
case .home, .search, .sources, .settings, .nowPlaying, .openURL, .remoteControl:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -18,16 +18,19 @@ enum SidebarMainItem: String, CaseIterable, Codable, Identifiable, Sendable {
|
|||||||
case channels
|
case channels
|
||||||
case sources
|
case sources
|
||||||
case settings
|
case settings
|
||||||
|
case openURL
|
||||||
|
case remoteControl
|
||||||
|
|
||||||
var id: String { rawValue }
|
var id: String { rawValue }
|
||||||
|
|
||||||
/// Default order for sidebar main items.
|
/// Default order for sidebar main items.
|
||||||
static var defaultOrder: [SidebarMainItem] {
|
static var defaultOrder: [SidebarMainItem] {
|
||||||
[.search, .home, .subscriptions, .bookmarks, .history, .channels, .sources, .downloads, .settings]
|
[.search, .home, .subscriptions, .bookmarks, .history, .channels, .sources, .openURL, .remoteControl, .downloads, .settings]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default visibility (all visible except subscriptions and channels).
|
/// Default visibility (all visible except subscriptions and channels).
|
||||||
static var defaultVisibility: [SidebarMainItem: Bool] {
|
static var defaultVisibility: [SidebarMainItem: Bool] {
|
||||||
|
#if os(tvOS)
|
||||||
[
|
[
|
||||||
.search: true,
|
.search: true,
|
||||||
.home: true,
|
.home: true,
|
||||||
@@ -37,8 +40,25 @@ enum SidebarMainItem: String, CaseIterable, Codable, Identifiable, Sendable {
|
|||||||
.downloads: true,
|
.downloads: true,
|
||||||
.channels: false,
|
.channels: false,
|
||||||
.sources: true,
|
.sources: true,
|
||||||
.settings: true
|
.settings: true,
|
||||||
|
.openURL: false,
|
||||||
|
.remoteControl: true
|
||||||
]
|
]
|
||||||
|
#else
|
||||||
|
[
|
||||||
|
.search: true,
|
||||||
|
.home: true,
|
||||||
|
.subscriptions: false,
|
||||||
|
.bookmarks: false,
|
||||||
|
.history: false,
|
||||||
|
.downloads: true,
|
||||||
|
.channels: false,
|
||||||
|
.sources: true,
|
||||||
|
.settings: true,
|
||||||
|
.openURL: false,
|
||||||
|
.remoteControl: false
|
||||||
|
]
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SF Symbol icon name.
|
/// SF Symbol icon name.
|
||||||
@@ -53,6 +73,8 @@ enum SidebarMainItem: String, CaseIterable, Codable, Identifiable, Sendable {
|
|||||||
case .channels: "person.2"
|
case .channels: "person.2"
|
||||||
case .sources: "server.rack"
|
case .sources: "server.rack"
|
||||||
case .settings: "gear"
|
case .settings: "gear"
|
||||||
|
case .openURL: "link"
|
||||||
|
case .remoteControl: "antenna.radiowaves.left.and.right"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +90,8 @@ enum SidebarMainItem: String, CaseIterable, Codable, Identifiable, Sendable {
|
|||||||
case .channels: String(localized: "sidebar.mainItem.channels")
|
case .channels: String(localized: "sidebar.mainItem.channels")
|
||||||
case .sources: String(localized: "sidebar.mainItem.sources")
|
case .sources: String(localized: "sidebar.mainItem.sources")
|
||||||
case .settings: String(localized: "sidebar.mainItem.settings")
|
case .settings: String(localized: "sidebar.mainItem.settings")
|
||||||
|
case .openURL: String(localized: "sidebar.mainItem.openURL")
|
||||||
|
case .remoteControl: String(localized: "sidebar.mainItem.remoteControl")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +134,8 @@ enum SidebarMainItem: String, CaseIterable, Codable, Identifiable, Sendable {
|
|||||||
case .channels: return TabBarItem.channels.rawValue
|
case .channels: return TabBarItem.channels.rawValue
|
||||||
case .sources: return TabBarItem.sources.rawValue
|
case .sources: return TabBarItem.sources.rawValue
|
||||||
case .settings: return TabBarItem.settings.rawValue
|
case .settings: return TabBarItem.settings.rawValue
|
||||||
|
case .openURL: return "open-url"
|
||||||
|
case .remoteControl: return "remote-control"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +151,8 @@ enum SidebarMainItem: String, CaseIterable, Codable, Identifiable, Sendable {
|
|||||||
case .channels: return .manageChannels
|
case .channels: return .manageChannels
|
||||||
case .sources: return .sources
|
case .sources: return .sources
|
||||||
case .settings: return .settings
|
case .settings: return .settings
|
||||||
|
case .openURL: return .openURL
|
||||||
|
case .remoteControl: return .remoteControl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,45 @@ private enum ExtractionStatus {
|
|||||||
/// Supports multiple URLs (one per line, max 20).
|
/// Supports multiple URLs (one per line, max 20).
|
||||||
struct OpenLinkSheet: View {
|
struct OpenLinkSheet: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
let prefilledURL: URL?
|
||||||
|
|
||||||
|
init(prefilledURL: URL? = nil) {
|
||||||
|
self.prefilledURL = prefilledURL
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
OpenLinkFormView(prefilledURL: prefilledURL, onRequestDismiss: { dismiss() })
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
|
Button(String(localized: "common.cancel")) {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - OpenLinkView
|
||||||
|
|
||||||
|
/// Standalone view for entering URLs — used as a tab root (e.g. tvOS sidebar).
|
||||||
|
/// Not wrapped in a NavigationStack; the containing tab provides one.
|
||||||
|
struct OpenLinkView: View {
|
||||||
|
var body: some View {
|
||||||
|
OpenLinkFormView(prefilledURL: nil, onRequestDismiss: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - OpenLinkFormView
|
||||||
|
|
||||||
|
/// Shared form body used by both OpenLinkSheet (sheet) and OpenLinkView (tab root).
|
||||||
|
struct OpenLinkFormView: View {
|
||||||
@Environment(\.appEnvironment) private var appEnvironment
|
@Environment(\.appEnvironment) private var appEnvironment
|
||||||
|
|
||||||
|
let prefilledURL: URL?
|
||||||
|
let onRequestDismiss: (() -> Void)?
|
||||||
|
|
||||||
@State private var urlText: String
|
@State private var urlText: String
|
||||||
@State private var clipboardURLs: [URL] = []
|
@State private var clipboardURLs: [URL] = []
|
||||||
@FocusState private var isTextEditorFocused: Bool
|
@FocusState private var isTextEditorFocused: Bool
|
||||||
@@ -56,54 +93,50 @@ struct OpenLinkSheet: View {
|
|||||||
@State private var pendingDownloadItems: [ExtractedItem] = []
|
@State private var pendingDownloadItems: [ExtractedItem] = []
|
||||||
|
|
||||||
/// Maximum number of URLs allowed.
|
/// Maximum number of URLs allowed.
|
||||||
private static let maxURLs = 20
|
fileprivate static let maxURLs = 20
|
||||||
|
|
||||||
/// Initialize with optional pre-filled URL.
|
init(prefilledURL: URL?, onRequestDismiss: (() -> Void)?) {
|
||||||
init(prefilledURL: URL? = nil) {
|
self.prefilledURL = prefilledURL
|
||||||
|
self.onRequestDismiss = onRequestDismiss
|
||||||
_urlText = State(initialValue: prefilledURL?.absoluteString ?? "")
|
_urlText = State(initialValue: prefilledURL?.absoluteString ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func dismissIfRequested() {
|
||||||
|
onRequestDismiss?()
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
Form {
|
||||||
Form {
|
urlInputSection
|
||||||
urlInputSection
|
extractionResultsSection
|
||||||
extractionResultsSection
|
actionButtonsSection
|
||||||
actionButtonsSection
|
yatteeServerWarningSection
|
||||||
yatteeServerWarningSection
|
|
||||||
}
|
|
||||||
.navigationTitle(String(localized: "openLink.title"))
|
|
||||||
#if os(iOS)
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.scrollDismissesKeyboard(.immediately)
|
|
||||||
#endif
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
|
||||||
Button(String(localized: "common.cancel")) {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
checkClipboard()
|
|
||||||
if urlText.isEmpty {
|
|
||||||
isTextEditorFocused = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if !os(tvOS)
|
|
||||||
.sheet(isPresented: $showingDownloadSheet, onDismiss: {
|
|
||||||
// Close OpenLinkSheet when download sheet is dismissed (if no errors)
|
|
||||||
if !hasErrors {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
BatchDownloadQualitySheet(videoCount: pendingDownloadItems.count) { quality, includeSubtitles in
|
|
||||||
Task {
|
|
||||||
await downloadPendingItems(quality: quality, includeSubtitles: includeSubtitles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
.navigationTitle(String(localized: "openLink.title"))
|
||||||
|
#if os(iOS)
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.scrollDismissesKeyboard(.immediately)
|
||||||
|
#endif
|
||||||
|
.onAppear {
|
||||||
|
checkClipboard()
|
||||||
|
if urlText.isEmpty {
|
||||||
|
isTextEditorFocused = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
|
.sheet(isPresented: $showingDownloadSheet, onDismiss: {
|
||||||
|
// Close sheet when download sheet is dismissed (if no errors and we're in sheet mode)
|
||||||
|
if !hasErrors {
|
||||||
|
dismissIfRequested()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
BatchDownloadQualitySheet(videoCount: pendingDownloadItems.count) { quality, includeSubtitles in
|
||||||
|
Task {
|
||||||
|
await downloadPendingItems(quality: quality, includeSubtitles: includeSubtitles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - URL Input Section
|
// MARK: - URL Input Section
|
||||||
@@ -456,7 +489,7 @@ struct OpenLinkSheet: View {
|
|||||||
subtitle: String(localized: "openLink.queuedSuccess.subtitle \(successCount)")
|
subtitle: String(localized: "openLink.queuedSuccess.subtitle \(successCount)")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dismiss()
|
dismissIfRequested()
|
||||||
} else if successCount > 0 {
|
} else if successCount > 0 {
|
||||||
appEnvironment.toastManager.show(
|
appEnvironment.toastManager.show(
|
||||||
category: .error,
|
category: .error,
|
||||||
@@ -595,7 +628,7 @@ struct OpenLinkSheet: View {
|
|||||||
subtitle: String(localized: "openLink.downloadQueued.subtitle \(successCount)")
|
subtitle: String(localized: "openLink.downloadQueued.subtitle \(successCount)")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dismiss()
|
dismissIfRequested()
|
||||||
} else if successCount > 0 {
|
} else if successCount > 0 {
|
||||||
appEnvironment.toastManager.show(
|
appEnvironment.toastManager.show(
|
||||||
category: .error,
|
category: .error,
|
||||||
@@ -695,7 +728,7 @@ struct OpenLinkSheet: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if !hasErrors {
|
if !hasErrors {
|
||||||
dismiss()
|
dismissIfRequested()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
appEnvironment.toastManager.show(
|
appEnvironment.toastManager.show(
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ struct UnifiedTabView: View {
|
|||||||
@State private var manageChannelsPath = NavigationPath()
|
@State private var manageChannelsPath = NavigationPath()
|
||||||
@State private var sourcesPath = NavigationPath()
|
@State private var sourcesPath = NavigationPath()
|
||||||
@State private var settingsPath = NavigationPath()
|
@State private var settingsPath = NavigationPath()
|
||||||
|
@State private var openURLPath = NavigationPath()
|
||||||
|
@State private var remoteControlPath = NavigationPath()
|
||||||
|
|
||||||
// Current selection - initial value is a placeholder; actual startup tab is applied in onAppear
|
// Current selection - initial value is a placeholder; actual startup tab is applied in onAppear
|
||||||
@State private var selection: SidebarItem = .home
|
@State private var selection: SidebarItem = .home
|
||||||
@@ -199,6 +201,27 @@ struct UnifiedTabView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(SidebarItem.settings.title, systemImage: SidebarItem.settings.systemImage)
|
Label(SidebarItem.settings.title, systemImage: SidebarItem.settings.systemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .openURL:
|
||||||
|
Tab(value: SidebarItem.openURL) {
|
||||||
|
NavigationStack(path: $openURLPath) {
|
||||||
|
OpenLinkView()
|
||||||
|
.withNavigationDestinations()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(SidebarItem.openURL.title, systemImage: SidebarItem.openURL.systemImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .remoteControl:
|
||||||
|
Tab(value: SidebarItem.remoteControl) {
|
||||||
|
NavigationStack(path: $remoteControlPath) {
|
||||||
|
RemoteControlContentView(navigationStyle: .link)
|
||||||
|
.navigationTitle(String(localized: "remoteControl.title"))
|
||||||
|
.withNavigationDestinations()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(SidebarItem.remoteControl.title, systemImage: SidebarItem.remoteControl.systemImage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,6 +342,8 @@ struct UnifiedTabView: View {
|
|||||||
@State private var manageChannelsPath = NavigationPath()
|
@State private var manageChannelsPath = NavigationPath()
|
||||||
@State private var sourcesPath = NavigationPath()
|
@State private var sourcesPath = NavigationPath()
|
||||||
@State private var settingsPath = NavigationPath()
|
@State private var settingsPath = NavigationPath()
|
||||||
|
@State private var openURLPath = NavigationPath()
|
||||||
|
@State private var remoteControlPath = NavigationPath()
|
||||||
|
|
||||||
// Current selection - initial value is a placeholder; actual startup tab is applied in onAppear
|
// Current selection - initial value is a placeholder; actual startup tab is applied in onAppear
|
||||||
@State private var selection: SidebarItem = .home
|
@State private var selection: SidebarItem = .home
|
||||||
@@ -463,6 +488,26 @@ struct UnifiedTabView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(SidebarItem.settings.title, systemImage: SidebarItem.settings.systemImage)
|
Label(SidebarItem.settings.title, systemImage: SidebarItem.settings.systemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .openURL:
|
||||||
|
Tab(value: SidebarItem.openURL) {
|
||||||
|
NavigationStack(path: $openURLPath) {
|
||||||
|
OpenLinkView().withNavigationDestinations()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(SidebarItem.openURL.title, systemImage: SidebarItem.openURL.systemImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .remoteControl:
|
||||||
|
Tab(value: SidebarItem.remoteControl) {
|
||||||
|
NavigationStack(path: $remoteControlPath) {
|
||||||
|
RemoteControlContentView(navigationStyle: .link)
|
||||||
|
.navigationTitle(String(localized: "remoteControl.title"))
|
||||||
|
.withNavigationDestinations()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(SidebarItem.remoteControl.title, systemImage: SidebarItem.remoteControl.systemImage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,6 +583,8 @@ struct UnifiedTabView: View {
|
|||||||
@State private var manageChannelsPath = NavigationPath()
|
@State private var manageChannelsPath = NavigationPath()
|
||||||
@State private var sourcesPath = NavigationPath()
|
@State private var sourcesPath = NavigationPath()
|
||||||
@State private var settingsPath = NavigationPath()
|
@State private var settingsPath = NavigationPath()
|
||||||
|
@State private var openURLPath = NavigationPath()
|
||||||
|
@State private var remoteControlPath = NavigationPath()
|
||||||
|
|
||||||
// Current selection - initial value is a placeholder; actual startup tab is applied in onAppear
|
// Current selection - initial value is a placeholder; actual startup tab is applied in onAppear
|
||||||
@State private var selection: SidebarItem = .home
|
@State private var selection: SidebarItem = .home
|
||||||
@@ -700,6 +747,27 @@ struct UnifiedTabView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label(SidebarItem.settings.title, systemImage: SidebarItem.settings.systemImage)
|
Label(SidebarItem.settings.title, systemImage: SidebarItem.settings.systemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case .openURL:
|
||||||
|
Tab(value: SidebarItem.openURL) {
|
||||||
|
NavigationStack(path: $openURLPath) {
|
||||||
|
OpenLinkView()
|
||||||
|
.withNavigationDestinations()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(SidebarItem.openURL.title, systemImage: SidebarItem.openURL.systemImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .remoteControl:
|
||||||
|
Tab(value: SidebarItem.remoteControl) {
|
||||||
|
NavigationStack(path: $remoteControlPath) {
|
||||||
|
RemoteControlContentView(navigationStyle: .link)
|
||||||
|
.navigationTitle(String(localized: "remoteControl.title"))
|
||||||
|
.withNavigationDestinations()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Label(SidebarItem.remoteControl.title, systemImage: SidebarItem.remoteControl.systemImage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -814,6 +882,10 @@ extension UnifiedTabView {
|
|||||||
settingsPath.append(destination)
|
settingsPath.append(destination)
|
||||||
case .nowPlaying:
|
case .nowPlaying:
|
||||||
break // Now Playing is a root tab, not a push destination
|
break // Now Playing is a root tab, not a push destination
|
||||||
|
case .openURL:
|
||||||
|
openURLPath.append(destination)
|
||||||
|
case .remoteControl:
|
||||||
|
remoteControlPath.append(destination)
|
||||||
}
|
}
|
||||||
navigationCoordinator?.clearPendingNavigation()
|
navigationCoordinator?.clearPendingNavigation()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user