Files
Flaro/Packages/FlixyKit/Sources/PlexKit/Models/PlexLibrary.swift
T
GuidoandClaude Opus 5 53dacc9f1d Plakate hochkant, Weiterschauen im Querformat
Alle Reihen zeigten Querformat-Kacheln. Für „Weiterschauen" ist das
richtig — man erkennt die Szene wieder, an der man aufgehört hat. Beim
Aussuchen sucht man dagegen nach Plakaten, und die sind hochkant.

Die Unterscheidung hängt an der Hub-Kennung, nicht am Titel: der kommt
in der Sprache des Servers, und die Startseite sähe sonst auf Englisch
anders aus als auf Deutsch, ohne dass jemand auf die Ursache käme.
`ondeck` zählt mit — ältere Serverfassungen nennen es so.

Bei Folgen in Hochkant-Reihen wird das Serienplakat genommen, nicht das
Szenenbild: ein 16:9-Bild hochkant beschnitten zeigt einen zufälligen
Ausschnitt und ist zum Wiedererkennen wertlos.

Plakate sind schmaler als Szenenbilder, damit bei gleicher Höhe mehr
nebeneinander passen — mehr Auswahl auf einen Blick ist der Zweck.

Vier Tests, darunter einer, der festhält, dass der übersetzte Titel die
Form nicht bestimmen darf.

149 Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 22:44:13 +02:00

123 lines
4.3 KiB
Swift

import Foundation
/// Eine Mediathek auf dem Server.
public struct PlexLibrary: Sendable, Equatable, Decodable, Identifiable {
public enum Kind: String, Sendable, Codable {
case movie, show, artist, photo
/// Live-TV und Aufnahmen.
case livetv
}
/// Die Nummer des Abschnitts, als Zeichenkette. Steckt in fast jedem
/// weiteren Pfad.
public let key: String
public let title: String
public let type: String
public let uuid: String?
public let art: String?
public let thumb: String?
public let updatedAt: Int?
public var id: String { key }
public var kind: Kind? { Kind(rawValue: type) }
/// Ob Flixy diese Mediathek darstellen kann. Musik und Fotos sind laut
/// Plan nicht vorgesehen — sie erscheinen deshalb gar nicht erst in der
/// Seitenleiste, statt dort auf einen leeren Bildschirm zu führen.
public var isSupported: Bool {
kind == .movie || kind == .show
}
public init(
key: String,
title: String,
type: String,
uuid: String? = nil,
art: String? = nil,
thumb: String? = nil,
updatedAt: Int? = nil
) {
self.key = key
self.title = title
self.type = type
self.uuid = uuid
self.art = art
self.thumb = thumb
self.updatedAt = updatedAt
}
}
/// Eine Reihe auf der Startseite.
///
/// Plex nennt sie „Hub" und liefert sie fertig zusammengestellt — Weiterschauen,
/// Zuletzt hinzugefügt, Sammlungen. Die App muss diese Reihen also nicht selbst
/// bauen, sondern nur darstellen.
public struct PlexHub: Sendable, Equatable, Decodable, Identifiable {
/// Stabiler Bezeichner wie `home.continue` oder `movie.recentlyadded`.
/// Danach richtet sich die Sortierung in den Einstellungen.
public let hubIdentifier: String
public let title: String
public let type: String?
public let key: String?
public let size: Int?
/// Ob es mehr Einträge gibt, als die Reihe zeigt — Grundlage für ein
/// „Alle anzeigen" am Ende.
public let more: Bool
public let items: [PlexItem]
public var id: String { hubIdentifier }
/// Ob diese Reihe Angefangenes zeigt.
///
/// Entscheidet über die Form der Kacheln: Weiterschauen lebt vom
/// Szenenbild — man erkennt die Stelle wieder, an der man aufgehört hat.
/// Überall sonst ist das Plakat richtig, denn danach sucht man einen Film
/// aus.
///
/// Geprüft wird die Kennung, nicht der Titel: der ist übersetzt und hieße
/// auf Deutsch „Weiterschauen", auf Englisch „Continue Watching".
public var isContinueWatching: Bool {
let identifier = hubIdentifier.lowercased()
// `ondeck` ist die ältere Bezeichnung; Server unterschiedlicher
// Fassungen liefern die eine oder die andere.
return identifier.contains("continue") || identifier.contains("ondeck")
}
private enum CodingKeys: String, CodingKey {
case hubIdentifier, title, type, key, size, more
case items = "Metadata"
}
public init(from decoder: any Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.title = try values.decode(String.self, forKey: .title)
// Ältere Serverfassungen liefern keinen `hubIdentifier`. Der Titel ist
// dann der einzige verfügbare Schlüssel — schlechter, aber besser als
// eine Antwort, die gar nicht lesbar ist.
self.hubIdentifier = try values.decodeIfPresent(String.self, forKey: .hubIdentifier) ?? title
self.type = try values.decodeIfPresent(String.self, forKey: .type)
self.key = try values.decodeIfPresent(String.self, forKey: .key)
self.size = try values.decodeIfPresent(Int.self, forKey: .size)
self.more = try values.decodeIfPresent(Bool.self, forKey: .more) ?? false
self.items = try values.decodeIfPresent([PlexItem].self, forKey: .items) ?? []
}
public init(
hubIdentifier: String,
title: String,
type: String? = nil,
key: String? = nil,
size: Int? = nil,
more: Bool = false,
items: [PlexItem] = []
) {
self.hubIdentifier = hubIdentifier
self.title = title
self.type = type
self.key = key
self.size = size
self.more = more
self.items = items
}
}