import SwiftUI import HaloDesign import MediaKit /// Das Aktionsmenü auf einer Detailseite. /// /// Alles, was den Server verändert, liegt hier zusammen — statt verstreut als /// einzelne Knöpfe. Das hält die Kopfzeile frei für das, was man ständig /// braucht: Abspielen und Herunterladen. /// /// Welche Einträge erscheinen, entscheidet der Server: Plex kennt Sterne, aber /// keine Favoriten, Jellyfin Favoriten, aber keine Sterne. Ein Eintrag, der /// nichts bewirkt, ist schlimmer als keiner. struct ItemActionsMenu: View { let item: MediaItem /// Wird gerufen, wenn sich am Server etwas geändert hat und die /// Detailseite neu geladen werden sollte. let onChange: () async -> Void @Environment(HaloSession.self) private var session @State private var isWorking = false @State private var errorMessage: String? private var capabilities: Set { session.backend?.capabilities ?? [] } var body: some View { Menu { // Dieselben Einträge wie im Kontextmenü einer Kachel — siehe // `ItemActionEntries`. Hier mit Kreisel und Fehlermeldung, weil // diese Ansicht lange genug lebt, um beides zu zeigen. ItemActionEntries(item: item, capabilities: capabilities, run: run) } label: { if isWorking { ProgressView().controlSize(.small) } else { Image(systemName: "ellipsis.circle") } } .disabled(isWorking) .alert( Text("error.title"), isPresented: Binding(get: { errorMessage != nil }, set: { if !$0 { errorMessage = nil } }) ) { Button("action.close") { errorMessage = nil } } message: { if let errorMessage { Text(errorMessage) } } } /// Führt eine Serveraktion aus und lädt danach neu. /// /// Bewusst ohne vorweggenommene Anzeige: den Haken sofort zu setzen und /// bei einem Fehler wieder zurückzunehmen sieht aus wie ein Wackler. Die /// Aktionen dauern Millisekunden. private func run(_ action: @escaping (any MediaBackend) async throws -> Void) { guard let backend = session.backend else { return } isWorking = true Task { defer { isWorking = false } do { try await action(backend) await onChange() } catch { errorMessage = ErrorText.describe(error) } } } }