SHA256
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.
148 lines
4.2 KiB
Swift
148 lines
4.2 KiB
Swift
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."
|
|
}
|
|
}
|
|
}
|