import SwiftUI import HaloDesign import HaloPlayback /// Alles, was die Wiedergabe betrifft. /// /// Vorher stand das verstreut in der langen Liste der Einstellungen, zwischen /// Serverangaben und Downloads. Zusammen ergibt es einen Bildschirm, den man /// einmal durchgeht und dann in Ruhe lässt. struct PlaybackSettingsView: View { @Environment(PlaybackSettings.self) private var settings @Environment(\.haloAccent) private var accent var body: some View { @Bindable var settings = settings Form { SettingsHeader( symbol: "play.rectangle", colour: SettingsPalette.playback, title: "settings.playback", explanation: "settings.playbackExplanation" ) Section { Picker("settings.qualityWifi", selection: $settings.wifiQuality) { ForEach(PlaybackSettings.Quality.allCases) { quality in Text(quality.label).tag(quality) } } Picker("settings.qualityCellular", selection: $settings.cellularQuality) { ForEach(PlaybackSettings.Quality.allCases) { quality in Text(quality.label).tag(quality) } } } header: { Text("settings.quality") } footer: { VStack(alignment: .leading, spacing: HaloSpacing.snug) { Text("settings.qualityExplanation") // **Nur mit Beleg.** Eine Warnung, die immer dasteht, // liest nach dem zweiten Mal niemand mehr. Sie erscheint, // wenn „ohne Grenze" eingestellt ist **und** die letzte // Wiedergabe tatsächlich gehakt hat. // // Ohne Grenze kommt die Entscheidung oft bei direkter // Wiedergabe heraus — und dann gibt es keine Stufe, auf // die irgendetwas ausweichen könnte. Die Voreinstellung // bleibt trotzdem, wie sie ist: zuhause am Kabel ist sie // die richtige, und still umstellen wäre nicht in Ordnung. if settings.wifiQuality == .original, settings.lastPlaybackStruggled { Label { Text("settings.qualityUnlimitedHint") } icon: { Image(systemName: "exclamationmark.triangle") } .font(.haloCaption) .foregroundStyle(Color.haloTextSecondary) } } } Section { Toggle("settings.rememberQuality", isOn: $settings.remembersQuality) Toggle("settings.showBitrate", isOn: $settings.showsBitrate) } footer: { Text("settings.rememberQualityExplanation") } Section { Picker("settings.rewind", selection: $settings.rewindSeconds) { ForEach(PlaybackSettings.skipChoices, id: \.self) { seconds in Text("settings.seconds \(seconds)").tag(seconds) } } Picker("settings.forward", selection: $settings.forwardSeconds) { ForEach(PlaybackSettings.skipChoices, id: \.self) { seconds in Text("settings.seconds \(seconds)").tag(seconds) } } Picker("settings.defaultRate", selection: $settings.defaultRate) { ForEach(PlaybackSettings.rateChoices, id: \.self) { rate in Text(rateLabel(rate)).tag(rate) } } } header: { Text("settings.controls") } footer: { // Zurück und vor getrennt, weil sie verschiedene Zwecke haben: // zurück, weil man etwas nicht verstanden hat; vor, weil man // etwas überspringen will. Text("settings.controlsExplanation") } Section { Toggle("settings.lockLandscape", isOn: $settings.locksLandscape) } footer: { Text("settings.lockLandscapeExplanation") } Section { Toggle("settings.autoSkipIntro", isOn: $settings.autoSkipIntro) Toggle("settings.autoSkipCredits", isOn: $settings.autoSkipCredits) Toggle("settings.autoPlayNext", isOn: $settings.autoPlayNext) } header: { Text("settings.episodes") } } .tint(accent) .haloForm() .scrollContentBackground(.hidden) .background(Color.haloCanvas) .navigationTitle(Text("settings.playback")) .haloInlineTitle() } private func rateLabel(_ rate: Double) -> String { rate == 1 ? String(localized: "settings.rateNormal") : rate.formatted(.number.precision(.fractionLength(0...2))) + "×" } } extension PlaybackSettings.Quality { var label: LocalizedStringKey { switch self { case .original: "quality.original" case .high: "quality.high" case .medium: "quality.medium" case .low: "quality.low" case .minimal: "quality.minimal" } } }