Herunterladen steht jetzt im Kontextmenü

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 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-03 08:46:02 +02:00
co-authored by Claude Opus 5
parent 6b6bc6ad02
commit 25e6ada0ce
@@ -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<Card: View>: 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<Card: View>: 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<Card: View>: 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<Card: View>: 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)
}