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 { list .background(Color.flixyCanvas) .navigationTitle(Text("homeLayout.title")) .flixyInlineTitle() // Auf dem Mac gibt es den Knopf nicht — dort zieht man Zeilen ohne // vorherigen Moduswechsel. #if os(iOS) .toolbar { EditButton() } #endif .task { await load() } } private var list: some View { List { @Bindable var layout = layout Section { Picker("home.heroSource", selection: $layout.heroSource) { ForEach(HomeLayoutSettings.HeroSource.allCases) { source in Text(source.label).tag(source) } } Picker("home.heroCount", selection: $layout.heroCount) { ForEach(HomeLayoutSettings.heroCountChoices, id: \.self) { count in Text(count.formatted()).tag(count) } } // Ein Regler und keine Auswahl aus festen Stufen: es gibt hier // keinen richtigen Wert, nur den, bei dem die erste Reihe // gerade so weit hereinlugt, wie man es mag. LabeledContent { Text(layout.heroHeight.formatted(.percent.precision(.fractionLength(0)))) .foregroundStyle(Color.flixyTextSecondary) .monospacedDigit() } label: { Text("home.heroHeight") } Slider( value: $layout.heroHeight, in: HomeLayoutSettings.heroHeightRange, step: 0.05 ) .accessibilityLabel(Text("home.heroHeight")) } header: { Text("home.hero") } footer: { Text("home.heroExplanation") } .listRowBackground(Color.flixySurface) Section { Toggle("home.combineContinue", isOn: $layout.combinesContinueRows) Toggle("home.hideWatched", isOn: $layout.hidesWatchedInLatest) } .listRowBackground(Color.flixySurface) if isLoading { ProgressView() .frame(maxWidth: .infinity) .listRowBackground(Color.clear) } else if rows.isEmpty { Text("homeLayout.empty") .foregroundStyle(Color.flixyTextSecondary) .listRowBackground(Color.flixySurface) } 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)) } } header: { Text("home.rowsHeader") } 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) } } extension HomeLayoutSettings.HeroSource { var label: LocalizedStringKey { switch self { case .continueFirst: "home.heroSource.continueFirst" case .latestOnly: "home.heroSource.latestOnly" } } }