Die Einstellungen verlassen die Leiste — oben rechts steht jetzt das Konto
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
19c9dc843c
commit
0aa11e1c25
@@ -0,0 +1,32 @@
|
||||
import SwiftUI
|
||||
import FlixyDesign
|
||||
|
||||
/// Der erste Buchstabe auf einer Scheibe.
|
||||
///
|
||||
/// **Kein Platzhalterbild.** Jellyfin gibt kein Profilbild heraus, ohne dass
|
||||
/// eines hinterlegt ist, und am Konto führt die App heute keines mit. Ein
|
||||
/// Buchstabe ist ehrlicher als eine graue Silhouette, die bei jedem gleich
|
||||
/// aussieht — und man erkennt an ihm wenigstens, mit welchem Konto man
|
||||
/// angemeldet ist.
|
||||
///
|
||||
/// Stand bis eben in `AccountBar` und wird jetzt zweimal gebraucht: dort in
|
||||
/// der Seitenleiste und auf dem iPhone oben rechts, wo seit dem Wegfall des
|
||||
/// Einstellungen-Tabs der Weg dorthin liegt.
|
||||
struct AccountAvatar: View {
|
||||
let name: String
|
||||
var diameter: CGFloat = 26
|
||||
|
||||
@Environment(\.flixyAccent) private var accent
|
||||
|
||||
var body: some View {
|
||||
Text(name.prefix(1).uppercased())
|
||||
// Mit der Scheibe wachsen, nicht fest: oben rechts auf dem
|
||||
// Telefon ist er grösser als in der Leiste, und ein Buchstabe in
|
||||
// Kleinschrift auf einer grossen Scheibe sieht aus wie ein
|
||||
// Fehler.
|
||||
.font(.system(size: diameter * 0.46, weight: .semibold))
|
||||
.foregroundStyle(Color.flixyCanvas)
|
||||
.frame(width: diameter, height: diameter)
|
||||
.background(accent, in: .circle)
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ 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
|
||||
|
||||
@@ -79,7 +78,7 @@ struct AccountBar: View {
|
||||
isExpanded.toggle()
|
||||
} label: {
|
||||
HStack(spacing: FlixySpacing.snug) {
|
||||
avatar
|
||||
AccountAvatar(name: title)
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(title)
|
||||
@@ -136,16 +135,4 @@ struct AccountBar: 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,12 @@ struct RootView: View {
|
||||
/// wäre er längst null.
|
||||
@State private var statusBarHeight: CGFloat = 0
|
||||
|
||||
/// Ob die Einstellungen gerade offen sind.
|
||||
///
|
||||
/// Nur auf dem iPhone: dort gibt es den Tab nicht mehr, und der Weg führt
|
||||
/// über das Monogramm oben rechts.
|
||||
@State private var showsSettings = false
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch session.phase {
|
||||
@@ -186,7 +192,11 @@ struct RootView: View {
|
||||
// Auswahl, die auf der anderen Seite nicht vorkommt, zeigte sonst
|
||||
// einen leeren Bereich.
|
||||
switch selection {
|
||||
case .library, .libraryHub, .liveTV: selection = .home
|
||||
// `.settings` gehört dazu, seit es den Tab auf dem iPhone nicht
|
||||
// mehr gibt: wer am iPad in den Einstellungen steht und das
|
||||
// Fenster schmal zieht, stünde sonst vor einem Bereich, den die
|
||||
// Leiste gar nicht führt.
|
||||
case .library, .libraryHub, .liveTV, .settings: selection = .home
|
||||
default: break
|
||||
}
|
||||
}
|
||||
@@ -315,12 +325,19 @@ struct RootView: View {
|
||||
}
|
||||
.customizationID("flixy.tab.downloads")
|
||||
|
||||
Tab(value: SidebarDestination.settings) {
|
||||
mediaStack { SettingsView() }
|
||||
} label: {
|
||||
tabLabel("sidebar.settings", symbol: "gearshape")
|
||||
// **Nicht auf dem iPhone.** Dort führt der Weg über das
|
||||
// Monogramm oben rechts — siehe `profileButton`. Einstellungen
|
||||
// sind nichts, was man im Wechsel mit der Mediathek benutzt; ein
|
||||
// Platz in der Leiste unten unterstellt genau das. In der
|
||||
// Seitenleiste bleibt der Eintrag, dort ist Platz.
|
||||
if sizeClass == .regular {
|
||||
Tab(value: SidebarDestination.settings) {
|
||||
mediaStack { SettingsView() }
|
||||
} label: {
|
||||
tabLabel("sidebar.settings", symbol: "gearshape")
|
||||
}
|
||||
.customizationID("flixy.tab.settings")
|
||||
}
|
||||
.customizationID("flixy.tab.settings")
|
||||
|
||||
// Die Suche nur dort, wo Platz für sie ist.
|
||||
//
|
||||
@@ -432,12 +449,30 @@ struct RootView: View {
|
||||
// Insel erreichen: Inhalt kam dort nie hin. Jetzt kommt er.
|
||||
.ignoresSafeArea(.container, edges: .top)
|
||||
} else {
|
||||
tabs
|
||||
tabs.overlay(alignment: .topTrailing) { profileButton }
|
||||
}
|
||||
#else
|
||||
tabs
|
||||
#endif
|
||||
}
|
||||
// **Als Blatt, nicht als Bereich.** `SettingsView` ist ein Formular
|
||||
// aus geschobenen Unterbildschirmen und braucht nur einen
|
||||
// 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.
|
||||
.sheet(isPresented: $showsSettings) {
|
||||
NavigationStack {
|
||||
SettingsView()
|
||||
// Ein Blatt lässt sich nach unten wegziehen — aber nicht
|
||||
// jeder weiss das, und wer in einem Unterbildschirm steht,
|
||||
// zieht damit gleich alles weg. Ein Knopf sagt es.
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .flixyTrailing) {
|
||||
Button("action.close") { showsSettings = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.miniPlayer(player, isVisible: playback.presentation == .mini)
|
||||
.onAppear { placement = storedPlacement }
|
||||
.onChange(of: placement) { _, new in
|
||||
@@ -450,6 +485,35 @@ struct RootView: View {
|
||||
.onChange(of: topInset, initial: true) { _, new in topChrome.inset = new }
|
||||
}
|
||||
|
||||
/// Der Weg in die Einstellungen auf dem iPhone.
|
||||
///
|
||||
/// **Ein Überzug, kein Werkzeugleisten-Knopf.** Die Startseite schaltet
|
||||
/// ihre Navigationsleiste ganz ab — sie war leer und belegte trotzdem
|
||||
/// Höhe —, ein `ToolbarItem` erschiene dort also gar nicht. Hier liegt er
|
||||
/// einmal über allen Bildschirmen statt dreimal in deren Leisten, und er
|
||||
/// sitzt überall an derselben Stelle.
|
||||
///
|
||||
/// Er blendet sich beim Scrollen aus wie die Insel auf dem iPad: über
|
||||
/// einem Film, den man gerade sucht, hat ein Kontoknopf nichts verloren.
|
||||
@ViewBuilder
|
||||
private var profileButton: some View {
|
||||
Button {
|
||||
showsSettings = true
|
||||
} label: {
|
||||
AccountAvatar(name: session.accountName ?? session.serverName, diameter: 32)
|
||||
.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)
|
||||
.opacity(topChrome.isRevealed ? 1 : 0)
|
||||
.allowsHitTesting(topChrome.isRevealed)
|
||||
.animation(.snappy(duration: 0.28), value: topChrome.isRevealed)
|
||||
.accessibilityLabel(Text("sidebar.settings"))
|
||||
}
|
||||
|
||||
/// Wie viel Platz die Leiste oben braucht.
|
||||
private static let topBarHeight: CGFloat = 52
|
||||
|
||||
|
||||
Reference in New Issue
Block a user