import SwiftUI import FlixyDesign import PlexKit /// 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. struct ItemActionsMenu: View { let item: PlexItem /// Wird gerufen, wenn sich am Server etwas geändert hat und die /// Detailseite neu geladen werden sollte. let onChange: () async -> Void @Environment(FlixySession.self) private var session @State private var isWorking = false @State private var errorMessage: String? var body: some View { Menu { Button { run { server in if item.isWatched { try await server.markUnwatched(ratingKey: item.ratingKey) } else { try await server.markWatched(ratingKey: item.ratingKey) } } } label: { Label( item.isWatched ? "action.markUnwatched" : "action.markWatched", systemImage: item.isWatched ? "eye.slash" : "checkmark.circle" ) } Menu { ForEach((1...5).reversed(), id: \.self) { stars in Button { // Plex rechnet in Zehnteln: fünf Sterne sind eine 10. run { try await $0.rate(ratingKey: item.ratingKey, rating: stars * 2) } } label: { Label(String(repeating: "★", count: stars), systemImage: "star") } } if item.rating != nil { Divider() Button(role: .destructive) { run { try await $0.rate(ratingKey: item.ratingKey, rating: 0) } } label: { Label("action.clearRating", systemImage: "star.slash") } } } label: { Label("action.rate", systemImage: "star") } } 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 (PlexServer) async throws -> Void) { guard let server = session.server else { return } isWorking = true Task { defer { isWorking = false } do { try await action(server) await onChange() } catch { errorMessage = PlexErrorText.describe(error) } } } }