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:
co-authored by
Claude Opus 5
parent
84612335ec
commit
f85891aba4
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +56,8 @@ struct AudioLanguagePicker: View {
|
||||
@Environment(\.flaroAccent) private var accent
|
||||
|
||||
var body: some View {
|
||||
switch form {
|
||||
case .zeile:
|
||||
HStack(spacing: FlaroSpacing.regular) {
|
||||
Text(title)
|
||||
.font(.flaroBody)
|
||||
@@ -54,7 +65,47 @@ struct AudioLanguagePicker: View {
|
||||
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user