Mediatheken nach oben — und das Regal sagt endlich, was es sieht

**Die Mediatheken sind Reiter geworden.** Sie standen unten auf der
Startseite, und dorthin musste man erst scrollen — an allen Vorschlägen
vorbei, obwohl „ich will in die Filme" genau der Fall ist, in dem einem
die Vorschläge egal sind. Damit fällt die Reihe unten weg: zwei Wege zum
selben Raster sind auf einem Fernseher kein Komfort, sondern eine Frage,
welcher der richtige ist.

Die Leiste liest sich jetzt „Start · Filme · Serien · Gemischtes ·
Suchen" und wird von tvOS mittig gehalten, egal wie viele Mediatheken
dazukommen. Die Stelle der Suche bestimmt Apple, nicht diese Datei:
`role: .search` setzt sie dorthin, wo sie in jeder anderen App auch
steht, und gibt ihr die Lupe.

**Und eine Statuszeile fürs Regal.** Bleibt es leer, gibt es genau drei
Möglichkeiten: die App-Gruppe fehlt, die Datei ist nicht da, oder sie ist
da und das System fragt trotzdem nicht. Von aussen sehen alle drei gleich
aus — nämlich nach dem grossen Plakat aus dem Asset-Katalog. Nach zwei
Runden Vermuten steht unten links jetzt, welcher Fall es ist: „3 Reihen,
9 Einträge, zuletzt 21:55" oder eben „Keine App-Gruppe".

Nur im Entwicklerbau (`#if DEBUG`) und als Überlagerung, nicht als letzte
Zeile der Liste: dorthin müsste man erst an allen Reihen vorbeiscrollen,
und wer nachsieht, warum das Regal leer ist, soll es sofort sehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-29 21:56:31 +02:00
co-authored by Claude Opus 5
parent 08514eb564
commit 41719595b9
4 changed files with 162 additions and 51 deletions
+24 -37
View File
@@ -38,9 +38,6 @@ struct TVHomeView: View {
.navigationDestination(for: MediaRoute.self) { route in
TVDetailView(route: route)
}
.navigationDestination(for: LibraryRoute.self) { route in
TVLibraryView(libraryID: route.id)
}
}
.task(id: session.serverName) { await feed.load(from: session.backend) }
.onChange(of: deepLinkTarget) { _, neu in
@@ -96,10 +93,6 @@ struct TVHomeView: View {
onFocus: { aufmacher = $0 }
)
}
if !session.libraries.isEmpty {
mediatheken
}
}
.padding(.bottom, FlixySpacing.screenMargin)
}
@@ -108,40 +101,34 @@ struct TVHomeView: View {
// Aufmacher muss bis an die Bildschirmkante reichen; ein Bild, das
// achtzig Punkte davor aufhört, sieht nach Fehler aus.
.ignoresSafeArea()
// Als Überlagerung und nicht als letzte Zeile der Liste: dorthin
// müsste man erst an allen Reihen vorbeiscrollen, und wer nachsieht,
// warum das Regal leer ist, soll es sofort sehen.
.overlay(alignment: .bottom) { regalStatus }
}
/// Die Mediatheken als eigene Reihe.
/// Was das Regal über der App zuletzt bekommen hat.
///
/// Unten und nicht oben: wer den Fernseher einschaltet, will meistens
/// weiterschauen, nicht stöbern. Und als Reihe statt als Registerkarten,
/// weil eine Reihe am Fokus hängt und Registerkarten eine zweite Bedienart
/// wären.
private var mediatheken: some View {
VStack(alignment: .leading, spacing: FlixySpacing.regular) {
Text("library.title")
.font(.flixyRowHeader)
.foregroundStyle(Color.flixyTextPrimary)
.padding(.horizontal, FlixySpacing.screenMargin)
ScrollView(.horizontal) {
LazyHStack(spacing: FlixySpacing.regular) {
ForEach(session.libraries, id: \.id) { mediathek in
Button {
pfad.append(LibraryRoute(id: mediathek.id))
} label: {
Text(mediathek.title)
.font(.flixyRowHeader)
.frame(width: 360, height: 180)
}
.buttonStyle(.card)
}
}
.padding(.horizontal, FlixySpacing.screenMargin)
.padding(.vertical, FlixySpacing.regular)
}
.scrollClipDisabled()
.scrollIndicators(.never)
/// **Nur im Entwicklerbau.** Es gehört nicht in die fertige App — aber
/// solange das Regal leer bleibt, sieht man von aussen nicht, woran es
/// liegt: fehlende App-Gruppe, fehlende Datei und ein System, das nicht
/// fragt, sehen alle gleich aus. Diese Zeile unterscheidet sie.
@ViewBuilder
private var regalStatus: some View {
#if DEBUG
VStack(alignment: .leading, spacing: FlixySpacing.tight) {
Text("topShelf.title")
.font(.flixyCaption)
.foregroundStyle(Color.flixyTextMuted)
Text(TopShelfWriter.status)
.font(.flixyCaption)
.foregroundStyle(Color.flixyTextSecondary)
}
.padding(.horizontal, FlixySpacing.screenMargin)
.padding(.bottom, FlixySpacing.regular)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.black.opacity(0.6))
#endif
}
// MARK: - Was ein Druck auslöst