Files
Flaro/Packages/FlaroKit/Sources/FlaroServerKit/FlaroContracts.swift
T
ScarriffleandClaude Opus 5 10a5070e58 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>
2026-10-06 16:06:58 +02:00

189 lines
5.2 KiB
Swift

import Foundation
/// Was Flaro über die Leitung schickt.
///
/// Ein Spiegel der `Contracts.cs` auf der Serverseite, und nur das: hier wird
/// nichts gerechnet und nichts gedeutet. Die Übersetzung in die neutralen
/// Typen steht in `FlaroBackend+Mapping`.
///
/// **Die Felder heissen wie dort.** Keine `CodingKeys`, keine Umbenennung:
/// ASP.NET Core schreibt `camelCase`, Swift liest `camelCase`, und jede
/// Abweichung wäre eine Stelle, an der beim nächsten Serverfeld jemand
/// nachziehen müsste.
enum Wire {
struct Page<T: Decodable & Sendable>: Decodable, Sendable {
let items: [T]
let total: Int
}
struct SystemInfo: Decodable, Sendable {
let version: String?
let serverName: String?
let needsSetup: Bool
let attributions: [Attribution]?
}
struct Attribution: Decodable, Sendable {
let text: String
let logoUrl: String
let url: String
}
struct LoginResponse: Decodable, Sendable {
let token: String
let user: User
}
struct User: Decodable, Sendable {
let id: String
let userName: String
let isAdministrator: Bool
}
struct Library: Decodable, Sendable {
let id: String
let name: String
let kind: String
let path: String
}
struct Stream: Decodable, Sendable {
let index: Int
let kind: String
let codec: String?
let language: String?
let title: String?
let isDefault: Bool
let isForced: Bool
let channels: Int?
let width: Int?
let height: Int?
}
struct File: Decodable, Sendable {
let id: String
let fileName: String
let sizeBytes: Int64
let container: String?
let durationSeconds: Double?
let streams: [Stream]
}
struct ItemState: Decodable, Sendable {
let positionSeconds: Double
let runtimeSeconds: Double?
let watched: Bool
let playCount: Int
let favourite: Bool
let rating: Int?
let dismissed: Bool
let lastPlayedAt: Date?
}
struct Movie: Decodable, Sendable {
let id: String
let title: String
let year: Int?
let overview: String?
let posterImage: String?
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 {
let id: String
let title: String
let year: Int?
let overview: String?
let posterImage: String?
let backdropImage: String?
let episodeCount: Int
let seasons: [Int]
let unwatchedCount: Int
let publishAt: Date?
}
struct Episode: Decodable, Sendable {
let id: String
let season: Int
let episode: Int
let title: String?
let overview: String?
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".
struct Started: Decodable, Sendable {
let id: String
let kind: String
let title: String
let subTitle: String?
let season: Int?
let episode: Int?
let year: Int?
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 {
let code: String
let handle: String
let expiresAt: Date
}
/// `state` ist „pending", „approved" oder „expired".
struct CodeState: Decodable, Sendable {
let state: String
let login: LoginResponse?
}
struct CodeRequest: Encodable, Sendable {
let deviceName: String
let deviceId: String
}
struct Delivery: Decodable, Sendable {
let method: String
let reasons: [String]
let audioToEncode: [Int]
let subtitlesDropped: [Int]
let packageState: String
let progress: Double
let error: String?
}
// --- Was hinausgeht --------------------------------------------------
struct LoginRequest: Encodable, Sendable {
let userName: String
let password: String
let deviceName: String
let deviceId: String
}
struct ProgressRequest: Encodable, Sendable {
let positionSeconds: Double
let runtimeSeconds: Double?
}
struct WatchedRequest: Encodable, Sendable { let watched: Bool }
struct FavouriteRequest: Encodable, Sendable { let favourite: Bool }
struct RatingRequest: Encodable, Sendable { let rating: Int? }
}