diff --git a/Flaro/Views/Detail/SeriesDetailView.swift b/Flaro/Views/Detail/SeriesDetailView.swift index 7dd8b8c..c9812d2 100644 --- a/Flaro/Views/Detail/SeriesDetailView.swift +++ b/Flaro/Views/Detail/SeriesDetailView.swift @@ -33,6 +33,13 @@ struct SeriesDetailView: View { @State private var isWorking = false @State private var summary: String? + /// Welche Staffel die Liste zeigt — `nil` heisst: die erste. + /// + /// **Weil der Server die Folgen am Stück liefert.** Eine Serie mit zwei + /// Staffeln ergab eine Liste, in der nach Folge 8 wieder eine Folge 1 + /// kam, ohne dass irgendwo stünde, dass dazwischen eine Staffel endet. + @State private var gewaehlteStaffel: Int? + var body: some View { ScrollView { VStack(alignment: .leading, spacing: FlaroSpacing.section) { @@ -172,23 +179,33 @@ struct SeriesDetailView: View { .padding(.horizontal, FlaroSpacing.screenMargin) case .loaded(let entries): + let nummern = staffelnummern(in: entries) + let sichtbar = sichtbareFolgen(in: entries, staffeln: nummern) + VStack(alignment: .leading, spacing: FlaroSpacing.regular) { // **Der Auswählen-Knopf steht bei dem, was er auswählt.** // Oben in der Werkzeugleiste wäre er weiter weg von der Liste // als von allem anderen auf dieser Seite. HStack { - Text(item.kind == .show ? "detail.seasons" : "detail.episodes") - .font(.flaroRowHeader) - .foregroundStyle(Color.flaroTextPrimary) + if nummern.count > 1 { + staffelwahl(nummern) + } else { + Text(item.kind == .show ? "detail.seasons" : "detail.episodes") + .font(.flaroRowHeader) + .foregroundStyle(Color.flaroTextPrimary) + } Spacer() - if entries.contains(where: { $0.kind == .episode }) { - selectionControls(for: entries) + if sichtbar.contains(where: { $0.kind == .episode }) { + // **Nur die gezeigte Staffel.** „Alle auswählen" soll + // das meinen, was dasteht — sonst lädt ein Griff + // sechzehn Folgen herunter statt acht. + selectionControls(for: sichtbar) } } .padding(.horizontal, FlaroSpacing.screenMargin) LazyVStack(spacing: 0) { - ForEach(entries) { entry in + ForEach(sichtbar) { entry in row(for: entry) Divider().overlay(Color.flaroSeparator) } @@ -197,6 +214,55 @@ struct SeriesDetailView: View { } } + /// Welche Staffeln in dieser Liste vorkommen. + /// + /// Leer, wenn die Einträge selbst Staffeln sind — dann gibt es nichts zu + /// wählen, die Liste **ist** die Wahl. + private func staffelnummern(in entries: [MediaItem]) -> [Int] { + Set(entries.compactMap { $0.kind == .episode ? $0.parentIndex : nil }).sorted() + } + + private func sichtbareFolgen(in entries: [MediaItem], staffeln: [Int]) -> [MediaItem] { + guard staffeln.count > 1 else { return entries } + let gewaehlt = gewaehlteStaffel ?? staffeln[0] + return entries.filter { $0.kind != .episode || $0.parentIndex == gewaehlt } + } + + /// Die Staffel als Menü — an der Stelle, an der sonst „Folgen" steht. + /// + /// **Kein `Picker`.** Diese Seite ist eine `ScrollView`, kein `Form`; + /// dort hat ein `Picker` kein Aussehen, auf das man sich verlassen kann. + /// Dieselbe Begründung wie bei `AudioLanguagePicker`. + private func staffelwahl(_ nummern: [Int]) -> some View { + let gewaehlt = gewaehlteStaffel ?? nummern[0] + + return Menu { + ForEach(nummern, id: \.self) { nummer in + Button { + gewaehlteStaffel = nummer + // Eine Auswahl aus der vorigen Staffel meint hier nichts + // mehr — stehen zu lassen, was man nicht mehr sieht, + // lädt unsichtbare Folgen herunter. + selection.removeAll() + } label: { + Label( + String(localized: "season.short \(nummer)"), + systemImage: nummer == gewaehlt ? "checkmark" : "" + ) + } + } + } label: { + HStack(spacing: FlaroSpacing.tight) { + Text("season.short \(gewaehlt)") + .font(.flaroRowHeader) + .foregroundStyle(Color.flaroTextPrimary) + Image(systemName: "chevron.up.chevron.down") + .font(.flaroCaption) + .foregroundStyle(accent) + } + } + } + /// Eine Zeile — als Weg zur Folge oder als Kreuz zum Ankreuzen. @ViewBuilder private func row(for entry: MediaItem) -> some View {