Files
certain/ios/Anmeldung/ServerAnsicht.swift
T
ScarriffleandClaude Opus 5 6b86a6bf91 feat: Erste Stufe der iOS-App - Anmeldung und Kontenliste
Neuer Ordner ios/ mit dem Geruest: Netzwerkschicht, Modelle, Offline-Ablage,
Texte in Deutsch und Englisch, Navigation, der komplette Anmeldeweg (Server
waehlen, anmelden, zweiter Faktor, Pflicht-Passwortwechsel) und als erste
Fachansicht die Kontenliste.

Die Anmeldung laeuft einmal ueber das Sitzungs-Cookie, holt sich dabei eine
App-Marke und legt sie in den Schluesselbund. Danach traegt nur noch der
Header. Kein Passwort auf dem Geraet, kein Ablauf nach 30 Tagen.

Der Code ist nie uebersetzt worden - hier gibt es kein Xcode. Geprueft ist
dafuer jedes Modellfeld gegen echte Antworten des laufenden Backends; dabei
kam heraus, dass created_at ohne Zeitzone und mit Mikrosekunden kommt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-20 16:20:03 +02:00

59 lines
2.1 KiB
Swift

import SwiftUI
/// Der allererste Bildschirm: Wo steht dein Certain?
///
/// Die Adresse wird nicht blind übernommen, sondern mit `GET /api/version`
/// geprüft. Das erspart die Sorte Fehlersuche, bei der man sich eine Stunde
/// fragt, warum die Anmeldung nicht geht, und am Ende ein Buchstabe fehlte.
struct ServerAnsicht: View {
@Environment(AppZustand.self) private var zustand
@State private var eingabe = ""
@FocusState private var imFeld: Bool
var body: some View {
NavigationStack {
Form {
Section {
TextField(String(localized: "server.platzhalter"), text: $eingabe)
.textContentType(.URL)
.keyboardType(.URL)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.focused($imFeld)
.submitLabel(.go)
.onSubmit { pruefe() }
} header: {
Text(String(localized: "server.ueberschrift"))
} footer: {
Text(String(localized: "server.erklaerung"))
}
if let meldung = zustand.meldung {
Section {
Label(meldung, systemImage: "exclamationmark.triangle")
.foregroundStyle(.red)
}
}
Section {
Button(action: pruefe) {
if zustand.beschaeftigt {
ProgressView()
} else {
Text(String(localized: "server.verbinden"))
}
}
.disabled(eingabe.trimmingCharacters(in: .whitespaces).isEmpty
|| zustand.beschaeftigt)
}
}
.navigationTitle(String(localized: "server.titel"))
.onAppear { imFeld = true }
}
}
private func pruefe() {
Task { _ = await zustand.pruefeUndMerkeServer(eingabe) }
}
}