import SwiftUI /// Ein Bild vom Server mit Lade- und Fehlerzustand. /// /// Kapselt `AsyncImage`, damit der Austausch gegen einen eigenen, zwischen- /// speichernden Lader später genau eine Datei betrifft. Die Startseite zeigt /// dutzende Bilder gleichzeitig; ohne Zwischenspeicher lädt `AsyncImage` sie /// bei jedem Erscheinen neu. public struct FlixyArtwork: View { private let url: URL? private let aspectRatio: CGFloat private let fallbackSymbol: String private let contentMode: ContentMode /// - Parameter contentMode: `.fill` füllt die Fläche und schneidet ab — /// richtig für Plakate und Szenenbilder, die ohnehin formatfüllend /// gedacht sind. `.fit` zeigt das ganze Bild. Das braucht alles, was /// kein festes Seitenverhältnis hat: **Sendersymbole** sind mal breit, /// mal schmal, und formatfüllend in einem Quadrat fehlten links und /// rechts genau die Teile, an denen man den Sender erkennt. public init( url: URL?, aspectRatio: CGFloat, fallbackSymbol: String = "film", contentMode: ContentMode = .fill ) { self.url = url self.aspectRatio = aspectRatio self.fallbackSymbol = fallbackSymbol self.contentMode = contentMode } public var body: some View { Rectangle() .fill(Color.flixyPlaceholder) .aspectRatio(aspectRatio, contentMode: .fit) .overlay { AsyncImage(url: url, transaction: Transaction(animation: .easeOut(duration: 0.2))) { phase in switch phase { case .success(let image): switch contentMode { case .fill: image.resizable().scaledToFill() case .fit: image.resizable().scaledToFit() } case .failure: // Kein Fehlertext: bei einer Reihe aus zwanzig Kacheln // wäre das mehr Lärm als Hilfe. Das Symbol sagt genug. Image(systemName: fallbackSymbol) .font(.title2) .foregroundStyle(Color.flixyTextMuted) case .empty: ShimmerPlaceholder() @unknown default: ShimmerPlaceholder() } } } .clipped() } }