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>
58 lines
2.1 KiB
Swift
58 lines
2.1 KiB
Swift
import Foundation
|
||
import Observation
|
||
|
||
/// Holt eine Liste, legt sie in den Zwischenspeicher und weiss, was bei einem
|
||
/// Fehlschlag noch da ist.
|
||
///
|
||
/// Jede Listenansicht braucht dasselbe: laden, Fehler zeigen, zum Aktualisieren
|
||
/// herunterziehen, offline den letzten Stand behalten. Einmal hier, statt in
|
||
/// jeder Ansicht neu.
|
||
@MainActor
|
||
@Observable
|
||
final class Lader<A: Codable & Sendable> {
|
||
private(set) var zustand: Ladezustand<A> = .laedt
|
||
|
||
private let schluessel: String
|
||
private let holen: () async throws -> A
|
||
|
||
/// - Parameter schluessel: Name im Zwischenspeicher. Der Endpunktpfad ist
|
||
/// eine gute Wahl – er ist eindeutig und beim Suchen lesbar.
|
||
init(schluessel: String, holen: @escaping () async throws -> A) {
|
||
self.schluessel = schluessel
|
||
self.holen = holen
|
||
}
|
||
|
||
/// Lädt. Beim ersten Mal wird ein vorhandener Stand sofort angezeigt,
|
||
/// damit die Liste nicht leer aufblitzt, während der Abruf läuft.
|
||
func lade(zeigeGemerktesSofort: Bool = true) async {
|
||
if zeigeGemerktesSofort, zustand.inhalt == nil,
|
||
let gemerkt = Zwischenspeicher.hole(A.self, unter: schluessel) {
|
||
zustand = .ausSpeicher(gemerkt.wert, geholtAm: gemerkt.geholtAm,
|
||
fehler: .keineVerbindung(""))
|
||
} else if zustand.inhalt == nil {
|
||
zustand = .laedt
|
||
}
|
||
|
||
do {
|
||
let frisch = try await holen()
|
||
Zwischenspeicher.lege(frisch, unter: schluessel)
|
||
zustand = .da(frisch)
|
||
} catch let fehler as APIFehler {
|
||
if let gemerkt = Zwischenspeicher.hole(A.self, unter: schluessel) {
|
||
zustand = .ausSpeicher(gemerkt.wert, geholtAm: gemerkt.geholtAm,
|
||
fehler: fehler)
|
||
} else {
|
||
zustand = .fehler(fehler)
|
||
}
|
||
} catch {
|
||
zustand = .fehler(.keineVerbindung(error.localizedDescription))
|
||
}
|
||
}
|
||
|
||
/// Fürs Herunterziehen: kein Zwischenzustand, die alte Liste bleibt
|
||
/// stehen, bis die neue da ist.
|
||
func aktualisiere() async {
|
||
await lade(zeigeGemerktesSofort: false)
|
||
}
|
||
}
|