Am Fernseher steht der Code vorn, nicht das Passwort

Schritt drei: anmelden. Die Oberfläche ist neu, das Modell dahinter ist
dasselbe wie am Telefon — `JellyfinSignInModel`, unverändert bis auf
einen Wert im Initialisierer.

**Die eine Verhaltensänderung.** Nach gefundenem Server sprang der Weg
bisher immer zu Benutzername und Passwort, mit der Begründung, ein
Passwortspeicher fülle beide Felder in einer Sekunde. Vor einem
Fernseher stimmt daran kein Wort: es gibt keinen Passwortspeicher, man
tippt mit dem Steuerkreuz, und das Gerät, auf dem man den Code
bestätigt, liegt sowieso auf dem Sofa. Also `preferring: .quickConnect`
— ein Wert im Initialisierer und keine Plattformbedingung mitten in
`findServer()`. Welcher Fall gilt, weiß die Oberfläche; das Modell
nicht. Der Vorgabewert hält iOS unverändert.

Der sechsstellige Code steht in 96 Punkt mit Sperrung: er wird aus drei
Metern abgelesen und in ein Telefon getippt. In Fliesstextgrösse wäre
das eine Zumutung. Beide Wege bleiben erreichbar — für Server, auf denen
Quick Connect abgeschaltet ist, und für den Fall, dass gerade kein
zweites Gerät zur Hand ist.

**Und der Beweis, dass die geteilte Schicht trägt.** Gegen die
Jellyfin-Attrappe im Simulator: die App verbindet sich, lädt die drei
Mediatheken und zeigt sie. FlixySession, JellyfinBackend, die
Zustandsmaschine der Anmeldung — alles unverändert, alles über
Pfadeinträge geteilt, keine Zeile kopiert.

Die Startseite ist noch eine Liste von Namen. Sie kommt in Schritt vier;
hier steht sie, damit die Wurzel vollständig ist und die Anmeldung für
sich prüfbar bleibt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-29 17:56:50 +02:00
co-authored by Claude Opus 5
parent 22f824b000
commit 1552e5f2b9
4 changed files with 312 additions and 19 deletions
+32 -10
View File
@@ -32,6 +32,16 @@ final class JellyfinSignInModel {
case signingIn
}
/// Welcher Weg nach dem Fund zuerst angeboten wird.
///
/// **Am Telefon und am Fernseher verschieden herum.** Dort füllt ein
/// Passwortspeicher beide Felder in einer Sekunde; hier tippt man sie mit
/// einem Steuerkreuz, Buchstabe für Buchstabe, und der Code ist der
/// bequemere Weg. Welcher Fall gilt, weiß die Oberfläche — dieses Modell
/// nicht. Deshalb ein Wert im Initialisierer statt einer
/// Plattformbedingung mitten in `findServer()`.
enum PreferredMethod { case credentials, quickConnect }
var address = ""
var userName = ""
var password = ""
@@ -51,12 +61,15 @@ final class JellyfinSignInModel {
var onAuthenticated: ((JellyfinClient, JellyfinAccount, String) -> Void)?
private let identity: JellyfinIdentity
private let preferred: PreferredMethod
private var client: JellyfinClient?
private var authenticator: JellyfinAuthenticator?
private var pollTask: Task<Void, Never>?
init(identity: JellyfinIdentity) {
/// Der Vorgabewert hält iOS unverändert.
init(identity: JellyfinIdentity, preferring: PreferredMethod = .credentials) {
self.identity = identity
self.preferred = preferring
}
// MARK: - Schritt 1: den Server finden
@@ -89,17 +102,26 @@ final class JellyfinSignInModel {
address = candidate.absoluteString
offersQuickConnect = await authenticator.supportsQuickConnect()
// **Name und Passwort zuerst.**
// **Am Telefon Name und Passwort zuerst, am Fernseher der Code.**
//
// Hier sprang der Weg bisher von selbst in den Code-Fluss, sobald
// der Server ihn anbietet. Das war gut gemeint — dabei läuft kein
// Passwort durch diese App —, geht aber am Alltag vorbei: der Code
// muss in einem zweiten Gerät bestätigt werden, und wer gerade im
// Zug sitzt, hat das nicht offen. Ein Passwortspeicher füllt
// dagegen beide Felder in einer Sekunde.
// Hier sprang der Weg früher immer von selbst in den Code-Fluss,
// sobald der Server ihn anbietet. Das war gut gemeint — dabei
// läuft kein Passwort durch diese App —, geht am Telefon aber am
// Alltag vorbei: der Code muss in einem zweiten Gerät bestätigt
// werden, und wer gerade im Zug sitzt, hat das nicht offen. Ein
// Passwortspeicher füllt dagegen beide Felder in einer Sekunde.
//
// Der Code bleibt erreichbar: `offersQuickConnect` steuert den
// Knopf darunter, und der steht schon da.
// Vor einem Fernseher stimmt jedes Wort davon nicht mehr: dort
// gibt es keinen Passwortspeicher, dort tippt man mit dem
// Steuerkreuz, und das zweite Gerät liegt sowieso auf dem Sofa.
//
// Beide Wege bleiben erreichbar: `offersQuickConnect` steuert den
// Knopf darunter, und `useCredentials()` führt zurück.
if preferred == .quickConnect, offersQuickConnect {
await beginQuickConnect()
return
}
step = .credentials
return
}