diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 911d9376..f58f35ac 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -7241,17 +7241,6 @@ } } }, - "panel.page.tracks" : { - "comment" : "Details panel page name", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tracks" - } - } - } - }, "panel.region.belowVideo" : { "comment" : "Corner layout region option", "localizations" : { diff --git a/Yattee/Models/PlayerControls/PanelSettings.swift b/Yattee/Models/PlayerControls/PanelSettings.swift index f3edbb00..b3e14a72 100644 --- a/Yattee/Models/PlayerControls/PanelSettings.swift +++ b/Yattee/Models/PlayerControls/PanelSettings.swift @@ -17,7 +17,6 @@ enum PanelPageType: String, Codable, CaseIterable, Sendable { case related case chapters case queue - case tracks /// Localized display name shown in the page bar and settings. var displayName: String { @@ -32,8 +31,6 @@ enum PanelPageType: String, Codable, CaseIterable, Sendable { return String(localized: "player.chapters") case .queue: return String(localized: "panel.page.queue") - case .tracks: - return String(localized: "panel.page.tracks") } } @@ -66,8 +63,6 @@ enum PanelPageType: String, Codable, CaseIterable, Sendable { return "list.bullet.rectangle" case .queue: return "list.bullet" - case .tracks: - return "slider.horizontal.3" } } } @@ -206,19 +201,6 @@ struct ChaptersPageSettings: Codable, Hashable, Sendable { var effectiveShowThumbnails: Bool { showThumbnails ?? true } } -/// Element toggles for the tracks page. -struct TracksPageSettings: Codable, Hashable, Sendable { - var showQuality: Bool? - var showAudio: Bool? - var showSubtitles: Bool? - var showRate: Bool? - - var effectiveShowQuality: Bool { showQuality ?? true } - var effectiveShowAudio: Bool { showAudio ?? true } - var effectiveShowSubtitles: Bool { showSubtitles ?? true } - var effectiveShowRate: Bool { showRate ?? true } -} - /// Typed per-page settings payload. Uses the same discriminator pattern as /// `ButtonSettings`; an unknown discriminator from a future app version /// decodes to `nil` at the configuration level so the pages array never @@ -229,7 +211,6 @@ enum PanelPageSettings: Codable, Hashable, Sendable { case related(RelatedPageSettings) case chapters(ChaptersPageSettings) case queue(QueuePageSettings) - case tracks(TracksPageSettings) private enum CodingKeys: String, CodingKey { case type @@ -238,7 +219,6 @@ enum PanelPageSettings: Codable, Hashable, Sendable { case related case chapters case queue - case tracks } private enum SettingsType: String, Codable { @@ -247,7 +227,6 @@ enum PanelPageSettings: Codable, Hashable, Sendable { case related case chapters case queue - case tracks } init(from decoder: Decoder) throws { @@ -265,8 +244,6 @@ enum PanelPageSettings: Codable, Hashable, Sendable { self = .chapters(try container.decode(ChaptersPageSettings.self, forKey: .chapters)) case .queue: self = .queue(try container.decode(QueuePageSettings.self, forKey: .queue)) - case .tracks: - self = .tracks(try container.decode(TracksPageSettings.self, forKey: .tracks)) } } @@ -289,9 +266,6 @@ enum PanelPageSettings: Codable, Hashable, Sendable { case .queue(let settings): try container.encode(SettingsType.queue, forKey: .type) try container.encode(settings, forKey: .queue) - case .tracks(let settings): - try container.encode(SettingsType.tracks, forKey: .type) - try container.encode(settings, forKey: .tracks) } } } @@ -349,12 +323,6 @@ struct PanelPageConfiguration: Codable, Hashable, Sendable, Identifiable { return QueuePageSettings() } - /// Tracks page settings when set, defaults otherwise. - var tracksSettings: TracksPageSettings { - if case .tracks(let settings) = settings { return settings } - return TracksPageSettings() - } - private enum CodingKeys: String, CodingKey { case pageType case isEnabled @@ -415,7 +383,6 @@ struct PanelSettings: Codable, Hashable, Sendable { PanelPageConfiguration(pageType: .related, region: .primary), PanelPageConfiguration(pageType: .chapters, region: .primary), PanelPageConfiguration(pageType: .queue, region: .primary), - PanelPageConfiguration(pageType: .tracks, isEnabled: false, region: .primary), ]) var effectiveLayoutMode: PanelLayoutMode { layoutMode ?? .stacked } diff --git a/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift b/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift index 0aa218c2..5662b1af 100644 --- a/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift +++ b/Yattee/ViewModels/PlayerControls/PlayerControlsSettingsViewModel.swift @@ -891,15 +891,6 @@ final class PlayerControlsSettingsViewModel { } } - /// Synchronously updates the tracks page element toggles. - func updatePanelTracksSettingsSync(_ mutation: (inout TracksPageSettings) -> Void) { - updatePanelSettingsSync { settings in - var tracks = settings.configuration(for: .tracks)?.tracksSettings ?? TracksPageSettings() - mutation(&tracks) - settings.updateSettings(.tracks(tracks), for: .tracks) - } - } - /// Whether the page bar shows labels next to icons. var panelShowPageBarLabels: Bool { panelSettings.effectiveShowPageBarLabels diff --git a/Yattee/Views/Player/Panels/PlayerPanelContentView.swift b/Yattee/Views/Player/Panels/PlayerPanelContentView.swift index ec6899f2..008a14fc 100644 --- a/Yattee/Views/Player/Panels/PlayerPanelContentView.swift +++ b/Yattee/Views/Player/Panels/PlayerPanelContentView.swift @@ -63,8 +63,6 @@ struct PlayerPanelContentView: View { return !(playerState?.chapters ?? []).isEmpty case .queue: return appEnvironment?.settingsManager.queueEnabled ?? true - case .tracks: - return true } } @@ -178,9 +176,6 @@ struct PlayerPanelContentView: View { case .queue: QueuePanelPage(context: context, settings: page.queueSettings, bottomInset: scrollConfig.bottomInset) .id(PanelPageType.queue) - case .tracks: - TracksPanelPage(context: context, settings: page.tracksSettings, bottomInset: scrollConfig.bottomInset) - .id(PanelPageType.tracks) } } } diff --git a/Yattee/Views/Player/Panels/TracksPanelPage.swift b/Yattee/Views/Player/Panels/TracksPanelPage.swift deleted file mode 100644 index 07f68204..00000000 --- a/Yattee/Views/Player/Panels/TracksPanelPage.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// TracksPanelPage.swift -// Yattee -// -// Details panel page embedding the quality/audio/subtitles/rate selector. -// - -import SwiftUI - -#if os(iOS) || os(macOS) - -struct TracksPanelPage: View { - let context: PanelPageContext - var settings: TracksPageSettings = TracksPageSettings() - var bottomInset: CGFloat = 0 - - private var appEnvironment: AppEnvironment? { context.appEnvironment } - private var playerService: PlayerService? { appEnvironment?.playerService } - private var playerState: PlayerState? { playerService?.state } - - var body: some View { - if let playerService { - let supportedFormats = playerService.currentBackendType.supportedFormats - QualitySelectorView( - streams: playerService.availableStreams.filter { stream in - let format = StreamFormat.detect(from: stream) - // DASH is never offered for manual selection - if format == .dash { - return false - } - return supportedFormats.contains(format) - }, - captions: playerService.availableCaptions, - currentStream: playerState?.currentStream, - currentAudioStream: playerState?.currentAudioStream, - currentCaption: playerService.currentCaption, - isLoading: playerService.availableStreams.isEmpty && playerState?.playbackState == .loading, - currentDownload: playerService.currentDownload, - isLoadingOnlineStreams: playerService.isLoadingOnlineStreams, - localCaptionURL: playerService.currentDownload.flatMap { download in - guard let path = download.localCaptionPath else { return nil } - return appEnvironment?.downloadManager.downloadsDirectory().appendingPathComponent(path) - }, - currentRate: playerState?.rate ?? .x1, - isControlsLocked: playerState?.isControlsLocked ?? false, - isAudioMode: appEnvironment?.settingsManager.audioOnlyModeEnabled ?? false, - embeddedAudioTracks: playerService.embeddedAudioTracks, - embeddedSubtitleTracks: playerService.embeddedSubtitleTracks, - currentEmbeddedAudioTrackID: playerService.selectedEmbeddedAudioTrackID, - currentEmbeddedSubtitleTrackID: playerService.selectedEmbeddedSubtitleTrackID, - embeddedVideoTrack: playerService.primaryEmbeddedVideoTrack, - onStreamSelected: { stream, audioStream in - Task { - await playerService.selectStreamManually(stream, audioStream: audioStream) - } - }, - onCaptionSelected: { caption in - playerService.loadCaption(caption) - }, - onLoadOnlineStreams: { - Task { - await playerService.loadOnlineStreams() - } - }, - onSwitchToOnlineStream: { stream, audioStream in - Task { - await playerService.switchToOnlineStream(stream, audioStream: audioStream) - } - }, - onEmbeddedAudioTrackSelected: { trackID in - playerService.selectEmbeddedAudioTrack(trackID) - }, - onEmbeddedSubtitleTrackSelected: { trackID in - playerService.selectEmbeddedSubtitleTrack(trackID) - }, - onRateChanged: { rate in - playerState?.rate = rate - playerService.currentBackend?.rate = Float(rate.rawValue) - }, - onLockToggled: { locked in - playerState?.isControlsLocked = locked - }, - onAudioModeToggled: { enabled in - Task { - await playerService.setAudioMode(enabled) - } - }, - isEmbedded: true, - visibleSections: settings - ) - .safeAreaPadding(.bottom, bottomInset) - .panelListLeadingMargin() - } - } -} - -#endif diff --git a/Yattee/Views/Player/QualitySelectorView+Sections.swift b/Yattee/Views/Player/QualitySelectorView+Sections.swift index 8d0ef3c8..f55d5b74 100644 --- a/Yattee/Views/Player/QualitySelectorView+Sections.swift +++ b/Yattee/Views/Player/QualitySelectorView+Sections.swift @@ -346,19 +346,15 @@ extension QualitySelectorView { #if os(tvOS) // tvOS rows are styled individually to match the Settings rows. VStack(spacing: 8) { - if visibleSections.effectiveShowRate { - playbackSpeedRow - } + playbackSpeedRow audioModeRow } #else VStack(spacing: 0) { - if visibleSections.effectiveShowRate { - playbackSpeedRow + playbackSpeedRow - Divider() - } + Divider() audioModeRow diff --git a/Yattee/Views/Player/QualitySelectorView.swift b/Yattee/Views/Player/QualitySelectorView.swift index 74080848..85955276 100644 --- a/Yattee/Views/Player/QualitySelectorView.swift +++ b/Yattee/Views/Player/QualitySelectorView.swift @@ -64,14 +64,10 @@ struct QualitySelectorView: View { /// the tvOS half-screen panel). When nil, falls back to `@Environment(\.dismiss)`. var onDismiss: (() -> Void)? - /// Embedded inside a container (details panel tracks page): no close - /// toolbar item, no sheet detents, and no minimum frame. + /// Embedded inside a container: no close toolbar item, no sheet detents, + /// and no minimum frame. var isEmbedded: Bool = false - /// Section visibility from the tracks page preset settings. Defaults show - /// everything, so the standalone sheet is unaffected. - var visibleSections: TracksPageSettings = TracksPageSettings() - #if os(tvOS) /// Bound to the first focusable row so we can programmatically pull focus /// into the panel on appear (the system doesn't auto-focus an inline @@ -119,18 +115,13 @@ struct QualitySelectorView: View { // MARK: - Computed Properties - /// Available tabs based on streams and the tracks page section settings + /// Available tabs based on streams var availableTabs: [QualitySelectorTab] { - var tabs: [QualitySelectorTab] = [] - if visibleSections.effectiveShowQuality { - tabs.append(.video) - } - if visibleSections.effectiveShowAudio, - (hasVideoOnlyStreams && !audioStreams.isEmpty) || embeddedAudioTracks.count > 1 { + var tabs: [QualitySelectorTab] = [.video] + if (hasVideoOnlyStreams && !audioStreams.isEmpty) || embeddedAudioTracks.count > 1 { tabs.append(.audio) } - if visibleSections.effectiveShowSubtitles, - !captions.isEmpty || !embeddedSubtitleTracks.isEmpty || canLoadExternalSubtitles { + if !captions.isEmpty || !embeddedSubtitleTracks.isEmpty || canLoadExternalSubtitles { tabs.append(.subtitles) } // The selector never renders with zero tabs; fall back to video. @@ -222,8 +213,7 @@ struct QualitySelectorView: View { onLockToggled: ((Bool) -> Void)? = nil, onAudioModeToggled: ((Bool) -> Void)? = nil, onDismiss: (() -> Void)? = nil, - isEmbedded: Bool = false, - visibleSections: TracksPageSettings = TracksPageSettings() + isEmbedded: Bool = false ) { self.streams = streams self.captions = captions @@ -255,7 +245,6 @@ struct QualitySelectorView: View { self.onAudioModeToggled = onAudioModeToggled self.onDismiss = onDismiss self.isEmbedded = isEmbedded - self.visibleSections = visibleSections } // MARK: - Body diff --git a/Yattee/Views/Player/tvOS/TVDetailsPanel.swift b/Yattee/Views/Player/tvOS/TVDetailsPanel.swift index d60bb642..072633c6 100644 --- a/Yattee/Views/Player/tvOS/TVDetailsPanel.swift +++ b/Yattee/Views/Player/tvOS/TVDetailsPanel.swift @@ -56,9 +56,6 @@ struct TVDetailsPanel: View { return !(playerState?.chapters ?? []).isEmpty case .queue: return appEnvironment?.settingsManager.queueEnabled ?? true - case .tracks: - // The tvOS quality panel covers tracks; never offered here. - return false } } @@ -90,7 +87,7 @@ struct TVDetailsPanel: View { // Content for the selected page Group { switch effectivePage { - case .info, .tracks: + case .info: infoContent case .comments: commentsContent diff --git a/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift b/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift index 33badd1b..1b9e8df7 100644 --- a/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift +++ b/Yattee/Views/Settings/PlayerControls/PanelPageEditorView.swift @@ -24,10 +24,6 @@ struct PanelPageEditorView: View { @State private var showRelatedThumbnails: Bool = true @State private var showChapterThumbnails: Bool = true @State private var showHistory: Bool = true - @State private var showQuality: Bool = true - @State private var showAudio: Bool = true - @State private var showSubtitles: Bool = true - @State private var showRate: Bool = true private var configuration: PanelPageConfiguration? { viewModel.panelSettings.configuration(for: pageType) @@ -57,8 +53,6 @@ struct PanelPageEditorView: View { chaptersElementsSection case .queue: queueElementsSection - case .tracks: - tracksElementsSection } } .navigationTitle(pageType.displayName) @@ -243,51 +237,6 @@ struct PanelPageEditorView: View { .disabled(!viewModel.canEditActivePreset) } - // MARK: - Tracks Elements Section - - private var tracksElementsSection: some View { - Section { - Toggle( - String(localized: "player.quality.video"), - isOn: $showQuality - ) - .onChange(of: showQuality) { _, newValue in - guard newValue != tracksSettings.effectiveShowQuality else { return } - viewModel.updatePanelTracksSettingsSync { $0.showQuality = newValue } - } - - Toggle( - String(localized: "stream.audio"), - isOn: $showAudio - ) - .onChange(of: showAudio) { _, newValue in - guard newValue != tracksSettings.effectiveShowAudio else { return } - viewModel.updatePanelTracksSettingsSync { $0.showAudio = newValue } - } - - Toggle( - String(localized: "stream.subtitles"), - isOn: $showSubtitles - ) - .onChange(of: showSubtitles) { _, newValue in - guard newValue != tracksSettings.effectiveShowSubtitles else { return } - viewModel.updatePanelTracksSettingsSync { $0.showSubtitles = newValue } - } - - Toggle( - String(localized: "controls.button.speed"), - isOn: $showRate - ) - .onChange(of: showRate) { _, newValue in - guard newValue != tracksSettings.effectiveShowRate else { return } - viewModel.updatePanelTracksSettingsSync { $0.showRate = newValue } - } - } header: { - Text(String(localized: "panel.settings.elements.header")) - } - .disabled(!viewModel.canEditActivePreset) - } - // MARK: - Helpers private var infoSettings: InfoPageSettings { @@ -310,10 +259,6 @@ struct PanelPageEditorView: View { configuration?.queueSettings ?? QueuePageSettings() } - private var tracksSettings: TracksPageSettings { - configuration?.tracksSettings ?? TracksPageSettings() - } - private func syncLocalState() { isEnabled = configuration?.isEnabled ?? true region = configuration?.region ?? .primary @@ -326,11 +271,6 @@ struct PanelPageEditorView: View { showRelatedThumbnails = relatedSettings.effectiveShowThumbnails showChapterThumbnails = chaptersSettings.effectiveShowThumbnails showHistory = queueSettings.effectiveShowHistory - let tracks = tracksSettings - showQuality = tracks.effectiveShowQuality - showAudio = tracks.effectiveShowAudio - showSubtitles = tracks.effectiveShowSubtitles - showRate = tracks.effectiveShowRate } } diff --git a/Yattee/Views/Settings/PlayerControls/TVPlayerPanelsSettingsView.swift b/Yattee/Views/Settings/PlayerControls/TVPlayerPanelsSettingsView.swift index 9afd83e7..b0dbb2f4 100644 --- a/Yattee/Views/Settings/PlayerControls/TVPlayerPanelsSettingsView.swift +++ b/Yattee/Views/Settings/PlayerControls/TVPlayerPanelsSettingsView.swift @@ -42,9 +42,9 @@ struct TVPlayerPanelsSettingsView: View { private struct TVPlayerPanelsContent: View { @Bindable var viewModel: PlayerControlsSettingsViewModel - /// Pages offered on tvOS; tracks is covered by the quality panel. + /// Pages offered on tvOS. private var tvOSPages: [PanelPageConfiguration] { - viewModel.panelPages.filter { $0.pageType != .tracks } + viewModel.panelPages } var body: some View { @@ -143,9 +143,9 @@ private struct TVPanelPageEditorView: View { isEnabled && viewModel.enabledPanelPageCount <= 1 } - /// Pages as displayed in the tvOS list (tracks hidden). + /// Pages as displayed in the tvOS list. private var displayedPages: [PanelPageConfiguration] { - viewModel.panelPages.filter { $0.pageType != .tracks } + viewModel.panelPages } private var displayedIndex: Int? { diff --git a/YatteeTests/PanelSettingsTests.swift b/YatteeTests/PanelSettingsTests.swift index 83aaacde..28e43610 100644 --- a/YatteeTests/PanelSettingsTests.swift +++ b/YatteeTests/PanelSettingsTests.swift @@ -57,10 +57,6 @@ struct PanelSettingsTests { settings.updateSettings(.related(RelatedPageSettings(showThumbnails: false)), for: .related) settings.updateSettings(.chapters(ChaptersPageSettings(showThumbnails: false)), for: .chapters) settings.updateSettings(.queue(QueuePageSettings(showHistory: false)), for: .queue) - settings.updateSettings( - .tracks(TracksPageSettings(showQuality: false, showSubtitles: false)), - for: .tracks - ) let decoded = try roundTrip(settings) #expect(decoded == settings) @@ -68,13 +64,6 @@ struct PanelSettingsTests { #expect(decoded.configuration(for: .related)?.relatedSettings.effectiveShowThumbnails == false) #expect(decoded.configuration(for: .chapters)?.chaptersSettings.effectiveShowThumbnails == false) #expect(decoded.configuration(for: .queue)?.queueSettings.effectiveShowHistory == false) - - let tracks = try #require(decoded.configuration(for: .tracks)?.tracksSettings) - #expect(tracks.effectiveShowQuality == false) - #expect(tracks.effectiveShowSubtitles == false) - // Unset fields keep their effective defaults - #expect(tracks.effectiveShowAudio == true) - #expect(tracks.effectiveShowRate == true) } @Test("Typed accessors fall back to defaults for mismatched payloads") @@ -86,7 +75,6 @@ struct PanelSettingsTests { ) #expect(page.commentsSettings.effectiveShowAvatars == true) #expect(page.queueSettings.effectiveShowHistory == true) - #expect(page.tracksSettings.effectiveShowQuality == true) } // MARK: - Backward Compatibility @@ -185,8 +173,6 @@ struct PanelSettingsTests { #expect(primary == [.related, .chapters, .queue]) #expect(belowVideo == [.info, .comments]) #expect(all == [.info, .comments, .related, .chapters, .queue]) - // Tracks is disabled by default - #expect(!all.contains(.tracks)) } // MARK: - Mutations