Auf dem iPhone belegten die Einstellungen einen von fünf Plätzen in der unteren Leiste, gleichrangig neben Start und Bibliothek. Das unterstellt, dass man zwischen ihnen hin und her wechselt, und das tut niemand. Jetzt stehen dort drei Einträge — Start, Bibliothek, Downloads — und der Weg in die Einstellungen führt über das Monogramm oben rechts. Auf dem iPad und am Mac bleibt alles, wie es war: dort hat die Seitenleiste Platz, und den Weg über das Konto gibt es ohnehin schon. Drei Entscheidungen, die nicht offensichtlich sind: **Ein Überzug, kein Werkzeugleisten-Knopf.** Die Startseite schaltet ihre Navigationsleiste ganz ab — sie war leer und belegte trotzdem Höhe —, ein `ToolbarItem` erschiene dort gar nicht. Der Knopf liegt deshalb einmal über allen Bildschirmen statt dreimal in deren Leisten und sitzt überall an derselben Stelle. Beim Scrollen blendet er sich aus, wie die Insel auf dem iPad: über einem Film, den man gerade sucht, hat ein Kontoknopf nichts verloren. **Ein Blatt, kein Bereich.** `SettingsView` ist ein Formular aus geschobenen Unterbildschirmen und braucht nur einen Navigationsstapel um sich; die Medienziele aus `mediaStack` ruft es nie auf. Beim Schliessen steht man wieder dort, wo man war. **Ein Buchstabe, kein Bild.** Den Avatar gab es schon — er steckte in der Kontozeile der Seitenleiste und wird jetzt von beiden benutzt. Ein Serverbild führt das Konto heute nirgends mit, weder bei Jellyfin noch bei Plex; eine graue Silhouette, die bei jedem gleich aussieht, sagt weniger als der Anfangsbuchstabe. Dazu gehört `.settings` in die Rückfallliste beim Grössenwechsel: wer am iPad in den Einstellungen steht und das Fenster schmal zieht, stünde sonst vor einem Bereich, den die Leiste gar nicht mehr führt. Geprüft im Simulator: drei Einträge unten, Monogramm oben rechts, Tippen öffnet die Einstellungen, Schliessen führt zurück. iOS und tvOS bauen, 469 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
139 lines
5.0 KiB
Swift
139 lines
5.0 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
|
|
@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 Sheet 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) {
|
|
AccountAvatar(name: title)
|
|
|
|
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)
|
|
}
|
|
}
|
|
// Ohne Vorrang staucht SwiftUI den Text vor dem Zwischenraum:
|
|
// in einer schmalen Leiste stand vom Namen nur noch „S…" da.
|
|
.layoutPriority(1)
|
|
|
|
Spacer(minLength: 0)
|
|
|
|
// 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)
|
|
}
|
|
|
|
}
|