Files
yattee/Yattee/Views/Player/Panels/TracksPanelPage.swift
Arkadiusz Fal c289e7576e Add per-page element customization and panel settings polish
Phase 5 of the player panels redesign:
- Per-page element toggles now rendered and editable for every page:
  comments (author avatars), related (thumbnails), chapters (thumbnails),
  queue (previously played section), tracks (video/audio/subtitles/speed
  sections)
- Page bar labels toggle (icons-only bar shows the selected page's name)
- Layout mode schematic diagram in the details panel settings
- NEW badge machinery for future page types (versionAdded on PanelPageType)
- Typed per-page settings accessors on PanelPageConfiguration and VM update
  helpers for each page kind
- Unit tests for per-page settings round-trip and accessor fallbacks

The standalone quality selector sheet is unaffected: section gating defaults
to everything visible and only the embedded tracks page passes preset
settings through.
2026-09-22 09:37:10 +02:00

97 lines
4.1 KiB
Swift

//
// 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)
}
}
}
#endif