Eine Folge ist weder ein Film noch eine Serie
Die App meldete auf der Startseite „Dieser Titel ist auf dem Server nicht
mehr vorhanden" — über eine Folge, die sie selbst gerade anzeigte.
Gemessen im Quelltext: `item(id:)` probiert `/movies/{id}`, dann
`/series/{id}`, und wirft sonst `.notFound`. In „Weiterschauen" steht
eine Folge mit ihrer eigenen Kennung, und einen Endpunkt für eine
einzelne Folge gab es gar nicht. Jetzt gibt es `GET /api/v1/episodes/{id}`
— mit `SeriesId`, denn ohne die Serie fehlen der Folge Titel und Bild.
Im Simulator nachgestellt und nachgeprüft: die Seite steht.
**Zuletzt hinzugefügt.** Die App zeigte „Filme" und „Serien" — den Anfang
der Mediathek in der Reihenfolge der Datenbank, also immer denselben.
Jetzt je Mediathek eine Reihe „Neu in …", sortiert nach Zugang. Die
Kennungen beginnen nun mit `latest`, und daran hängen zwei Einstellungen,
die an einem Flaro-Server deshalb nichts taten: „Gesehenes in Neuzugängen
ausblenden" und „Aufmacher nur aus Neuzugängen".
**Geplante Titel.** `MediaItem` kennt jetzt `availableFrom`. Aufmacher und
Detailseite zeigen statt des Knopfes „Bald ist es soweit" mit dem Termin.
Die Kachel spielte trotzdem los — im Simulator kam der Spieler bis „Wird
vorbereitet …" —, also zählt ein geplanter Titel jetzt wie eine Sammlung:
nichts zu starten, der Tipp führt auf die Seite.
**Und das Bild im Aufmacher.** Bei einer angefangenen Folge stand dort ihr
Standbild statt des Bildes der Serie; wer der Serie eines ausgesucht
hatte, fand ein anderes vor. Die Kachel behält das Standbild — sie zeigt,
wo man steht —, der Aufmacher bekommt `SeriesBackdropImage`.
**Die Punkte im Karussell waren 26 × 3 Pixel.** Von drei Klicks auf
denselben Punkt traf nur der auf der Mittellinie; fünf Pixel daneben
geschah nichts. Der Knopf ist jetzt 27 Pixel hoch, sichtbar bleiben 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f10fb4c1bc
commit
10a5070e58
@@ -54,13 +54,12 @@ public actor FlaroBackend: MediaBackend {
|
||||
}
|
||||
|
||||
public func homeRows() async throws(MediaBackendError) -> [MediaRowGroup] {
|
||||
// **Vier Anfragen, nicht eine.** Flaro hat keinen Endpunkt, der eine
|
||||
// fertige Startseite liefert, und das ist Absicht: welche Reihen eine
|
||||
// App zeigt, entscheidet die App. Der Server liefert die Zutaten.
|
||||
// **Mehrere Anfragen, nicht eine.** Flaro hat keinen Endpunkt, der
|
||||
// eine fertige Startseite liefert, und das ist Absicht: welche Reihen
|
||||
// eine App zeigt, entscheidet die App. Der Server liefert die Zutaten.
|
||||
async let weiter = reihe(pfad: "/api/v1/home/continue")
|
||||
async let naechste = reihe(pfad: "/api/v1/home/next")
|
||||
async let filme = seite(pfad: "/api/v1/movies", take: 24, as: Wire.Movie.self)
|
||||
async let serien = seite(pfad: "/api/v1/series", take: 24, as: Wire.Series.self)
|
||||
let mediatheken = (try? await libraries()) ?? []
|
||||
|
||||
var reihen: [MediaRowGroup] = []
|
||||
|
||||
@@ -76,18 +75,46 @@ public actor FlaroBackend: MediaBackend {
|
||||
title: String(localized: "flaro.row.nextUp", bundle: .module),
|
||||
items: eintraege))
|
||||
}
|
||||
if let gefunden = try? await filme, !gefunden.items.isEmpty {
|
||||
|
||||
// **Je Mediathek eine Reihe mit dem zuletzt Hinzugefügten.**
|
||||
//
|
||||
// Vorher standen hier zwei Reihen, „Filme" und „Serien", mit dem
|
||||
// Anfang der Mediathek in der Reihenfolge der Datenbank — also immer
|
||||
// demselben. Drei Dinge waren damit falsch: es war kein Neuzugang,
|
||||
// es warf getrennte Mediatheken wieder zusammen, und die Kennungen
|
||||
// begannen nicht mit `latest`. An dieser Vorsilbe hängen zwei
|
||||
// Einstellungen — „Gesehenes in Neuzugängen ausblenden" und
|
||||
// „Aufmacher nur aus Neuzugängen" —, und beide taten an einem
|
||||
// Flaro-Server deshalb nichts.
|
||||
for mediathek in mediatheken {
|
||||
let pfad = mediathek.kind == .show ? "/api/v1/series" : "/api/v1/movies"
|
||||
let abfrage = [
|
||||
URLQueryItem(name: "library", value: mediathek.id),
|
||||
URLQueryItem(name: "sort", value: "added"),
|
||||
URLQueryItem(name: "take", value: "24"),
|
||||
]
|
||||
let titel: [MediaItem]
|
||||
if mediathek.kind == .show {
|
||||
guard let seite = try? await client.send(
|
||||
FlaroRequest(path: pfad, query: abfrage),
|
||||
as: Wire.Page<Wire.Series>.self) else { continue }
|
||||
titel = seite.items.map { $0.neutral() }
|
||||
} else {
|
||||
guard let seite = try? await client.send(
|
||||
FlaroRequest(path: pfad, query: abfrage),
|
||||
as: Wire.Page<Wire.Movie>.self) else { continue }
|
||||
titel = seite.items.map { $0.neutral() }
|
||||
}
|
||||
guard !titel.isEmpty else { continue }
|
||||
reihen.append(MediaRowGroup(
|
||||
id: "flaro.movies",
|
||||
title: String(localized: "flaro.row.movies", bundle: .module),
|
||||
items: gefunden.items.map { $0.neutral() }))
|
||||
}
|
||||
if let gefunden = try? await serien, !gefunden.items.isEmpty {
|
||||
reihen.append(MediaRowGroup(
|
||||
id: "flaro.series",
|
||||
title: String(localized: "flaro.row.series", bundle: .module),
|
||||
items: gefunden.items.map { $0.neutral() }))
|
||||
id: "latest.\(mediathek.id)",
|
||||
title: String(
|
||||
localized: "flaro.row.latestIn \(mediathek.title)",
|
||||
bundle: .module),
|
||||
libraryID: mediathek.id,
|
||||
items: titel))
|
||||
}
|
||||
|
||||
return reihen
|
||||
}
|
||||
|
||||
@@ -135,6 +162,21 @@ public actor FlaroBackend: MediaBackend {
|
||||
FlaroRequest(path: "/api/v1/series/\(id)"), as: Wire.Series.self) {
|
||||
return serie.neutral()
|
||||
}
|
||||
// **Und zuletzt eine Folge.**
|
||||
//
|
||||
// Sie fehlte, und das war der Grund für „Dieser Titel ist auf dem
|
||||
// Server nicht mehr vorhanden" direkt von der Startseite: in
|
||||
// „Weiterschauen" steht eine Folge mit ihrer eigenen Kennung, und die
|
||||
// ist weder ein Film noch eine Serie.
|
||||
if let folge = try? await client.send(
|
||||
FlaroRequest(path: "/api/v1/episodes/\(id)"), as: Wire.Episode.self) {
|
||||
var serie: Wire.Series?
|
||||
if let kennung = folge.seriesId {
|
||||
serie = try? await client.send(
|
||||
FlaroRequest(path: "/api/v1/series/\(kennung)"), as: Wire.Series.self)
|
||||
}
|
||||
return folge.neutral(series: serie)
|
||||
}
|
||||
throw .notFound
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ enum Wire {
|
||||
let backdropImage: String?
|
||||
let files: [File]
|
||||
let state: ItemState?
|
||||
/// Ab wann abspielbar. In der Zukunft heisst: noch nicht.
|
||||
let publishAt: Date?
|
||||
}
|
||||
|
||||
struct Series: Decodable, Sendable {
|
||||
@@ -101,6 +103,7 @@ enum Wire {
|
||||
let episodeCount: Int
|
||||
let seasons: [Int]
|
||||
let unwatchedCount: Int
|
||||
let publishAt: Date?
|
||||
}
|
||||
|
||||
struct Episode: Decodable, Sendable {
|
||||
@@ -112,6 +115,10 @@ enum Wire {
|
||||
let stillImage: String?
|
||||
let files: [File]
|
||||
let state: ItemState?
|
||||
let publishAt: Date?
|
||||
/// Zu welcher Serie sie gehört — ohne das ist eine Folge für sich
|
||||
/// nicht darstellbar: Titel und Bild der Serie fehlten.
|
||||
let seriesId: String?
|
||||
}
|
||||
|
||||
/// Eine Kachel aus „Weiterschauen" oder „Nächste Folge".
|
||||
@@ -126,6 +133,12 @@ enum Wire {
|
||||
let posterImage: String?
|
||||
let backdropImage: String?
|
||||
let state: ItemState
|
||||
/// Zu welcher Serie die Folge gehört.
|
||||
let seriesId: String?
|
||||
/// Das breite Bild der Serie — für den Aufmacher, nicht für die
|
||||
/// Kachel: die will das Standbild der Folge.
|
||||
let seriesBackdropImage: String?
|
||||
let publishAt: Date?
|
||||
}
|
||||
|
||||
struct CodeResponse: Decodable, Sendable {
|
||||
|
||||
@@ -20,6 +20,7 @@ extension Wire.Movie {
|
||||
resumeMilliseconds: state.map { Int($0.positionSeconds * 1000) },
|
||||
lastPlayedAt: state?.lastPlayedAt,
|
||||
isFavourite: state?.favourite ?? false,
|
||||
availableFrom: publishAt,
|
||||
versions: [Wire.fassung(id: id, files: files)]
|
||||
)
|
||||
}
|
||||
@@ -39,6 +40,7 @@ extension Wire.Series {
|
||||
// offen ist — die App zeigt daraus die Plakette, rechnet aber
|
||||
// intern mit der Gegenzahl. Beides anzubieten hiesse, zwei Wege
|
||||
// zu derselben Aussage zu haben.
|
||||
availableFrom: publishAt,
|
||||
episodeCount: episodeCount,
|
||||
playedEpisodeCount: episodeCount - unwatchedCount,
|
||||
childCount: seasons.count
|
||||
@@ -60,11 +62,14 @@ extension Wire.Episode {
|
||||
index: episode,
|
||||
parentIndex: season,
|
||||
seriesTitle: series?.title,
|
||||
seriesID: series?.id,
|
||||
// Die Serie aus dem Vertrag, wenn sie nicht mitgegeben wurde:
|
||||
// ohne sie führte der Weg zurück zur Serie ins Leere.
|
||||
seriesID: series?.id ?? seriesId,
|
||||
playCount: state?.playCount,
|
||||
resumeMilliseconds: state.map { Int($0.positionSeconds * 1000) },
|
||||
lastPlayedAt: state?.lastPlayedAt,
|
||||
isFavourite: state?.favourite ?? false,
|
||||
availableFrom: publishAt,
|
||||
versions: [Wire.fassung(id: id, files: files)]
|
||||
)
|
||||
}
|
||||
@@ -85,15 +90,21 @@ extension Wire.Started {
|
||||
year: year,
|
||||
duration: state.runtimeSeconds.map { Int($0 * 1000) },
|
||||
thumb: folge ? backdropImage : posterImage,
|
||||
art: backdropImage,
|
||||
// **Für die Fläche über den halben Bildschirm das Bild der
|
||||
// Serie.** `backdropImage` ist bei einer Folge ihr Standbild —
|
||||
// richtig für die Kachel, falsch für den Aufmacher: wer der
|
||||
// Serie ein Bild ausgesucht hatte, fand dort ein anderes vor.
|
||||
art: folge ? (seriesBackdropImage ?? backdropImage) : backdropImage,
|
||||
seriesThumb: folge ? posterImage : nil,
|
||||
index: episode,
|
||||
parentIndex: season,
|
||||
seriesTitle: folge ? title : nil,
|
||||
seriesID: folge ? seriesId : nil,
|
||||
playCount: state.playCount,
|
||||
resumeMilliseconds: Int(state.positionSeconds * 1000),
|
||||
lastPlayedAt: state.lastPlayedAt,
|
||||
isFavourite: state.favourite
|
||||
isFavourite: state.favourite,
|
||||
availableFrom: publishAt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"flaro.row.latestIn %@": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "Neu in %@"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "New in %@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"flaro.row.movies": {
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
|
||||
Reference in New Issue
Block a user