Die Sprachwahl gehört neben den Knopf, der sie anwendet

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 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-10 22:16:12 +02:00
co-authored by Claude Opus 5
parent 84612335ec
commit f85891aba4
3 changed files with 127 additions and 47 deletions
+38
View File
@@ -2468,6 +2468,23 @@
} }
} }
}, },
"languages.audioShort": {
"extractionState": "manual",
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Ton"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Audio"
}
}
}
},
"languages.avoidAudioDescription": { "languages.avoidAudioDescription": {
"extractionState": "manual", "extractionState": "manual",
"localizations": { "localizations": {
@@ -2689,6 +2706,10 @@
} }
} }
}, },
"languages.partial %@": {
"comment": "Eine kleine Schriftart, die",
"isCommentAutoGenerated": true
},
"languages.partial %lld": { "languages.partial %lld": {
"extractionState": "manual", "extractionState": "manual",
"localizations": { "localizations": {
@@ -2821,6 +2842,23 @@
} }
} }
}, },
"languages.subtitlesShort": {
"extractionState": "manual",
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Untertitel"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Subtitles"
}
}
}
},
"languages.title": { "languages.title": {
"localizations": { "localizations": {
"de": { "de": {
+33 -33
View File
@@ -44,39 +44,6 @@ struct PlayableDetailView: View {
TagStrip(tags: item.genres) 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 { if !item.cast.isEmpty {
CastRow(cast: item.cast) CastRow(cast: item.cast)
} }
@@ -202,6 +169,39 @@ struct PlayableDetailView: View {
} }
DownloadButton(item: item, mediaIndex: selectedVersion) 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
)
}
}
} }
} }
} }
+52 -10
View File
@@ -31,9 +31,18 @@ struct AudioLanguagePicker: View {
case subtitles 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 scope: Scope
let title: LocalizedStringKey let title: LocalizedStringKey
var kind: Kind = .audio var kind: Kind = .audio
var form: Form = .zeile
/// Welche Sprachen es wirklich gibt — `nil` heisst: alle anbieten. /// Welche Sprachen es wirklich gibt — `nil` heisst: alle anbieten.
/// ///
@@ -47,6 +56,8 @@ struct AudioLanguagePicker: View {
@Environment(\.flaroAccent) private var accent @Environment(\.flaroAccent) private var accent
var body: some View { var body: some View {
switch form {
case .zeile:
HStack(spacing: FlaroSpacing.regular) { HStack(spacing: FlaroSpacing.regular) {
Text(title) Text(title)
.font(.flaroBody) .font(.flaroBody)
@@ -54,7 +65,47 @@ struct AudioLanguagePicker: View {
Spacer(minLength: FlaroSpacing.regular) Spacer(minLength: FlaroSpacing.regular)
Menu { 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)
}
}
}
/// Das Menü selbst — zweimal dieselben Einträge, zwei Aufhänger.
private func menü(@ViewBuilder label: () -> some View) -> some View {
Menu { auswahlEintraege } label: { label() }
}
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 // „Wie allgemein" zuerst und als Vorgabe: die meisten Ebenen
// sollen nichts Eigenes sagen, sondern die Frage nach oben // sollen nichts Eigenes sagen, sondern die Frage nach oben
// durchreichen. // 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)
}
} }
} }