diff --git a/Flixy/Resources/Localizable.xcstrings b/Flixy/Resources/Localizable.xcstrings index 13824d4..ce3ae0e 100644 --- a/Flixy/Resources/Localizable.xcstrings +++ b/Flixy/Resources/Localizable.xcstrings @@ -5,10 +5,6 @@ "comment" : "A separator between different parts of the episode metadata.", "isCommentAutoGenerated" : true }, - "%@ GB" : { - "comment" : "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.", - "isCommentAutoGenerated" : true - }, "%@ → %@" : { "comment" : "A line in a diagnostic trace that shows a network request path and its result. The first argument is the network request path. The second argument is the network response result.", "isCommentAutoGenerated" : true, @@ -21,8 +17,8 @@ } } }, - "1080p" : { - "comment" : "A label for a 1080p video.", + "%@ GB" : { + "comment" : "A picker option in the settings view that lets the user select the maximum amount of storage space that can be used for downloaded content. The value is given in gigabytes.", "isCommentAutoGenerated" : true }, "4K" : { @@ -33,8 +29,8 @@ "comment" : "A label for a 720p video.", "isCommentAutoGenerated" : true }, - "SD" : { - "comment" : "A label for a 720p movie.", + "1080p" : { + "comment" : "A label for a 1080p video.", "isCommentAutoGenerated" : true }, "account.hint" : { @@ -1564,7 +1560,9 @@ } } }, - "episode.number %@" : {}, + "episode.number %@" : { + + }, "episode.short %lld %lld" : { "comment" : "Staffel- und Folgennummer", "extractionState" : "stale", @@ -2201,6 +2199,7 @@ } }, "languages.headerExplanation" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -2854,23 +2853,6 @@ } } }, - "liveTV.recordSeries" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Serie aufnehmen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Record series" - } - } - } - }, "liveTV.recording.inProgress" : { "extractionState" : "manual", "localizations" : { @@ -2939,6 +2921,23 @@ } } }, + "liveTV.recordSeries" : { + "extractionState" : "manual", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Serie aufnehmen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Record series" + } + } + } + }, "liveTV.runningNow" : { "extractionState" : "stale", "localizations" : { @@ -3107,6 +3106,7 @@ } }, "miniPlayer.expand" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3207,6 +3207,7 @@ }, "player.buffering" : { "comment" : "Sprachausgabe für den Abspielknopf, solange auf Daten gewartet wird", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3524,23 +3525,6 @@ } } }, - "player.subtitleTracks" : { - "extractionState" : "stale", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Untertitel" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Subtitles" - } - } - } - }, "player.subtitlesOff" : { "comment" : "Eintrag, der die Untertitel abschaltet", "extractionState" : "stale", @@ -3559,6 +3543,23 @@ } } }, + "player.subtitleTracks" : { + "extractionState" : "stale", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Untertitel" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Subtitles" + } + } + } + }, "player.tracks" : { "extractionState" : "stale", "localizations" : { @@ -3627,6 +3628,7 @@ } }, "quality.audioShort" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3642,22 +3644,6 @@ } } }, - "quality.autoShort" : { - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auto" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Auto" - } - } - } - }, "quality.automatic" : { "extractionState" : "stale", "localizations" : { @@ -3675,6 +3661,23 @@ } } }, + "quality.autoShort" : { + "extractionState" : "stale", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auto" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auto" + } + } + } + }, "quality.current" : { "localizations" : { "de" : { @@ -3947,6 +3950,7 @@ } }, "quality.originalShort" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -3998,6 +4002,10 @@ } } }, + "SD" : { + "comment" : "A label for a 720p movie.", + "isCommentAutoGenerated" : true + }, "search.hint" : { "localizations" : { "de" : { @@ -4515,6 +4523,7 @@ } }, "settings.downloadsHeaderExplanation" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -4683,6 +4692,7 @@ } }, "settings.playbackExplanation" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -6231,4 +6241,4 @@ } }, "version" : "1.1" -} +} \ No newline at end of file diff --git a/Flixy/Views/Player/PlayerView.swift b/Flixy/Views/Player/PlayerView.swift index f941839..a26f175 100644 --- a/Flixy/Views/Player/PlayerView.swift +++ b/Flixy/Views/Player/PlayerView.swift @@ -168,11 +168,27 @@ struct PlayerView: View { guard isMac, case .active = phase else { return } pointerMoved() } + // **Die Umgebung wird den Blättern mitgegeben, nicht vererbt.** + // + // Ein Blatt hängt an einem eigenen Darstellungsbaum. Auf dem iPad kam + // die Umgebung des Players dort bisher an, auf dem Mac nicht — und + // `@Environment(PlaybackSettings.self)` ist kein Wert mit + // Ersatzangabe, sondern eine Behauptung: fehlt der Gegenstand, hält + // Swift die Anwendung an. Sichtbar war davon nichts, weil der Player + // in diesem Augenblick mitten in einem Layoutdurchlauf steckt: die + // App fror ein, statt zu beenden. + // + // Hier weitergereicht ist es keine Frage des Baums mehr. Der Akzent + // steht daneben, weil er sonst still auf die Vorgabe zurückfiele — + // kein Absturz, aber die falsche Farbe. .sheet(item: $trackPicker) { kind in TrackPickerSheet(model: model, kind: kind) + .environment(\.flixyAccent, accent) } .sheet(isPresented: $showsQualityChoice) { QualitySheet(model: model) + .environment(settings) + .environment(\.flixyAccent, accent) } .onChange(of: showsQualityChoice) { _, isShown in // Nach dem Schließen die Bedienelemente stehen lassen und die diff --git a/Flixy/Views/Player/QualitySheet.swift b/Flixy/Views/Player/QualitySheet.swift index 7d45d0c..5f19457 100644 --- a/Flixy/Views/Player/QualitySheet.swift +++ b/Flixy/Views/Player/QualitySheet.swift @@ -12,7 +12,15 @@ struct QualitySheet: View { let model: PlayerModel @Environment(\.flixyAccent) private var accent - @Environment(PlaybackSettings.self) private var settings + /// **Als Fragezeichen**, nicht als Behauptung. + /// + /// `@Environment(PlaybackSettings.self)` ohne Optional hält die Anwendung + /// an, wenn der Gegenstand nicht im Baum steht — und ein Blatt hängt an + /// einem eigenen Baum. Genau daran ist der Player eingefroren, und zu + /// sehen war nichts davon. Der Player reicht die Einstellungen jetzt + /// ausdrücklich herein; das Fragezeichen sorgt dafür, dass ein Vergessen + /// künftig nur eine fehlende Zeile kostet statt der ganzen App. + @Environment(PlaybackSettings.self) private var settings: PlaybackSettings? @Environment(\.dismiss) private var dismiss @State private var showsEveryRung = false @@ -67,7 +75,7 @@ struct QualitySheet: View { /// Was gerade läuft — gemessen, nicht gewünscht. @ViewBuilder private var current: some View { - if settings.showsBitrate, let statistics = model.statistics { + if settings?.showsBitrate == true, let statistics = model.statistics { Section { if let observed = statistics.observedBitrate { LabeledContent("quality.current.bitrate", value: bitrateText(observed)) diff --git a/FlixyUITests/PlayerSheets.swift b/FlixyUITests/PlayerSheets.swift new file mode 100644 index 0000000..7e9a912 --- /dev/null +++ b/FlixyUITests/PlayerSheets.swift @@ -0,0 +1,43 @@ +import XCTest + +/// Die Blätter im Player — Qualität, Tonspur, Untertitel. +/// +/// Dieser Test ist aus einem Fehlerbericht entstanden: der Qualitätsknopf +/// ließ die App hängen. Solange niemand darauf tippen konnte, fiel das nicht +/// auf — die Knöpfe lagen unter der Fensterleiste des Macs. +/// +/// Braucht die Attrappe: `python3 Tools/JellyfinStub/server.py`. +final class PlayerSheets: XCTestCase { + func testQualitySheetOpens() { + let app = XCUIApplication() + app.launchArguments = ["-FlixyStubServer", "http://127.0.0.1:8096"] + app.launch() + + XCTAssertTrue(app.staticTexts["Weiterschauen"].waitForExistence(timeout: 30)) + + // Der Aufmacher wechselt alle sechs Stunden, und der Knopf heißt je + // nach Titel anders: „Abspielen" bei einem unangefasten, bei einem + // angefangenen „Weiterschauen". Beide sind derselbe Weg in den + // Player — der Test darf sich nicht auf einen von beiden festlegen. + let start = app.buttons.matching( + NSPredicate(format: "label IN {'Abspielen', 'Weiterschauen'}") + ).firstMatch + XCTAssertTrue(start.waitForExistence(timeout: 10), "Kein Weg in den Player") + start.tap() + + var quality = app.buttons["Qualität"].firstMatch + if !quality.waitForExistence(timeout: 25) { + // Die Bedienelemente blenden sich nach ein paar Sekunden aus. + app.tap() + quality = app.buttons["Qualität"].firstMatch + } + XCTAssertTrue(quality.waitForExistence(timeout: 15), "Der Qualitätsknopf ist nicht da") + + quality.tap() + + XCTAssertTrue( + app.staticTexts["Alle Stufen"].waitForExistence(timeout: 20), + "Das Qualitätsblatt öffnet nicht" + ) + } +}