Files
Flaro/Flixy/Views/Navigation/AccountBar.swift
T
ScarriffleandClaude Opus 5 95723588c2 Nachgesehen — und dabei vier Fehler gefunden, zwei davon meine neuen
Der Simulator läuft wieder. Ich habe alles durchgeklickt, was ich dir in den
letzten beiden Runden ungeprüft gegeben habe. Es war nicht in Ordnung.

**Zwei Navigationsleisten übereinander.** Auf dem iPhone hat die Leiste unten
fünf Plätze; alles darüber schiebt das System in ein „Mehr"-Verzeichnis — und
dessen Einträge bekamen zwei Zurück-Pfeile, einen vom System und einen von
uns. Statt das zu flicken, gibt es jetzt die **Bibliothek**, die du ohnehin
wolltest: auf dem iPhone ein Eintrag, hinter dem alle Mediatheken stehen, auf
dem iPad weiter jede einzeln in der Seitenleiste. Damit bleibt die Leiste bei
fünf Einträgen, und das „Mehr" entfällt.

**Kacheln in der Mediathek öffneten nichts.** Kein Fehler, keine Meldung —
Antippen führte einfach ins Leere. Der Grund: in einem Stapel dürfen nicht
beide Arten von Verknüpfung nebeneinander vorkommen. Sobald ein Bildschirm
über eine **Ansichts**-Verknüpfung geschoben wurde, fanden die
**Wert**-Verknüpfungen darin ihr Ziel nicht mehr. Jetzt ist alles wertbasiert,
und das Ziel für Detailseiten steht **einmal je Stapel** an dessen Wurzel statt
in jedem Bildschirm einzeln.

**Der Lupenknopf in der Kopfzeile zeigte auf ein Ziel, das es nie gab.** Kein
Bildschirm hat je ein `SearchRoute`-Ziel angemeldet — der Knopf tat seit jeher
nichts. Er ist weg; die Suche hat ihren eigenen Platz in der Leiste.

**Der Download-Dialog zeigte Schlüssel statt Namen** — „quality.hd" statt
„720p". Die drei Übersetzungen fehlten schlicht.

**Die Kontokarte wuchs aus dem Bild heraus.** Die Leiste unten in der
Seitenleiste hat eine feste Höhe; meine aufklappende Karte schob die Zeile mit
dem Namen nach unten hinaus. Jetzt hängt sie als Popover darüber — die Zeile
bleibt stehen, wie bei JellyTV.

Verkleinerte Downloads bei Jellyfin laufen: Stufen erscheinen, die Adresse
stimmt, die Datei kommt an. Dass die Attrappe nur 28 kB liefert und die App
das als „keine Videodatei" zurückweist, ist richtig so — die Prüfung tut, was
sie soll.

Zwei Gruben in der Attrappe nebenbei gestopft: sie kannte `/stream.mp4` nicht
und meldete ihr Testbild als 360p, womit es gar keine kleineren Stufen geben
konnte.

263 Tests grün. Diesmal alles angesehen: iPhone und iPad, Einstellungen,
Bibliothek, Detailseite, Download-Dialog, Kontokarte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 15:22:18 +02:00

149 lines
5.4 KiB
Swift

import SwiftUI
import FlixyDesign
/// Wer angemeldet ist — unten in der Seitenleiste.
///
/// Der Platz ist nicht zufällig: dort sucht man sein Konto, weil es in jeder
/// App der letzte Eintrag ist. Und von dort führt der Weg in die
/// Einstellungen, statt dass sie als gleichrangiger Eintrag zwischen den
/// Mediatheken stehen.
///
/// **Eine Karte, die über der Zeile aufgeht**, kein Systemmenü: mit denselben
/// Abständen und derselben Schrift wie der Rest der App, statt der grauen
/// Standardliste.
///
/// Sie hängt als Popover an der Zeile und wächst nicht in der Leiste selbst.
/// Das war der erste Versuch, und er ging schief: die Leiste unten hat eine
/// feste Höhe, die Karte wuchs nach unten aus dem Bild heraus, und die Zeile
/// mit dem Namen verschwand dabei.
///
/// Auf dem iPhone gibt es diese Leiste nicht; dort haben die Einstellungen
/// ihren eigenen Eintrag.
struct AccountBar: View {
@Binding var selection: SidebarDestination
@Environment(FlixySession.self) private var session
@Environment(\.flixyAccent) private var accent
@State private var isExpanded = false
@State private var showsSignOutConfirmation = false
private var title: String {
session.accountName ?? session.serverName
}
/// Der Server steht nur dann darunter, wenn oben schon der Kontoname
/// steht — sonst stünde zweimal dasselbe.
private var subtitle: String? {
session.accountName == nil ? nil : session.serverName
}
var body: some View {
accountRow
.popover(isPresented: $isExpanded, arrowEdge: .bottom) {
VStack(spacing: 0) {
action("account.settings", symbol: "gearshape") {
isExpanded = false
selection = .settings
}
separator
action(
"action.signOut",
symbol: "rectangle.portrait.and.arrow.right",
isDestructive: true
) {
isExpanded = false
showsSignOutConfirmation = true
}
}
.frame(minWidth: 240)
.background(Color.flixySurface)
// Auf dem iPhone gäbe es diese Leiste gar nicht; die Angabe
// steht trotzdem da, damit aus dem Popover dort kein
// bildschirmfüllendes Blatt wird.
.presentationCompactAdaptation(.popover)
}
.confirmationDialog(
Text("settings.signOutConfirm"),
isPresented: $showsSignOutConfirmation,
titleVisibility: .visible
) {
Button("action.signOut", role: .destructive) { session.signOut() }
Button("action.cancel", role: .cancel) {}
} message: {
Text("settings.signOutExplanation")
}
}
private var accountRow: some View {
Button {
isExpanded.toggle()
} label: {
HStack(spacing: FlixySpacing.snug) {
avatar
VStack(alignment: .leading, spacing: 0) {
Text(title)
.font(.flixyCardTitle)
.foregroundStyle(Color.flixyTextPrimary)
.lineLimit(1)
if let subtitle {
Text(subtitle)
.font(.flixyCaption)
.foregroundStyle(Color.flixyTextMuted)
.lineLimit(1)
}
}
Spacer(minLength: FlixySpacing.snug)
// Zeigt nach oben: dorthin geht die Karte auf.
Image(systemName: "chevron.up")
.font(.caption2.weight(.semibold))
.foregroundStyle(Color.flixyTextMuted)
}
.padding(.horizontal, FlixySpacing.regular)
.padding(.vertical, FlixySpacing.snug)
.contentShape(.rect)
}
.buttonStyle(.plain)
.accessibilityLabel(Text(title))
.accessibilityHint(Text("account.hint"))
}
private func action(
_ title: LocalizedStringKey,
symbol: String,
isDestructive: Bool = false,
perform: @escaping () -> Void
) -> some View {
Button(action: perform) {
Label(title, systemImage: symbol)
.font(.flixyBody)
.foregroundStyle(isDestructive ? Color.flixyDestructive : Color.flixyTextPrimary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, FlixySpacing.regular)
.padding(.vertical, FlixySpacing.regular)
.contentShape(.rect)
}
.buttonStyle(.plain)
}
private var separator: some View {
Divider().overlay(Color.flixySeparator)
}
/// Der erste Buchstabe auf einer Scheibe.
///
/// Jellyfin gibt kein Profilbild heraus, ohne dass eines hinterlegt ist,
/// und Plex kennt den Kontonamen am Server gar nicht. Ein Buchstabe ist
/// ehrlicher als ein graues Platzhalterbild.
private var avatar: some View {
Text(title.prefix(1).uppercased())
.font(.flixyCaption.weight(.semibold))
.foregroundStyle(Color.flixyCanvas)
.frame(width: 26, height: 26)
.background(accent, in: .circle)
}
}