import Foundation /// Etwas, das der Nutzer ausblenden und verschieben kann. public protocol Arrangeable { /// Eine Kennung, die über Abrufe hinweg gleich bleibt. /// /// **Nicht** der angezeigte Titel: benennt der Server eine Reihe oder eine /// Mediathek um, wäre sonst alles Gespeicherte hinfällig. var arrangementID: String { get } var arrangementTitle: String { get } } /// Was ausgeblendet ist und in welcher Reihenfolge der Rest steht. /// /// War bis eben an die Startseiten-Reihen von Plex gebunden. Die Rechnung ist /// dieselbe für jede Liste, die jemand selbst zusammenstellt — Reihen, /// Seitenleisteneinträge, Sender. Sie ein zweites Mal zu schreiben hieße, sie /// ein zweites Mal falsch zu machen. public struct Arrangement: Sendable, Equatable { public var hidden: Set public var order: [String] public init(hidden: Set = [], order: [String] = []) { self.hidden = hidden self.order = order } /// Wendet Auswahl und Reihenfolge an. /// /// Unbekannte Einträge landen am Ende statt zu verschwinden. Sie zu /// verwerfen wäre die einfachere Regel — und der Grund dafür, dass ein /// neu angelegtes Regal auf dem Server in der App nie aufgetaucht wäre. public func apply(to elements: [Element]) -> [Element] { let visible = elements.filter { !hidden.contains($0.arrangementID) } guard !order.isEmpty else { return visible } let positions = Dictionary( order.enumerated().map { ($1, $0) }, // Ein doppelter Eintrag in der gespeicherten Reihenfolge — etwa // nach einem fehlgeschlagenen Schreibvorgang — soll nicht // abstürzen. Die erste Position gewinnt. uniquingKeysWith: { first, _ in first } ) return visible.enumerated().sorted { left, right in let leftPosition = positions[left.element.arrangementID] ?? Int.max let rightPosition = positions[right.element.arrangementID] ?? Int.max // Bei gleicher Position — beide unbekannt — bleibt die Reihenfolge // des Servers erhalten. Ohne diese Rückfalllinie wäre die // Anordnung neuer Einträge zufällig. if leftPosition == rightPosition { return left.offset < right.offset } return leftPosition < rightPosition }.map(\.element) } /// Die Liste für den Einstellungsbildschirm: gespeicherte Reihenfolge /// zuerst, danach alles Übrige. /// /// Gespeicherte Einträge, die es nicht mehr gibt, fallen heraus — sie /// anzuzeigen hieße, eine Reihe anzubieten, die niemand mehr hat. public func arrangedIdentifiers(from elements: [Element]) -> [String] { let available = elements.map(\.arrangementID) let known = Set(available) let stored = order.filter(known.contains) return stored + available.filter { !stored.contains($0) } } } extension MediaRowGroup: Arrangeable { public var arrangementID: String { id } public var arrangementTitle: String { title } }