From f85891aba4af2da3b032b949ec715e6302fcbc16 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sat, 10 Oct 2026 22:16:12 +0200 Subject: [PATCH] =?UTF-8?q?Die=20Sprachwahl=20geh=C3=B6rt=20neben=20den=20?= =?UTF-8?q?Knopf,=20der=20sie=20anwendet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auf der Filmseite standen „Tonsprache für diesen Titel" und „Untertitel für diesen Titel" als zwei breite Zeilen unter der Beschreibung — weit weg von „Abspielen", und mit so viel Platz dazwischen, dass sie wie eine Einstellung aussahen und nicht wie eine Wahl für diesen Film. Jetzt stehen sie als „Ton: Deutsch" und „Untertitel: Keine" in der Reihe mit Abspielen, Von vorn und Herunterladen. Der Auswahlknopf hat dafür eine zweite Form bekommen statt eines zweiten Typs: neben „Abspielen" ist kein Platz für eine Zeile mit Spalten, und Beschriftung plus Wert in einem sagt dasselbe in einem Wort mehr. Auf der Serienseite bleibt es bei der Zeile — dort stehen sie in einer Liste. Co-Authored-By: Claude Opus 5 --- Flaro/Resources/Localizable.xcstrings | 38 ++++++++++ Flaro/Views/Detail/PlayableDetailView.swift | 66 ++++++++--------- .../Views/Settings/AudioLanguagePicker.swift | 70 +++++++++++++++---- 3 files changed, 127 insertions(+), 47 deletions(-) diff --git a/Flaro/Resources/Localizable.xcstrings b/Flaro/Resources/Localizable.xcstrings index 966c424..d8ef91c 100644 --- a/Flaro/Resources/Localizable.xcstrings +++ b/Flaro/Resources/Localizable.xcstrings @@ -2468,6 +2468,23 @@ } } }, + "languages.audioShort": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Ton" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Audio" + } + } + } + }, "languages.avoidAudioDescription": { "extractionState": "manual", "localizations": { @@ -2689,6 +2706,10 @@ } } }, + "languages.partial %@": { + "comment": "Eine kleine Schriftart, die", + "isCommentAutoGenerated": true + }, "languages.partial %lld": { "extractionState": "manual", "localizations": { @@ -2821,6 +2842,23 @@ } } }, + "languages.subtitlesShort": { + "extractionState": "manual", + "localizations": { + "de": { + "stringUnit": { + "state": "translated", + "value": "Untertitel" + } + }, + "en": { + "stringUnit": { + "state": "translated", + "value": "Subtitles" + } + } + } + }, "languages.title": { "localizations": { "de": { diff --git a/Flaro/Views/Detail/PlayableDetailView.swift b/Flaro/Views/Detail/PlayableDetailView.swift index 96c2150..23aed13 100644 --- a/Flaro/Views/Detail/PlayableDetailView.swift +++ b/Flaro/Views/Detail/PlayableDetailView.swift @@ -44,39 +44,6 @@ struct PlayableDetailView: View { TagStrip(tags: item.genres) } - // **Die Sprache vor dem Start, nicht erst mitten im Film.** - // - // Im Spieler umzustellen heisst, den Strom neu aufzubauen — - // und bei einem Film, den man immer im Original sehen will, - // jedes Mal von Neuem. Welche Spuren es gibt, steht hier - // ohnehin schon. - // - // **Nur bei einem Film.** Bei einer Folge gilt, was für die - // Serie eingestellt ist; dieselbe Frage zwanzigmal zu - // stellen wäre keine Wahl, sondern eine Prüfung der Geduld. - if item.kind != .episode, let angebot = sprachen { - VStack(alignment: .leading, spacing: FlaroSpacing.snug) { - if !angebot.audio.isEmpty { - AudioLanguagePicker( - scope: .series(item.id), - title: "languages.forThisTitle", - kind: .audio, - available: angebot.audio - ) - } - - if !angebot.subtitles.isEmpty { - AudioLanguagePicker( - scope: .series(item.id), - title: "languages.subtitlesForThisTitle", - kind: .subtitles, - available: angebot.subtitles - ) - } - } - .padding(.horizontal, FlaroSpacing.screenMargin) - } - if !item.cast.isEmpty { CastRow(cast: item.cast) } @@ -202,6 +169,39 @@ struct PlayableDetailView: View { } DownloadButton(item: item, mediaIndex: selectedVersion) + + // **Die Sprache vor dem Start, nicht erst mitten im Film.** + // + // Im Spieler umzustellen heisst, den Strom neu aufzubauen — + // und bei einem Film, den man immer im Original sehen will, + // jedes Mal von Neuem. Welche Spuren es gibt, steht hier + // ohnehin schon, also gehört die Wahl neben den Knopf, der + // sie anwendet. + // + // **Nur bei einem Film.** Bei einer Folge gilt, was für die + // Serie eingestellt ist; dieselbe Frage zwanzigmal zu + // stellen wäre keine Wahl, sondern eine Prüfung der Geduld. + if item.kind != .episode, let angebot = sprachen { + if !angebot.audio.isEmpty { + AudioLanguagePicker( + scope: .series(item.id), + title: "languages.audioShort", + kind: .audio, + form: .knopf, + available: angebot.audio + ) + } + + if !angebot.subtitles.isEmpty { + AudioLanguagePicker( + scope: .series(item.id), + title: "languages.subtitlesShort", + kind: .subtitles, + form: .knopf, + available: angebot.subtitles + ) + } + } } } } diff --git a/Flaro/Views/Settings/AudioLanguagePicker.swift b/Flaro/Views/Settings/AudioLanguagePicker.swift index f46410a..aa26c48 100644 --- a/Flaro/Views/Settings/AudioLanguagePicker.swift +++ b/Flaro/Views/Settings/AudioLanguagePicker.swift @@ -31,9 +31,18 @@ struct AudioLanguagePicker: View { case subtitles } + /// Wie der Knopf aussieht. + enum Form { + /// Eine Zeile: Beschriftung links, Wahl rechts. Für Listen. + case zeile + /// Ein Knopf wie „Ton: Deutsch". Für die Reihe neben „Abspielen". + case knopf + } + let scope: Scope let title: LocalizedStringKey var kind: Kind = .audio + var form: Form = .zeile /// Welche Sprachen es wirklich gibt — `nil` heisst: alle anbieten. /// @@ -47,14 +56,56 @@ struct AudioLanguagePicker: View { @Environment(\.flaroAccent) private var accent var body: some View { - HStack(spacing: FlaroSpacing.regular) { - Text(title) - .font(.flaroBody) + switch form { + case .zeile: + HStack(spacing: FlaroSpacing.regular) { + Text(title) + .font(.flaroBody) + .foregroundStyle(Color.flaroTextPrimary) + + Spacer(minLength: FlaroSpacing.regular) + + menü { auswahl } + } + + case .knopf: + // **Beschriftung und Wert in einem.** Neben „Abspielen" ist kein + // Platz für eine Zeile mit Spalten; „Ton: Deutsch" sagt dasselbe + // in einem Wort mehr. + menü { + HStack(spacing: FlaroSpacing.tight) { + Image(systemName: kind == .audio ? "waveform" : "captions.bubble") + Text("\(Text(title)): \(currentLabel)") + .font(.flaroCardTitle) + Image(systemName: "chevron.up.chevron.down") + .font(.flaroCaption) + } + .padding(.horizontal, FlaroSpacing.regular) + .padding(.vertical, FlaroSpacing.snug) + .background(Color.flaroElevated, in: Capsule()) .foregroundStyle(Color.flaroTextPrimary) + } + } + } - Spacer(minLength: FlaroSpacing.regular) + /// Das Menü selbst — zweimal dieselben Einträge, zwei Aufhänger. + private func menü(@ViewBuilder label: () -> some View) -> some View { + Menu { auswahlEintraege } label: { label() } + } - Menu { + private var auswahl: some View { + HStack(spacing: FlaroSpacing.tight) { + Text(currentLabel) + .font(.flaroBody) + Image(systemName: "chevron.up.chevron.down") + .font(.flaroCaption) + } + .foregroundStyle(accent) + } + + @ViewBuilder + private var auswahlEintraege: some View { + Group { // „Wie allgemein" zuerst und als Vorgabe: die meisten Ebenen // sollen nichts Eigenes sagen, sondern die Frage nach oben // durchreichen. @@ -91,15 +142,6 @@ struct AudioLanguagePicker: View { ) } } - } label: { - HStack(spacing: FlaroSpacing.tight) { - Text(currentLabel) - .font(.flaroBody) - Image(systemName: "chevron.up.chevron.down") - .font(.flaroCaption) - } - .foregroundStyle(accent) - } } }