Die Schwelle aus den Einstellungen wirkt serverseitig, also sofort überall: `/home/continue` liest die gespeicherte Spalte. Eine Stelle blieb trotzdem widersprüchlich — die **Beschriftung**. Web und Apps entschieden „Weiterschauen" gegen „Abspielen" selbst, allein daran, ob eine Stelle über null steht. Nach einer Sekunde stand der Titel damit nicht mehr in der Reihe, hiess aber weiter „Weiterschauen", trug einen Fortschrittsbalken unter der Kachel und bot „Aus Weiterschauen entfernen" an. Der Server hatte diese Frage längst beantwortet. `ItemStateResponse` bringt sie jetzt mit; `MediaItem.isResumable` und `.resumeProgress` sind die einzigen Stellen, die sie noch stellen. Im Simulator nachgeprüft: nach einer Sekunde heisst der Knopf „Abspielen", „Von vorn" fehlt, und der Balken unter der Kachel ist weg. Kennt ein Server das Feld nicht — Jellyfin, oder ein älterer Flaro —, gilt wie früher die blosse Stelle. **Und das Profilbild steht in den Apps.** `FlaroAccount` führt es mit, `/auth/me` bringt es bei jedem Start frisch, und der Buchstabenkreis tritt zurück, sobald eines da ist. Jellyfin bleibt beim Buchstaben — dort gibt es kein Profilbild, und das ist keine Lücke, sondern der Stand der Dinge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
83 lines
3.2 KiB
Swift
83 lines
3.2 KiB
Swift
import SwiftUI
|
|
import FlaroDesign
|
|
|
|
/// Das eigene Bild auf einer Scheibe — oder der erste Buchstabe.
|
|
///
|
|
/// **Der Buchstabe ist keine Notlösung.** Jellyfin gibt kein Profilbild
|
|
/// heraus, und auch an einem Flaro-Konto muss keines hängen. 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.
|
|
///
|
|
/// Steht ein Bild im Konto, tritt es an seine Stelle. Lädt es nicht, kommt
|
|
/// der Buchstabe zurück: eine leere Scheibe sagt weniger als ein P.
|
|
///
|
|
/// Stand einmal in `AccountBar` und wird inzwischen dreimal gebraucht: dort in
|
|
/// der Seitenleiste, auf dem iPhone oben rechts und in der Werkzeugleiste.
|
|
struct AccountAvatar: View {
|
|
let name: String
|
|
var diameter: CGFloat = 26
|
|
/// Das Bild des Kontos, falls eines hinterlegt ist.
|
|
var image: URL?
|
|
|
|
@Environment(\.flaroAccent) private var accent
|
|
|
|
var body: some View {
|
|
buchstabe
|
|
.frame(width: diameter, height: diameter)
|
|
.background(accent, in: .circle)
|
|
.overlay {
|
|
if let image {
|
|
AsyncImage(url: image) { bild in
|
|
bild.resizable().scaledToFill()
|
|
} placeholder: {
|
|
// Nichts: darunter liegt der Buchstabe und füllt die
|
|
// Lücke, bis das Bild da ist.
|
|
Color.clear
|
|
}
|
|
.frame(width: diameter, height: diameter)
|
|
.clipShape(.circle)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var buchstabe: 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.flaroCanvas)
|
|
}
|
|
}
|
|
|
|
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 flaroOpenSettings: () -> 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(FlaroSession.self) private var session
|
|
@Environment(\.flaroOpenSettings) private var openSettings
|
|
|
|
var body: some View {
|
|
Button(action: openSettings) {
|
|
AccountAvatar(name: session.accountName ?? session.serverName, diameter: 34,
|
|
image: session.accountAvatarURL)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityLabel(Text("sidebar.settings"))
|
|
}
|
|
}
|