SHA256
feat(ios): replace embedded website with initial native SwiftUI API client
Introduce real SwiftUI auth/dashboard/task/note/shopping screens, a bounded origin-bound URLSession transport and Keychain-backed native sessions. Archive the rejected WebKit renderer rather than embedding it for unfinished modules. Sixteen portable URL/contract/transport tests and Swift format/parsing pass on Linux; Apple SDK application and UI validation remains mandatory. This is an incomplete native slice, not full PWA parity or a release.
This commit is contained in:
1 parent
11bf4ca620
commit
97570b7fdf
15 files changed
+1182
-11
No files matched your search
@@ -0,0 +1,147 @@
|
||||
import Foundation
|
||||
|
||||
enum NativePage: String, CaseIterable, Identifiable, Sendable {
|
||||
case home, tasks, shopping, notes, more
|
||||
var id: String { rawValue }
|
||||
var title: String {
|
||||
switch self {
|
||||
case .home: "Übersicht"
|
||||
case .tasks: "Aufgaben"
|
||||
case .shopping: "Einkauf"
|
||||
case .notes: "Notizen"
|
||||
case .more: "Mehr"
|
||||
}
|
||||
}
|
||||
var symbol: String {
|
||||
switch self {
|
||||
case .home: "house"
|
||||
case .tasks: "checkmark.square"
|
||||
case .shopping: "cart"
|
||||
case .notes: "note.text"
|
||||
case .more: "ellipsis"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NativeBootstrap: Decodable, Sendable {
|
||||
let appName: String
|
||||
let setupRequired: Bool
|
||||
let passwordResetEnabled: Bool
|
||||
}
|
||||
|
||||
struct NativeAuthentication: Decodable, Sendable {
|
||||
let enabled: Bool
|
||||
let passwordLoginEnabled: Bool
|
||||
let guestPasswordLoginEnabled: Bool?
|
||||
}
|
||||
|
||||
struct NativeUser: Decodable, Sendable {
|
||||
let id: Int
|
||||
let username: String
|
||||
let displayName: String
|
||||
}
|
||||
|
||||
struct NativePermissions: Decodable, Sendable {
|
||||
let admin: Bool
|
||||
let modules: [String: String]
|
||||
let widgets: [String: String]
|
||||
func access(_ module: String) -> String { admin ? "write" : modules[module] ?? "write" }
|
||||
func showsWidget(_ widget: String) -> Bool { admin || widgets[widget] != "none" }
|
||||
}
|
||||
|
||||
struct NativeIdentity: Decodable, Sendable {
|
||||
let user: NativeUser?
|
||||
let permissions: NativePermissions?
|
||||
let csrfToken: String?
|
||||
let twoFactorRequired: Bool?
|
||||
}
|
||||
|
||||
struct NativeEnvelope<Value: Decodable & Sendable>: Decodable, Sendable { let data: Value }
|
||||
struct NativeEmpty: Decodable, Sendable {}
|
||||
|
||||
struct NativeTask: Decodable, Identifiable, Sendable {
|
||||
let id: Int
|
||||
let title: String
|
||||
let description: String?
|
||||
let status: String
|
||||
let priority: String
|
||||
let dueDate: String?
|
||||
let category: String?
|
||||
}
|
||||
|
||||
struct NativeNote: Decodable, Identifiable, Sendable {
|
||||
let id: Int
|
||||
let title: String?
|
||||
let content: String
|
||||
let pinned: Int
|
||||
}
|
||||
|
||||
struct NativeShoppingList: Decodable, Identifiable, Sendable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let itemTotal: Int?
|
||||
let itemChecked: Int?
|
||||
let openCount: Int?
|
||||
}
|
||||
|
||||
struct NativeShoppingItem: Decodable, Identifiable, Sendable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let quantity: String?
|
||||
let category: String?
|
||||
let isChecked: Int
|
||||
}
|
||||
|
||||
struct NativeEventPreview: Decodable, Identifiable, Sendable {
|
||||
let id: Int
|
||||
let title: String
|
||||
let startDatetime: String
|
||||
}
|
||||
|
||||
struct NativeTaskPreview: Decodable, Identifiable, Sendable {
|
||||
let id: Int
|
||||
let title: String
|
||||
let dueDate: String?
|
||||
}
|
||||
|
||||
struct NativeDashboard: Decodable, Sendable {
|
||||
let openTaskCount: Int?
|
||||
let shoppingOpenCount: Int?
|
||||
let urgentTasks: [NativeTaskPreview]
|
||||
let upcomingEvents: [NativeEventPreview]
|
||||
let shoppingLists: [NativeShoppingList]
|
||||
let pinnedNotes: [NativeNote]
|
||||
}
|
||||
|
||||
enum NativeValue: Encodable, Sendable {
|
||||
case string(String)
|
||||
case number(Int)
|
||||
case bool(Bool)
|
||||
case null
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var value = encoder.singleValueContainer()
|
||||
switch self {
|
||||
case .string(let text): try value.encode(text)
|
||||
case .number(let number): try value.encode(number)
|
||||
case .bool(let flag): try value.encode(flag)
|
||||
case .null: try value.encodeNil()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum NativeAPIError: LocalizedError {
|
||||
case response(Int)
|
||||
case invalidResponse, invalidPath, unavailable, closed
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .response(401): "Anmeldung oder Sitzung ist nicht mehr gültig."
|
||||
case .response(403): "Diese Aktion ist nicht erlaubt. Bitte prüfe deine Berechtigungen."
|
||||
case .response(409): "Der Eintrag wurde inzwischen geändert. Bitte neu laden."
|
||||
case .response(429): "Zu viele Versuche. Bitte warte einen Moment."
|
||||
case .response(let status): "Die Anfrage konnte nicht abgeschlossen werden (HTTP \(status))."
|
||||
case .invalidPath, .invalidResponse: "Der Server hat keine gültige API-Antwort geliefert."
|
||||
case .unavailable: "Verbindung fehlgeschlagen. Bitte Server, Zertifikat und Netzwerk prüfen."
|
||||
case .closed: "Diese Sitzung wurde geschlossen."
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user