Overhaul native approval UX and add widget surfaces
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
Bring the SwiftUI app in line with the Apple-native mock and keep pending approvals actionable from Live Activities and watch complications.
This commit is contained in:
@@ -12,6 +12,8 @@ protocol IDPServicing {
|
||||
}
|
||||
|
||||
actor MockIDPService: IDPServicing {
|
||||
static let shared = MockIDPService()
|
||||
|
||||
private let profile = MemberProfile(
|
||||
name: "Phil Kunz",
|
||||
handle: "phil@idp.global",
|
||||
@@ -20,15 +22,24 @@ actor MockIDPService: IDPServicing {
|
||||
recoverySummary: "Recovery kit healthy with 2 of 3 backup paths verified."
|
||||
)
|
||||
|
||||
private let appStateStore: AppStateStoring
|
||||
private var requests: [ApprovalRequest] = []
|
||||
private var notifications: [AppNotification] = []
|
||||
|
||||
init() {
|
||||
requests = Self.seedRequests()
|
||||
notifications = Self.seedNotifications()
|
||||
init(appStateStore: AppStateStoring = UserDefaultsAppStateStore()) {
|
||||
self.appStateStore = appStateStore
|
||||
|
||||
if let state = appStateStore.load() {
|
||||
requests = state.requests.sorted { $0.createdAt > $1.createdAt }
|
||||
notifications = state.notifications.sorted { $0.sentAt > $1.sentAt }
|
||||
} else {
|
||||
requests = Self.seedRequests()
|
||||
notifications = Self.seedNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
func bootstrap() async throws -> BootstrapContext {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(120))
|
||||
return BootstrapContext(
|
||||
suggestedPairingPayload: "idp.global://pair?token=swiftapp-demo-berlin&origin=code.foss.global&device=Safari%20on%20Berlin%20MBP"
|
||||
@@ -36,6 +47,7 @@ actor MockIDPService: IDPServicing {
|
||||
}
|
||||
|
||||
func signIn(with request: PairingAuthenticationRequest) async throws -> SignInResult {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(260))
|
||||
|
||||
try validateSignedGPSPosition(in: request)
|
||||
@@ -51,6 +63,8 @@ actor MockIDPService: IDPServicing {
|
||||
at: 0
|
||||
)
|
||||
|
||||
persistSharedStateIfAvailable()
|
||||
|
||||
return SignInResult(
|
||||
session: session,
|
||||
snapshot: snapshot()
|
||||
@@ -58,6 +72,7 @@ actor MockIDPService: IDPServicing {
|
||||
}
|
||||
|
||||
func identify(with request: PairingAuthenticationRequest) async throws -> DashboardSnapshot {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(180))
|
||||
|
||||
try validateSignedGPSPosition(in: request)
|
||||
@@ -73,15 +88,19 @@ actor MockIDPService: IDPServicing {
|
||||
at: 0
|
||||
)
|
||||
|
||||
persistSharedStateIfAvailable()
|
||||
|
||||
return snapshot()
|
||||
}
|
||||
|
||||
func refreshDashboard() async throws -> DashboardSnapshot {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(180))
|
||||
return snapshot()
|
||||
}
|
||||
|
||||
func approveRequest(id: UUID) async throws -> DashboardSnapshot {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(150))
|
||||
|
||||
guard let index = requests.firstIndex(where: { $0.id == id }) else {
|
||||
@@ -100,10 +119,13 @@ actor MockIDPService: IDPServicing {
|
||||
at: 0
|
||||
)
|
||||
|
||||
persistSharedStateIfAvailable()
|
||||
|
||||
return snapshot()
|
||||
}
|
||||
|
||||
func rejectRequest(id: UUID) async throws -> DashboardSnapshot {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(150))
|
||||
|
||||
guard let index = requests.firstIndex(where: { $0.id == id }) else {
|
||||
@@ -122,10 +144,13 @@ actor MockIDPService: IDPServicing {
|
||||
at: 0
|
||||
)
|
||||
|
||||
persistSharedStateIfAvailable()
|
||||
|
||||
return snapshot()
|
||||
}
|
||||
|
||||
func simulateIncomingRequest() async throws -> DashboardSnapshot {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(120))
|
||||
|
||||
let syntheticRequest = ApprovalRequest(
|
||||
@@ -151,10 +176,13 @@ actor MockIDPService: IDPServicing {
|
||||
at: 0
|
||||
)
|
||||
|
||||
persistSharedStateIfAvailable()
|
||||
|
||||
return snapshot()
|
||||
}
|
||||
|
||||
func markNotificationRead(id: UUID) async throws -> DashboardSnapshot {
|
||||
restoreSharedState()
|
||||
try await Task.sleep(for: .milliseconds(80))
|
||||
|
||||
guard let index = notifications.firstIndex(where: { $0.id == id }) else {
|
||||
@@ -162,6 +190,7 @@ actor MockIDPService: IDPServicing {
|
||||
}
|
||||
|
||||
notifications[index].isUnread = false
|
||||
persistSharedStateIfAvailable()
|
||||
return snapshot()
|
||||
}
|
||||
|
||||
@@ -227,6 +256,30 @@ actor MockIDPService: IDPServicing {
|
||||
return "An identity proof was completed for \(context.deviceName) on \(context.originHost)."
|
||||
}
|
||||
|
||||
private func restoreSharedState() {
|
||||
guard let state = appStateStore.load() else {
|
||||
requests = Self.seedRequests()
|
||||
notifications = Self.seedNotifications()
|
||||
return
|
||||
}
|
||||
|
||||
requests = state.requests.sorted { $0.createdAt > $1.createdAt }
|
||||
notifications = state.notifications.sorted { $0.sentAt > $1.sentAt }
|
||||
}
|
||||
|
||||
private func persistSharedStateIfAvailable() {
|
||||
guard let state = appStateStore.load() else { return }
|
||||
|
||||
appStateStore.save(
|
||||
PersistedAppState(
|
||||
session: state.session,
|
||||
profile: state.profile,
|
||||
requests: requests,
|
||||
notifications: notifications
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private static func seedRequests() -> [ApprovalRequest] {
|
||||
[
|
||||
ApprovalRequest(
|
||||
|
||||
Reference in New Issue
Block a user