Files
yattee/Yattee/Models/Navigation/SidebarItem.swift
Arkadiusz Fal d422bf13e5 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.
2026-04-18 20:38:01 +02:00

250 lines
7.4 KiB
Swift

//
// SidebarItem.swift
// Yattee
//
// Represents items that can appear in the sidebar navigation.
//
import Foundation
/// Represents all possible sidebar items for navigation.
enum SidebarItem: Hashable, Identifiable {
// MARK: - Fixed Navigation Items
case home
case search
case sources
case settings
case nowPlaying
case openURL
case remoteControl
// MARK: - Dynamic Channel Items
case channel(channelID: String, name: String, source: ContentSource)
// MARK: - Dynamic Playlist Items
case playlist(id: UUID, title: String)
// MARK: - Dynamic Media Source Items
case mediaSource(id: UUID, name: String, type: MediaSourceType)
// MARK: - Dynamic Instance Items
case instance(id: UUID, name: String, type: InstanceType)
// MARK: - Collection Items
case bookmarks
case history
case downloads
case subscriptionsFeed
case manageChannels
// MARK: - Identifiable
var id: String {
switch self {
case .home:
return "home"
case .search:
return "search"
case .sources:
return "sources"
case .settings:
return "settings"
case .nowPlaying:
return "now-playing"
case .openURL:
return "open-url"
case .remoteControl:
return "remote-control"
case .channel(let channelID, _, let source):
return "channel-\(source.provider)-\(channelID)"
case .playlist(let id, _):
return "playlist-\(id.uuidString)"
case .mediaSource(let id, _, _):
return "mediasource-\(id.uuidString)"
case .instance(let id, _, _):
return "instance-\(id.uuidString)"
case .bookmarks:
return "bookmarks"
case .history:
return "history"
case .downloads:
return "downloads"
case .subscriptionsFeed:
return "subscriptions-feed"
case .manageChannels:
return "manage-channels"
}
}
// MARK: - Display Properties
var title: String {
switch self {
case .home:
return String(localized: "tabs.home")
case .search:
return String(localized: "tabs.search")
case .sources:
return String(localized: "tabs.sources")
case .settings:
return String(localized: "tabs.settings")
case .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, _):
return name
case .playlist(_, let title):
return title
case .mediaSource(_, let name, _):
return name
case .instance(_, let name, _):
return name
case .bookmarks:
return String(localized: "home.bookmarks.title")
case .history:
return String(localized: "home.history.title")
case .downloads:
return String(localized: "home.downloads.title")
case .subscriptionsFeed:
return String(localized: "home.subscriptions.title")
case .manageChannels:
return String(localized: "sidebar.manageChannels")
}
}
var systemImage: String {
switch self {
case .home:
return "house.fill"
case .search:
return "magnifyingglass"
case .sources:
return "server.rack"
case .settings:
return "gear"
case .nowPlaying:
return "play.circle.fill"
case .openURL:
return "link"
case .remoteControl:
return "antenna.radiowaves.left.and.right"
case .channel:
return "person.circle"
case .playlist:
return "list.bullet.rectangle"
case .mediaSource(_, _, let type):
return type.systemImage
case .instance(_, _, let type):
return type.systemImage
case .bookmarks:
return "bookmark.fill"
case .history:
return "clock"
case .downloads:
return "arrow.down.circle"
case .subscriptionsFeed:
return "play.square.stack.fill"
case .manageChannels:
return "person.2"
}
}
// MARK: - Navigation
/// Converts this sidebar item to a NavigationDestination for pushing onto the navigation stack.
/// Returns nil for items that are root views (home, search) which don't push.
func navigationDestination() -> NavigationDestination? {
switch self {
case .home, .search, .sources, .settings, .nowPlaying, .openURL, .remoteControl:
// These are root tabs, not push destinations
return nil
case .channel(let channelID, _, let source):
return .channel(channelID, source)
case .playlist(let id, let title):
return .playlist(.local(id, title: title))
case .mediaSource(let id, _, _):
return .mediaSource(id)
case .instance:
// Instances are root views in the sidebar, not push destinations
return nil
case .bookmarks:
return .bookmarks
case .history:
return .history
case .downloads:
return .downloads
case .subscriptionsFeed:
return .subscriptionsFeed
case .manageChannels:
return .manageChannels
}
}
// MARK: - Item Categories
/// Whether this is a fixed navigation item (always visible).
var isFixedNavigation: Bool {
switch self {
case .home, .search, .sources, .settings, .nowPlaying, .openURL, .remoteControl:
return true
default:
return false
}
}
/// Whether this is a dynamic channel item.
var isChannel: Bool {
if case .channel = self { return true }
return false
}
/// Whether this is a dynamic playlist item.
var isPlaylist: Bool {
if case .playlist = self { return true }
return false
}
/// Whether this is a dynamic media source item.
var isMediaSource: Bool {
if case .mediaSource = self { return true }
return false
}
/// Whether this is a dynamic instance item.
var isInstance: Bool {
if case .instance = self { return true }
return false
}
}
// MARK: - Factory Methods
extension SidebarItem {
/// Creates a SidebarItem from a Subscription.
static func from(subscription: Subscription) -> SidebarItem {
.channel(
channelID: subscription.channelID,
name: subscription.name,
source: subscription.contentSource
)
}
/// Creates a SidebarItem from a LocalPlaylist.
static func from(playlist: LocalPlaylist) -> SidebarItem {
.playlist(id: playlist.id, title: playlist.title)
}
/// Creates a SidebarItem from a MediaSource.
static func from(mediaSource: MediaSource) -> SidebarItem {
.mediaSource(id: mediaSource.id, name: mediaSource.name, type: mediaSource.type)
}
/// Creates a SidebarItem from an Instance.
static func from(instance: Instance) -> SidebarItem {
.instance(id: instance.id, name: instance.displayName, type: instance.type)
}
}