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