Files
Flaro/Flixy/Views/Search/SearchView.swift
T
ScarriffleandClaude Opus 5 ad556c9034 Ein Knopf an jeder Folge, und die Suche spielt nicht mehr sofort ab
**Staffelansicht: Download je Folge.** Für den Fall, den du beschrieben
hast — Staffel halb gesehen, Rest nachholen. Ein Tipp öffnet dieselbe
Qualitätsauswahl wie auf der Detailseite.

Der Knopf steht **neben** dem Link, nicht darin: ein `Button` im Label
eines `NavigationLink` löst auf iOS nie aus, und `.plain` lässt das
lautlos geschehen. Dieselbe Falle steht in `QuickPlayTile` schon
beschrieben. Als Geschwister in einer Reihe teilt das Layout den Platz
auf, und der Titel kann gar nicht erst unter dem Knopf verschwinden.

Die Kinderliste trägt keine Dateiangaben — ohne Nachladen wäre die
Qualitätsliste leer und die Prüfung auf freien Speicher übersprungen.
`DownloadButton` holt sie deshalb beim ersten Tipp nach (`loadsDetails`),
mit Kreisel und fester Breite, damit die Zeile nicht springt. Nicht beim
Erscheinen: zwanzig Zeilen wären zwanzig Anfragen für einen Knopf, den man
meistens nicht drückt.

Nur bei Folgen. Für eine ganze Staffel gibt es den Knopf oben schon.

**Suche: Antippen führt auf die Seite.** Die Suche baut dieselbe Reihe wie
die Startseite und war damit auch dieselbe Geste. Wer sucht, weiß aber
noch nicht, was er sehen will — der erste Treffer ist eine Vermutung des
Servers. Abspielen steht weiter im Menü unter langem Drücken.

Dabei aufgefallen: `SearchView` hinterlegte `flixyItemChanged` nicht,
anders als Startseite und Mediathek. „Als gesehen markieren" wirkte dort
am Server, aber die Kachel lud nie neu — es sah aus, als wäre nichts
passiert.

Im Simulator geprüft: Knopf an jeder Folge, Auswahl mit echten Größen
(„Original · HD · 906,9 MB"), Suchtreffer öffnet die Detailseite. iOS und
tvOS bauen, 489 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 21:05:49 +02:00

93 lines
3.5 KiB
Swift

import SwiftUI
import FlixyDesign
import MediaKit
/// Sucht über alle Mediatheken des Servers.
///
/// **Nur noch Darstellung.** Entprellen, fragen und merken liegen in
/// `SearchFeed`, weil der Fernseher dieselben Fragen an denselben Server
/// stellt. Siehe dort, warum das kein Beiwerk ist.
struct SearchView: View {
@Environment(FlixySession.self) private var session
@State private var text = ""
@State private var feed = SearchFeed()
var body: some View {
Group {
if text.isEmpty {
recentSearches
} else if feed.hubs.isEmpty && !feed.isSearching {
ContentUnavailableView.search(text: text)
} else {
results
}
}
.background(Color.flixyCanvas)
.navigationTitle(Text("search.title"))
.flixyInlineTitle()
.searchable(text: $text)
// Jeder Tastendruck löst eine Anfrage aus — aber erst nach einer
// kurzen Pause. Ohne sie schickt „Blade Runner" zwölf Anfragen los,
// von denen elf verworfen werden.
.task(id: text) { await feed.search(text, from: session.backend) }
// **Hier fehlte er.** Startseite und Mediathek hinterlegen das längst;
// in der Suche nicht, und deshalb wirkte „als gesehen markieren" aus
// dem Kontextmenü zwar am Server, aber die Kachel lud nie neu — es sah
// aus, als wäre nichts passiert.
.environment(\.flixyItemChanged, { await feed.search(text, from: session.backend) })
}
private var results: some View {
ScrollView {
LazyVStack(alignment: .leading, spacing: FlixySpacing.section) {
ForEach(feed.hubs) { hub in
// **Antippen führt auf die Seite, nicht in den Film.** Die
// Suche benutzt dieselbe Reihe wie die Startseite — und
// damit war sie bisher auch dieselbe Geste. Wer sucht,
// weiß aber noch nicht, was er sehen will; der erste
// Treffer ist eine Vermutung des Servers. Starten geht
// weiter, über langes Drücken.
MediaRow(row: hub, opensDetails: true)
}
}
.padding(.vertical, FlixySpacing.loose)
}
.flixyRevealsTopChrome()
}
@ViewBuilder
private var recentSearches: some View {
if feed.recent.isEmpty {
ContentUnavailableView {
Label("search.title", systemImage: "magnifyingglass")
} description: {
Text("search.hint")
}
} else {
List {
Section {
ForEach(feed.recent, id: \.self) { entry in
Button {
text = entry
} label: {
Label(entry, systemImage: "clock.arrow.circlepath")
.foregroundStyle(Color.flixyTextSecondary)
}
}
} header: {
Text("search.recent")
} footer: {
Button("search.forgetRecent", role: .destructive) {
feed.forgetRecent()
}
.font(.flixyCaption)
}
.listRowBackground(Color.flixySurface)
}
.scrollContentBackground(.hidden)
.flixyRevealsTopChrome()
}
}
}