import SwiftUI import HaloDesign /// Welche Sprachen der Player von sich aus wählen soll. /// /// Die Einstellung gab es schon — der Player liest sie bei jeder Wiedergabe /// aus und wählt die passende Spur. Nur einstellen konnte man sie nirgends: /// sie stand in den Voreinstellungen und blieb dort für immer leer. Eine /// Vorliebe, die niemand äußern kann, ist keine. struct LanguagesView: View { @Environment(LanguagePreferences.self) private var settings @Environment(\.haloAccent) private var accent @State private var isAdding = false private var codes: [String] { LanguagePreferences.offeredCodes } var body: some View { @Bindable var settings = settings Form { SettingsHeader( symbol: "captions.bubble", colour: SettingsPalette.languages, title: "languages.title", explanation: "languages.headerExplanation" ) Section { Picker("languages.audio", selection: $settings.audioLanguage) { Text("languages.serverDefault").tag("") ForEach(codes, id: \.self) { code in Text(name(of: code)).tag(code) } } } header: { Text("languages.audioHeader") } footer: { Text("languages.audioExplanation") } fallbackSection Section { Toggle("languages.avoidAudioDescription", isOn: $settings.avoidsAudioDescription) } footer: { Text("languages.avoidAudioDescriptionExplanation") } Section { Picker("languages.subtitles", selection: $settings.subtitleLanguage) { Text("languages.subtitlesOff").tag("") ForEach(codes, id: \.self) { code in Text(name(of: code)).tag(code) } } } header: { Text("languages.subtitlesHeader") } footer: { Text("languages.subtitlesExplanation") } } .tint(accent) .haloForm() .scrollContentBackground(.hidden) .background(Color.haloCanvas) .navigationTitle(Text("languages.title")) .haloInlineTitle() // Eine Rangliste ohne Umsortieren wäre keine. Auf dem iPhone braucht // das Ziehen den Bearbeitungsmodus nicht — `onMove` in einer `Form` // genügt, sobald die Liste bearbeitbar ist. .environment(\.editMode, .constant(.active)) } /// Die Rangliste. /// /// **Was sie ist:** der Boden unter der Wunschsprache. Sie greift nur, /// wenn ein Titel die gewünschte Sprache gar nicht hat. /// /// **Was sie nicht ist:** eine zweite Wunschsprache. Steht die /// Wunschsprache zur Verfügung, wird die Liste nicht einmal angesehen. @ViewBuilder private var fallbackSection: some View { @Bindable var settings = settings Section { if settings.fallbacks.isEmpty { Text("languages.fallbackEmpty") .foregroundStyle(Color.haloTextSecondary) } else { ForEach(Array(settings.fallbacks.enumerated()), id: \.element) { position, code in HStack(spacing: HaloSpacing.regular) { // Die Nummer, weil die Reihenfolge der ganze Zweck // ist — eine Liste ohne sichtbaren Rang liest sich // wie eine Menge. Text("\(position + 1)") .font(.haloCaption.monospacedDigit()) .foregroundStyle(Color.haloTextSecondary) .frame(minWidth: 18, alignment: .trailing) Text(name(of: code)) .foregroundStyle(Color.haloTextPrimary) } } .onMove { von, nach in settings.fallbacks.move(fromOffsets: von, toOffset: nach) } .onDelete { settings.fallbacks.remove(atOffsets: $0) } } Button { isAdding = true } label: { Label("languages.fallbackAdd", systemImage: "plus") } .disabled(remaining.isEmpty) } header: { Text("languages.fallbackHeader") } footer: { Text("languages.fallbackExplanation") } .confirmationDialog("languages.fallbackAdd", isPresented: $isAdding, titleVisibility: .visible) { ForEach(remaining, id: \.self) { code in Button(name(of: code)) { settings.fallbacks.append(code) } } } } /// Was noch nicht in der Liste steht. /// /// Eine Sprache zweimal in einer Rangliste hat keine Bedeutung — und die /// Liste benutzt den Code als Kennung, was bei Dubletten bräche. private var remaining: [String] { codes.filter { !settings.fallbacks.contains($0) } } /// Der Name der Sprache in der Sprache des Geräts. private func name(of code: String) -> String { Locale.current.localizedString(forLanguageCode: code)?.capitalized ?? code.uppercased() } }