Phase 5: Nachladen, Startseiten-Layout

Nachladen
- DownloadRefiller wählt aus, WELCHE Folgen fehlen; wie viele, rechnet
  die Regel-Engine. Getrennt, weil das Rechnen ohne Server auskommt und
  damit prüfbar ist.
- In Ausstrahlungsreihenfolge, Specials in Staffel 0 bleiben außen vor.
- Läuft nach dem Ansehen und beim Start, nicht auf einem Zeitgeber: eine
  App, die im Hintergrund unaufgefordert Gigabytes lädt, wäre eine
  unangenehme Überraschung auf der Mobilfunkrechnung.

Startseiten-Layout
- Reihen ein-/ausblenden und per Bearbeiten-Modus umsortieren.
- Die Anordnungslogik liegt als HubArrangement in PlexKit statt im
  App-Target — dort ist sie ohne Simulator testbar, und dort sitzen die
  unangenehmen Fälle: neue Reihen vom Server landen am Ende statt zu
  verschwinden, verschwundene aus der gespeicherten Liste stören nicht,
  ein doppelter Eintrag stürzt nicht ab. 11 Tests.

126 Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-09-11 17:43:20 +02:00
co-authored by Claude Opus 5
parent af23372997
commit b7fc18823a
10 changed files with 455 additions and 2 deletions
+70
View File
@@ -0,0 +1,70 @@
import SwiftUI
import FlixyDesign
import PlexKit
/// Reihen der Startseite ein- und ausblenden und umsortieren.
struct HomeLayoutView: View {
@Environment(FlixySession.self) private var session
@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")
}
}
.scrollContentBackground(.hidden)
}
private func load() async {
defer { isLoading = false }
guard let server = session.server 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 server.homeHubs()) ?? []
rows = layout.rows(from: hubs)
}
}