Files
swiftapp/swift/Sources/Features/Auth/LoginRootView.swift
Jürgen Kunz 61a0cc1f7d
Some checks failed
CI / test (push) Has been cancelled
Overhaul native approval UX and add widget surfaces
Bring the SwiftUI app in line with the Apple-native mock and keep pending approvals actionable from Live Activities and watch complications.
2026-04-19 16:29:13 +02:00

127 lines
4.2 KiB
Swift

import SwiftUI
struct LoginRootView: View {
@ObservedObject var model: AppViewModel
#if !os(macOS)
@State private var isNFCSheetPresented = false
#endif
var body: some View {
#if os(macOS)
MacPairingView(model: model)
#else
NavigationStack {
ZStack(alignment: .top) {
LiveQRScannerView { payload in
model.manualPairingPayload = payload
Task {
await model.signIn(with: payload, transport: .qr)
}
}
.ignoresSafeArea()
VStack(spacing: 0) {
IdPGlassCapsule {
VStack(alignment: .leading, spacing: 6) {
Text("Scan a pairing code")
.font(.headline)
Text("Turn this iPhone into your idp.global passport with QR or NFC.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(.horizontal, 16)
.padding(.top, 12)
Spacer()
Button {
isNFCSheetPresented = true
} label: {
IdPGlassCapsule {
HStack(spacing: 10) {
Image(systemName: "wave.3.right")
.foregroundStyle(IdP.tint)
Text("Hold near NFC tag")
.font(.headline)
.foregroundStyle(.primary)
}
}
}
.buttonStyle(.plain)
.padding(.horizontal, 16)
.padding(.bottom, 24)
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Use demo payload") {
Task {
await model.signInWithSuggestedPayload()
}
}
.font(.footnote)
.disabled(model.isAuthenticating || model.suggestedPairingPayload.isEmpty)
}
}
}
.sheet(isPresented: $isNFCSheetPresented) {
NFCSheet(actionTitle: "Approve") { request in
await model.signIn(with: request)
}
}
#endif
}
}
#if os(macOS)
private struct MacPairingView: View {
@ObservedObject var model: AppViewModel
var body: some View {
VStack(alignment: .leading, spacing: 18) {
HStack(spacing: 12) {
Image(systemName: "shield.lefthalf.filled")
.font(.title2)
.foregroundStyle(IdP.tint)
VStack(alignment: .leading, spacing: 2) {
Text("Set up idp.global")
.font(.headline)
Text("Use the demo payload or paste a pairing link.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
TextEditor(text: $model.manualPairingPayload)
.font(.footnote.monospaced())
.scrollContentBackground(.hidden)
.frame(minHeight: 140)
.padding(10)
.background(Color.idpSecondaryGroupedBackground, in: RoundedRectangle(cornerRadius: IdP.cardRadius, style: .continuous))
VStack(spacing: 10) {
Button("Use demo payload") {
Task {
await model.signInWithSuggestedPayload()
}
}
.buttonStyle(PrimaryActionStyle())
Button("Link with payload") {
Task {
await model.signInWithManualPayload()
}
}
.buttonStyle(SecondaryActionStyle())
}
}
.padding(20)
}
}
#endif