import SwiftUI import FlixyDesign import MediaKit /// Der Aufmacher oben auf der Startseite. struct HeroSection: View { let item: MediaItem @Environment(FlixySession.self) private var session @Environment(PlaybackCoordinator.self) private var playback @Environment(\.flixyAccent) private var accent @Environment(\.horizontalSizeClass) private var sizeClass var body: some View { HeroBanner( title: item.rowTitle, metadata: metadata, summary: item.summary, artworkURL: session.backend.flatMap { HeroArtwork.backdropURL(for: item, backend: $0, compact: sizeClass == .compact) }, logoURL: session.backend.flatMap { HeroArtwork.logoURL(for: item, backend: $0) }, // Auf dem iPhone hochkant: 16:9 ließ dem Text so wenig Platz, dass // Schriftzug und Beschreibung übereinanderlagen. aspectRatio: HeroArtwork.aspectRatio(compact: sizeClass == .compact), // Auf einem großen Bildschirm nimmt der Aufmacher die Höhe, die // das Karussell ihm gibt — sonst wäre er so hoch, wie 16:9 es // vorschreibt, und darüber und darunter bliebe der halbe // Bildschirm leer. fillsAvailableSpace: sizeClass != .compact ) { Button { playback.play(item) } label: { Label(playLabel, systemImage: "play.fill") .font(.flixyCardTitle) .padding(.horizontal, FlixySpacing.loose) .padding(.vertical, FlixySpacing.snug) } .buttonStyle(.borderedProminent) .tint(accent) NavigationLink(value: MediaRoute(ratingKey: item.id, title: item.title)) { Label("hero.details", systemImage: "info.circle") .font(.flixyCardTitle) .padding(.horizontal, FlixySpacing.loose) .padding(.vertical, FlixySpacing.snug) } .buttonStyle(.bordered) .tint(Color.flixyTextSecondary) } } /// „Weiterschauen" statt „Abspielen", wenn schon etwas gesehen wurde — /// sonst wäre nicht erkennbar, dass der Film an der alten Stelle weitergeht. private var playLabel: LocalizedStringKey { item.watchProgress == nil ? "hero.play" : "hero.resume" } private var metadata: String? { var pieces: [String] = [] if item.kind == .episode, let season = item.parentIndex, let episode = item.index { pieces.append(String(localized: "episode.short \(season) \(episode)")) } if let year = item.year { pieces.append(String(year)) } if let duration = item.duration { pieces.append(Duration.milliseconds(duration).runtimeLabel) } if let rating = item.contentRating { pieces.append(rating) } return pieces.isEmpty ? nil : pieces.joined(separator: " · ") } } extension Duration { /// Laufzeit als „2 Std 46 Min" bzw. „46 Min". var runtimeLabel: String { let totalMinutes = Int(components.seconds / 60) let hours = totalMinutes / 60 let minutes = totalMinutes % 60 if hours > 0 { return String(localized: "runtime.hoursMinutes \(hours) \(minutes)") } return String(localized: "runtime.minutes \(minutes)") } }