fix(ios): clear private native state and apply upstream dark tokens

Discard editor drafts on account/server changes, obscure inactive sheets and clear loaded records even when vault erasure fails. Serialize local removal and distinguish dark tint from primary button fill. Swift format/parsing and sixteen portable tests pass; Apple SDK validation remains outstanding.
This commit is contained in:
Yuvomi Mobile Contributors committed 2026-10-21 09:09:39 +02:00
1 parent 97570b7fdf
commit 81973c3c45
2 files changed
+40 -8

No files matched your search

+36 -6
View File
@@ -2,6 +2,12 @@ import SwiftUI
private enum NativeDesign {
static let accent = Color(red: 108 / 255, green: 58 / 255, blue: 237 / 255)
static func tint(_ dark: Bool) -> Color {
dark ? Color(red: 167 / 255, green: 139 / 255, blue: 250 / 255) : accent
}
static func button(_ dark: Bool) -> Color {
dark ? Color(red: 124 / 255, green: 58 / 255, blue: 237 / 255) : accent
}
static func background(_ dark: Bool) -> Color {
dark
? Color(red: 25 / 255, green: 24 / 255, blue: 22 / 255)
@@ -39,8 +45,18 @@ struct NativeRootView: View {
NativeBrandMark().frame(width: 64, height: 64)
}
}
.tint(NativeDesign.accent)
.tint(NativeDesign.tint(scheme == .dark))
.task { await store.restore() }
.onChange(of: store.user?.id) {
draft = nil
deletePath = nil
search = ""
}
.onChange(of: store.server) {
draft = nil
deletePath = nil
search = ""
}
.sheet(item: $draft) { entry in
NativeEditor(draft: entry, busy: store.busy, error: store.error) { body in
let path: String
@@ -139,7 +155,7 @@ struct NativeRootView: View {
Image(systemName: page.symbol).font(.system(size: 21))
Text(page.title).font(.system(size: 11))
}
.foregroundStyle(store.page == page ? NativeDesign.accent : .secondary)
.foregroundStyle(store.page == page ? NativeDesign.tint(scheme == .dark) : .secondary)
.frame(maxWidth: .infinity).padding(.vertical, 10)
}
.disabled(store.busy).accessibilityIdentifier("nav-\(page.rawValue)")
@@ -341,6 +357,7 @@ struct NativeRootView: View {
}
private struct NativeAuthenticationScreen: View {
@Environment(\.colorScheme) private var scheme
@ObservedObject var store: NativeStore
let forget: () -> Void
@State private var address = ""
@@ -379,8 +396,10 @@ private struct NativeAuthenticationScreen: View {
}
invalidAddress = false
Task { await store.connect(address) }
}.buttonStyle(.borderedProminent).disabled(store.busy || address.isEmpty)
.accessibilityIdentifier("connect")
}.buttonStyle(.borderedProminent).tint(NativeDesign.button(scheme == .dark)).disabled(
store.busy || address.isEmpty
)
.accessibilityIdentifier("connect")
} else if store.twoFactor {
TextField("Code oder Wiederherstellungscode", text: $code).textContentType(.oneTimeCode)
.textFieldStyle(.roundedBorder)
@@ -388,7 +407,8 @@ private struct NativeAuthenticationScreen: View {
let value = code
code = ""
Task { await store.verify(value) }
}.buttonStyle(.borderedProminent).disabled(store.busy || code.isEmpty)
}.buttonStyle(.borderedProminent).tint(NativeDesign.button(scheme == .dark)).disabled(
store.busy || code.isEmpty)
Button("Zurück zur Anmeldung") { store.cancelTwoFactor() }.disabled(store.busy)
} else {
if store.setup { TextField("Anzeigename", text: $name).textFieldStyle(.roundedBorder) }
@@ -406,7 +426,7 @@ private struct NativeAuthenticationScreen: View {
Task {
await store.login(username: username, password: submitted, displayName: name)
}
}.buttonStyle(.borderedProminent).disabled(
}.buttonStyle(.borderedProminent).tint(NativeDesign.button(scheme == .dark)).disabled(
store.busy || username.isEmpty || password.isEmpty
).accessibilityIdentifier("login")
}
@@ -437,6 +457,8 @@ private struct NativeAuthenticationScreen: View {
private struct NativeEditor: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.scenePhase) private var phase
@Environment(\.colorScheme) private var scheme
@State var draft: NativeDraft
let busy: Bool
let error: String?
@@ -482,6 +504,14 @@ private struct NativeEditor: View {
}
}
}.interactiveDismissDisabled(busy)
.overlay {
if phase != .active {
ZStack {
NativeDesign.background(scheme == .dark).ignoresSafeArea()
NativeBrandMark().frame(width: 64, height: 64)
}
}
}
}
}
+4 -2
View File
@@ -175,26 +175,28 @@ final class NativeStore: ObservableObject {
revoked = true
} catch { revoked = false }
await api?.close()
clearLoadedData()
try vault?.clear()
if let address = try? ServerAddress(server), let storage = vault {
api = try NativeAPI(server: address, saved: nil, persist: storage.save)
}
clearLoadedData()
if !revoked { error = "Lokal abgemeldet. Die Abmeldung auf dem Server konnte nicht bestätigt werden." }
}
}
func forget() async {
guard !busy else { return }
busy = true
defer { busy = false }
generation += 1
await api?.close()
clearLoadedData()
do { try vault?.clear() } catch {
self.error = NativeAPIError.unavailable.errorDescription
return
}
api = nil
vault = nil
clearLoadedData()
server = ""
appName = "Yuvomi"
setup = false