Der Aufmacher bleibt stehen, und Folgen bekommen ein Bild
Drei Sachen am Karussell. Die Vorschlaege wechselten bei jedem Start, obwohl es dafuer laengst ein Zeitfenster von sechs Stunden gab. Der Grund: das Los hing an der Position in der Liste, nicht am Titel. Kam einer dazu, verschwand einer, oder aenderte sich ein Fortschritt, bekamen alle dahinter ein anderes Los — und „Weiterschauen\" waechst und schrumpft staendig. Jetzt haengt das Los an der Kennung; wer dazukommt, stoert niemanden. Als eigener Typ mit elf Tests, weil „es wechselt zu oft\" kein Befund ist, den man debuggen kann. Folgen zeigten kein Bild. Die Kette Folge → Serie gab es zwar, aber sie hoerte beim ersten Glied auf, das sich **zusammensetzen** liess — und ob es das Bild dahinter gibt, stellt sich erst beim Abruf heraus. Die Reihenfolge wandert deshalb ins Laden: Folge, Staffel, Serie, der Reihe nach, bis eine Adresse antwortet. Und auf dem Mac gibt es jetzt Pfeile links und rechts, sobald der Zeiger ueber dem Aufmacher liegt, sowie Punkte, die unter dem Zeiger wachsen und auf die man springen kann. Nur dort: auf dem iPad und dem iPhone wischt man ohnehin, und zwei Knoepfe staenden nur im Bild. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4b084bc926
commit
2a2afa8573
@@ -8,7 +8,18 @@ import SwiftUI
|
||||
/// jedem Erscheinen neu. Auf dem Mac wurde daraus ein sichtbarer Fehler: der
|
||||
/// Aufmacher und die Hälfte der Plakate blieben grau.
|
||||
public struct FlixyArtwork: View {
|
||||
private let url: URL?
|
||||
/// Die Adressen, der Reihe nach probiert.
|
||||
///
|
||||
/// **Warum mehrere.** Eine Folge hat selten ein eigenes Querformatbild,
|
||||
/// die Staffel manchmal, die Serie fast immer. Bisher wurde die erste
|
||||
/// Adresse genommen, die sich **zusammensetzen** liess — und ob es das
|
||||
/// Bild dahinter gibt, stellt sich erst beim Abruf heraus. Gab es das
|
||||
/// Staffelbild nicht, blieb die Fläche grau, und der Weg zur Serie wurde
|
||||
/// nie versucht.
|
||||
///
|
||||
/// Die Reihenfolge gehört deshalb hierher, ins Laden, nicht ins
|
||||
/// Zusammensetzen: erst wer gefragt hat, weiss, ob es etwas gibt.
|
||||
private let urls: [URL]
|
||||
private let aspectRatio: CGFloat
|
||||
/// Ob das Bild den Rahmen einnimmt, den es bekommt, statt sich seine Höhe
|
||||
/// aus dem Seitenverhältnis auszurechnen.
|
||||
@@ -33,7 +44,24 @@ public struct FlixyArtwork: View {
|
||||
fallbackSymbol: String = "film",
|
||||
contentMode: ContentMode = .fill
|
||||
) {
|
||||
self.url = url
|
||||
self.init(
|
||||
urls: [url].compactMap(\.self),
|
||||
aspectRatio: aspectRatio,
|
||||
fillsFrame: fillsFrame,
|
||||
fallbackSymbol: fallbackSymbol,
|
||||
contentMode: contentMode
|
||||
)
|
||||
}
|
||||
|
||||
/// Mehrere Adressen, von der besten zur schlechtesten.
|
||||
public init(
|
||||
urls: [URL],
|
||||
aspectRatio: CGFloat,
|
||||
fillsFrame: Bool = false,
|
||||
fallbackSymbol: String = "film",
|
||||
contentMode: ContentMode = .fill
|
||||
) {
|
||||
self.urls = urls
|
||||
self.aspectRatio = aspectRatio
|
||||
self.fillsFrame = fillsFrame
|
||||
self.fallbackSymbol = fallbackSymbol
|
||||
@@ -49,7 +77,7 @@ public struct FlixyArtwork: View {
|
||||
content
|
||||
}
|
||||
.clipped()
|
||||
.task(id: url) { await load() }
|
||||
.task(id: urls) { await load() }
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -69,8 +97,12 @@ public struct FlixyArtwork: View {
|
||||
/// nicht — das kostet immer einen Durchlauf.
|
||||
private var available: PlatformImage? {
|
||||
if let loaded { return loaded }
|
||||
guard let url else { return nil }
|
||||
return ArtworkLoader.cachedImage(for: url)
|
||||
// Die erste, die schon im Speicher liegt — in der Reihenfolge der
|
||||
// Vorlieben, nicht in der des Zufalls.
|
||||
for url in urls {
|
||||
if let bild = ArtworkLoader.cachedImage(for: url) { return bild }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@@ -92,7 +124,7 @@ public struct FlixyArtwork: View {
|
||||
}
|
||||
|
||||
private func load() async {
|
||||
guard let url else {
|
||||
guard !urls.isEmpty else {
|
||||
hasFailed = false
|
||||
loaded = nil
|
||||
return
|
||||
@@ -100,7 +132,7 @@ public struct FlixyArtwork: View {
|
||||
|
||||
// Was schon da ist, zeigt `available` bereits — hier bleibt nur das
|
||||
// Nachladen.
|
||||
if ArtworkLoader.cachedImage(for: url) != nil {
|
||||
if available != nil {
|
||||
hasFailed = false
|
||||
return
|
||||
}
|
||||
@@ -108,12 +140,17 @@ public struct FlixyArtwork: View {
|
||||
loaded = nil
|
||||
hasFailed = false
|
||||
|
||||
let image = await ArtworkLoader.shared.image(for: url)
|
||||
guard !Task.isCancelled else { return }
|
||||
|
||||
withAnimation(.easeOut(duration: 0.2)) {
|
||||
loaded = image
|
||||
hasFailed = image == nil
|
||||
// **Der Reihe nach, bis eine antwortet.** Genau das konnte die
|
||||
// frühere Fassung nicht: sie hatte nur eine Adresse und musste sich
|
||||
// vorher entscheiden, ohne zu wissen, ob es das Bild gibt.
|
||||
for url in urls {
|
||||
let bild = await ArtworkLoader.shared.image(for: url)
|
||||
guard !Task.isCancelled else { return }
|
||||
if let bild {
|
||||
withAnimation(.easeOut(duration: 0.2)) { loaded = bild }
|
||||
return
|
||||
}
|
||||
}
|
||||
hasFailed = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public struct HeroBanner<Actions: View>: View {
|
||||
/// durch jeden Aufruf.
|
||||
private let metadataContent: AnyView?
|
||||
private let summary: String?
|
||||
private let artworkURL: URL?
|
||||
private let artworkURLs: [URL]
|
||||
/// Der freigestellte Schriftzug des Films. Steht er zur Verfügung, tritt
|
||||
/// er an die Stelle des gesetzten Titels — „Harry Potter" in seiner
|
||||
/// eigenen Schrift ist wiedererkennbar, eine Systemschrift nicht.
|
||||
@@ -34,7 +34,7 @@ public struct HeroBanner<Actions: View>: View {
|
||||
metadata: String? = nil,
|
||||
metadataContent: AnyView? = nil,
|
||||
summary: String? = nil,
|
||||
artworkURL: URL?,
|
||||
artworkURLs: [URL],
|
||||
logoURL: URL? = nil,
|
||||
// 16:9, weil Plex genau das als `art` liefert. Vorher stand hier
|
||||
// 3:4 — mit `scaledToFill` wurde das Bild dadurch links und rechts
|
||||
@@ -48,7 +48,7 @@ public struct HeroBanner<Actions: View>: View {
|
||||
self.metadata = metadata
|
||||
self.metadataContent = metadataContent
|
||||
self.summary = summary
|
||||
self.artworkURL = artworkURL
|
||||
self.artworkURLs = artworkURLs
|
||||
self.logoURL = logoURL
|
||||
self.aspectRatio = aspectRatio
|
||||
self.fillsAvailableSpace = fillsAvailableSpace
|
||||
@@ -127,7 +127,7 @@ public struct HeroBanner<Actions: View>: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
FlixyArtwork(url: artworkURL, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
FlixyArtwork(urls: artworkURLs, aspectRatio: aspectRatio, fillsFrame: fillsAvailableSpace)
|
||||
.overlay(alignment: .bottom) {
|
||||
// Der Verlauf endet in der Hintergrundfarbe der Seite, nicht in
|
||||
// Schwarz: sonst entsteht genau dort, wo der Aufmacher in die
|
||||
|
||||
Reference in New Issue
Block a user