Files
Flaro/Flixy/Views/Settings/HomeLayoutView.swift
T
ScarriffleandClaude Opus 5 fe2d730c00 Die Ansichten sprechen nicht mehr mit Plex
Start, Suche, Mediathek, Detailseiten und Downloads arbeiten jetzt mit
`MediaItem` und dem `MediaBackend`-Vertrag. Damit sind sie fertig für
einen zweiten Server, ohne dass an ihnen noch etwas zu tun wäre.

Dabei sind ein paar Namen ehrlicher geworden: aus `grandparentTitle`
wurde `seriesTitle`, aus `viewedLeafCount` `playedEpisodeCount`. Und
`TagStrip` nahm bisher Plex-Marken mit Kennung und Rolle entgegen, um
davon nur den Namen anzuzeigen — Genres sind jetzt schlicht Zeichenketten.

`Arrangement` ist aus PlexKit heraus und generisch: dieselbe Rechnung
gilt für jede Liste, die jemand selbst zusammenstellt. Phase 3 baut die
konfigurierbare Seitenleiste darauf, statt sie ein zweites Mal — und ein
zweites Mal falsch — zu schreiben.

Die Fehlerübersetzung gibt es nur noch einmal. Es waren zwei, und sie
waren bereits auseinandergelaufen: derselbe Fehler bekam je nach
Bildschirm eine andere Erklärung.

Das Nachladen der Mediathek bleibt unangetastet: eine lange Liste, die
fünf Einträge vor dem Ende nachlädt. `MediaPage` heißt zwar Seite, ist
aber eine technische Portion — Blättern mit „weiter zu Seite 2" gehört
nicht in eine Mediathek, in der man stöbert. Der Kommentar dort sagt das
jetzt ausdrücklich, damit es niemand später „aufräumt".

Player, Live-TV und die Download-Engine sprechen noch unmittelbar mit
Plex. Sie folgen als Nächstes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 12:46:52 +02:00

79 lines
2.8 KiB
Swift

import SwiftUI
import FlixyDesign
import MediaKit
/// Reihen der Startseite ein- und ausblenden und umsortieren.
struct HomeLayoutView: View {
@Environment(FlixySession.self) private var session
@Environment(\.flixyAccent) private var accent
@Environment(HomeLayoutSettings.self) private var layout
@State private var rows: [HomeRow] = []
@State private var isLoading = true
var body: some View {
Group {
if isLoading {
ProgressView().frame(maxWidth: .infinity, maxHeight: .infinity)
} else if rows.isEmpty {
ContentUnavailableView {
Label("home.empty.title", systemImage: "rectangle.stack")
} description: {
Text("homeLayout.empty")
}
} else {
list
}
}
.background(Color.flixyCanvas)
.navigationTitle(Text("homeLayout.title"))
.navigationBarTitleDisplayMode(.inline)
.toolbar { EditButton() }
.task { await load() }
}
private var list: some View {
List {
Section {
ForEach($rows) { $row in
Toggle(isOn: Binding(
get: { !row.isHidden },
set: {
row.isHidden = !$0
layout.setHidden(!$0, for: row.id)
}
)) {
Text(row.title)
.foregroundStyle(Color.flixyTextPrimary)
}
.listRowBackground(Color.flixySurface)
}
.onMove { indices, destination in
rows.move(fromOffsets: indices, toOffset: destination)
layout.setOrder(rows.map(\.id))
}
} footer: {
Text("homeLayout.explanation")
}
}
// Ausdrücklich, nicht nur geerbt.
//
// Listen und Formulare zeichnen ihre Zeilen teilweise in einem
// eigenen Darstellungskontext, in dem eigene Umgebungswerte verloren
// gehen können — sichtbar an Picker-Werten, die in der Standardfarbe
// leuchteten, während die Schalter daneben schon die gewählte trugen.
.tint(accent)
.scrollContentBackground(.hidden)
}
private func load() async {
defer { isLoading = false }
guard let backend = session.backend else { return }
// Die Reihen kommen vom Server — es gibt keine feste Liste, die sich
// vorweg anzeigen ließe. Was hier steht, hängt davon ab, welche
// Mediatheken und Sammlungen angelegt sind.
let hubs = (try? await backend.homeRows()) ?? []
rows = layout.rows(from: hubs)
}
}