Die Bibliothek beginnt jetzt in einer Mediathek

Vorher stand dort eine Liste aller Mediatheken: auf „Bibliothek" tippen,
dann auf „Filme", dann endlich ein Raster. In eine andere wechseln hiess
zurück und wieder hinein. Der Zwischenbildschirm hat nie etwas erzählt,
was eine Leiste nicht auch zeigen kann — er war ein Platzproblem, kein
Entwurf.

Jetzt steht man sofort in einer Mediathek, und darüber der unteren Leiste
liegt eine zweite mit den Mediatheken und Live-TV. Auf dem iPad und am Mac
ändert sich nichts: dort steht jede Mediathek einzeln in der Seitenleiste,
auf dem Fernseher nebeneinander in der oberen Leiste — es ist dieselbe
Idee in der Form, die ein Telefon hergibt.

**Warum eine zweite Leiste und nicht Plätze in der unteren.** Die hat
fünf, und alles darüber schiebt das System in ein „Mehr"-Verzeichnis,
dessen Einträge zwei Navigationsleisten übereinander tragen. Genau daran
ist der Zwischenbildschirm seinerzeit entstanden. Eine selbst gezeichnete
Leiste kennt diese Grenze nicht — und sie sieht aus wie die Insel auf dem
iPad, weil sie dieselbe Fläche benutzt: `FlixySidebar.surface(in:)` ist
ein Bauteil, kein nachgebautes Aussehen, und wird jetzt zum dritten Mal
gerufen.

Als Sicherheitsrand und nicht als Überzug, damit die Liste über der
Leiste endet statt dahinter zu verschwinden. Bei mehr als vier
Mediatheken wird sie waagerecht scrollbar.

**Das Monogramm steht jetzt in der Werkzeugleiste**, am rechten Ende, auf
derselben Höhe wie Suche, Filter und Ansicht — die rücken dafür nach
links. Als Überzug lag es über dem Inhalt, auf einem Raster also über
einem Plakat. Dass es dabei an den Anfang der Reihe rutschte, hat einen
Grund, der im Quelltext steht: SwiftUI reiht Werkzeugleisten von aussen
nach innen, ein von aussen angehängter Eintrag steht deshalb **vor** den
übrigen. Wo in der Reihe etwas steht, kann nur der entscheiden, der die
Reihe schreibt. Auf der Startseite bleibt es ein Überzug — dort gibt es
keine Navigationsleiste.

Dazu: die Zuordnung Mediathekenart → Symbol stand dreimal im Projekt, eine
davon falsch (jede Mediathek bekam „film", auch eine mit Serien). Jetzt
einmal, neben den übrigen Ableitungen aus den Modellen.

Geprüft im Simulator: „Bibliothek" öffnet sofort das Raster, die Leiste
wechselt zwischen Filme, Serien, Gemischtes und Live-TV, und das
Monogramm steht ganz rechts in der Werkzeugleiste.

iOS und tvOS bauen, 469 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-01 09:40:00 +02:00
co-authored by Claude Opus 5
parent 0aa11e1c25
commit 7f380e5f4d
7 changed files with 244 additions and 76 deletions
@@ -30,3 +30,32 @@ struct AccountAvatar: View {
.background(accent, in: .circle)
}
}
extension EnvironmentValues {
/// Öffnet die Einstellungen.
///
/// **Über die Umgebung, weil der Knopf an mehreren Stellen steht.** Das
/// Blatt gehört der Wurzel — nur sie überlebt den Wechsel des Bereichs.
/// Jeder Bildschirm, der den Knopf zeigt, müsste den Weg dorthin sonst
/// als Argument durchgereicht bekommen.
@Entry var flixyOpenSettings: () -> Void = {}
}
/// Das Monogramm als Knopf für eine Werkzeugleiste.
///
/// **Als Ansicht und nicht als Modifikator.** Ein Modifikator hängt seine
/// Einträge aussen an, und SwiftUI reiht aussen zuerst — das Monogramm stand
/// damit **vor** Suche, Filter und Ansicht statt dahinter. Wo in der Reihe es
/// steht, kann nur der entscheiden, der die Reihe schreibt.
struct AccountToolbarButton: View {
@Environment(FlixySession.self) private var session
@Environment(\.flixyOpenSettings) private var openSettings
var body: some View {
Button(action: openSettings) {
AccountAvatar(name: session.accountName ?? session.serverName, diameter: 34)
}
.buttonStyle(.plain)
.accessibilityLabel(Text("sidebar.settings"))
}
}
+18 -13
View File
@@ -275,6 +275,14 @@ struct RootView: View {
TabView(selection: $selection) {
Tab("sidebar.home", systemImage: "house", value: SidebarDestination.home) {
mediaStack { HomeView() }
// **Nur hier.** Die Startseite hat keine
// Navigationsleiste — sie war leer und belegte trotzdem
// Höhe —, also ist ein Überzug der einzige Platz. Die
// übrigen Bildschirme haben eine, und dort stünde er
// entweder über deren Knöpfen oder mitten im Inhalt. Wer
// zu den Einstellungen will, geht auf Start; das ist ein
// Schritt und kein Umweg.
.overlay(alignment: .topTrailing) { profileButton }
}
.customizationID("flixy.tab.home")
@@ -292,7 +300,7 @@ struct RootView: View {
) {
mediaStack { LibraryView(library: library) }
} label: {
sidebarLabel(Text(library.title), symbol: symbol(for: library.kind))
sidebarLabel(Text(library.title), symbol: library.kind.symbol)
}
.customizationID("flixy.tab.library.\(library.id)")
}
@@ -311,7 +319,7 @@ struct RootView: View {
}
} else {
Tab(value: SidebarDestination.libraryHub) {
mediaStack { LibraryHubView() }
mediaStack { LibraryBrowser() }
} label: {
tabLabel("sidebar.library", symbol: "books.vertical")
}
@@ -449,7 +457,7 @@ struct RootView: View {
// Insel erreichen: Inhalt kam dort nie hin. Jetzt kommt er.
.ignoresSafeArea(.container, edges: .top)
} else {
tabs.overlay(alignment: .topTrailing) { profileButton }
tabs
}
#else
tabs
@@ -460,6 +468,7 @@ struct RootView: View {
// Navigationsstapel um sich — die Medienziele aus `mediaStack` ruft
// es nie auf. Und beim Schliessen steht man wieder dort, wo man war,
// statt in einem Bereich, den man erst wieder verlassen muss.
.environment(\.flixyOpenSettings, { showsSettings = true })
.sheet(isPresented: $showsSettings) {
NavigationStack {
SettingsView()
@@ -500,14 +509,17 @@ struct RootView: View {
Button {
showsSettings = true
} label: {
AccountAvatar(name: session.accountName ?? session.serverName, diameter: 32)
AccountAvatar(name: session.accountName ?? session.serverName, diameter: 34)
.padding(FlixySpacing.snug)
// Damit er auch über einem hellen Aufmacher steht.
.background(.black.opacity(0.28), in: .circle)
}
.buttonStyle(.plain)
.padding(.trailing, FlixySpacing.regular)
.padding(.top, statusBarHeight + FlixySpacing.snug)
// **Ohne die Statusleiste.** Auf dem iPhone sitzt der Baum innerhalb
// des Sicherheitsrands; sie noch einmal dazuzurechnen schöbe den
// Knopf um ihre Höhe nach unten, mitten ins Bild.
.padding(.top, FlixySpacing.snug)
.opacity(topChrome.isRevealed ? 1 : 0)
.allowsHitTesting(topChrome.isRevealed)
.animation(.snappy(duration: 0.28), value: topChrome.isRevealed)
@@ -543,7 +555,7 @@ struct RootView: View {
NavigationEntry(
destination: .library(key: library.id, title: library.title),
title: Text(library.title),
symbol: symbol(for: library.kind),
symbol: library.kind.symbol,
plainTitle: library.title
)
}
@@ -631,11 +643,4 @@ struct RootView: View {
#endif
}
private func symbol(for kind: MediaLibrary.Kind) -> String {
switch kind {
case .movie: "film"
case .show: "tv"
default: "rectangle.stack"
}
}
}