From 25e6ada0cedd05a036a024ff568902dcc56a246e Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sat, 3 Oct 2026 08:46:02 +0200 Subject: [PATCH] =?UTF-8?q?Herunterladen=20steht=20jetzt=20im=20Kontextmen?= =?UTF-8?q?=C3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bisher führte der Weg über die Detailseite — zwei Schritte für etwas, das man beim Vorbeiscrollen entscheidet. Langes Drücken auf eine Kachel bietet es jetzt direkt an, samt derselben Qualitätsauswahl. Die Kachel kennt nur eine Zusammenfassung ohne Dateiangaben; geholt wird deshalb erst beim Tippen. Eine Reihe aus zwanzig Kacheln wären sonst zwanzig Anfragen für ein Menü, das selten aufgeht. **Nur bei Filmen und Folgen.** Bei einer Serie oder Staffel lädt man nicht *den* Titel, sondern seine Folgen — dafür gibt es auf der Seite „Alle Folgen laden". Und nicht bei dem, was schon in der Warteschlange steht. Co-Authored-By: Claude Opus 5 --- Flixy/Views/Components/QuickPlayTile.swift | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/Flixy/Views/Components/QuickPlayTile.swift b/Flixy/Views/Components/QuickPlayTile.swift index b6208fb..50c40cf 100644 --- a/Flixy/Views/Components/QuickPlayTile.swift +++ b/Flixy/Views/Components/QuickPlayTile.swift @@ -1,5 +1,6 @@ import SwiftUI import FlixyDesign +import FlixyDownloads import MediaKit /// Eine Kachel, die zur Detailseite führt — mit einem Knopf, der sofort @@ -29,10 +30,19 @@ struct QuickPlayTile: View { @Environment(FlixySession.self) private var session @Environment(PlaybackCoordinator.self) private var playback @Environment(QuickPlay.self) private var quickPlay + @Environment(DownloadCoordinator.self) private var downloads + @Environment(LanguagePreferences.self) private var languages @Environment(\.flixyAccent) private var accent @State private var isPointerInside = false @State private var errorMessage: String? + /// Der nachgeladene Titel, sobald die Qualitätsauswahl offen sein soll. + /// + /// **Zwei Dinge in einem Zustand**, und das mit Absicht: die Kachel kennt + /// nur eine Zusammenfassung ohne Dateiangaben, also muss vor der Frage + /// nachgeladen werden. Ein eigenes `Bool` daneben wäre ein zweiter + /// Zustand, der mit diesem gleichziehen müsste. + @State private var downloadChoice: MediaItem? /// Was die umgebende Seite tun soll, wenn sich am Server etwas geändert /// hat. Siehe `ItemActionEntries`. @@ -93,6 +103,15 @@ struct QuickPlayTile: View { Divider() } Button("tile.play", systemImage: "play.fill") { trigger() } + + // **Herunterladen gehört hierher.** Bisher führte der Weg + // über die Detailseite — zwei Schritte für etwas, das man + // beim Vorbeiscrollen entscheidet. + if isDownloadable { + Button("downloads.download", systemImage: "arrow.down.circle") { + Task { await prepareDownload() } + } + } Divider() } ItemActionEntries( @@ -117,6 +136,24 @@ struct QuickPlayTile: View { // Aktion, die Millisekunden dauert, zu spät und ginge zu früh. Geht // sie dagegen schief, muss man es erfahren — sonst bliebe der Haken // einfach aus, und das sieht aus wie ein Fehler der App. + .confirmationDialog( + Text("downloads.quality.title"), + isPresented: Binding( + get: { downloadChoice != nil }, + set: { if !$0 { downloadChoice = nil } } + ), + titleVisibility: .visible + ) { + if let ziel = downloadChoice { + let wahl = DownloadQualityChoice(item: ziel, backend: session.backend) + ForEach(wahl.available) { quality in + Button(wahl.label(for: quality)) { enqueue(ziel, quality: quality) } + } + } + Button("action.cancel", role: .cancel) {} + } message: { + Text("downloads.quality.hint") + } .alert( Text("error.title"), isPresented: Binding(get: { errorMessage != nil }, set: { if !$0 { errorMessage = nil } }) @@ -177,6 +214,35 @@ struct QuickPlayTile: View { .accessibilityHidden(true) } + /// Ob es etwas herunterzuladen gibt — und ob es nicht schon da ist. + /// + /// Sammlungen, Serien und Staffeln fallen heraus: dort lädt man nicht + /// *den* Titel, sondern seine Folgen, und dafür gibt es auf der Seite + /// „Alle Folgen laden". + private var isDownloadable: Bool { + guard item.kind == .movie || item.kind == .episode else { return false } + return !downloads.items.contains { $0.ratingKey == item.id } + } + + /// Holt die Dateiangaben nach und öffnet dann die Auswahl. + /// + /// Erst beim Tippen, nicht beim Zeichnen: eine Reihe aus zwanzig Kacheln + /// wären sonst zwanzig Anfragen für ein Menü, das selten aufgeht. + private func prepareDownload() async { + downloadChoice = (try? await session.backend?.item(id: item.id)) ?? item + } + + private func enqueue(_ ziel: MediaItem, quality: DownloadQuality) { + let service = DownloadService( + backend: session.backend, coordinator: downloads, languages: languages + ) + do { + _ = try service.download(ziel, quality: quality) + } catch { + errorMessage = error.explanation + } + } + private func trigger() { quickPlay.start(item, backend: session.backend, coordinator: playback) }