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:
@@ -1,193 +1,126 @@
|
||||
import SwiftUI
|
||||
|
||||
private let loginAccent = AppTheme.accent
|
||||
|
||||
struct LoginRootView: View {
|
||||
@ObservedObject var model: AppViewModel
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
#if !os(macOS)
|
||||
@State private var isNFCSheetPresented = false
|
||||
#endif
|
||||
|
||||
var body: some View {
|
||||
AppScrollScreen(compactLayout: compactLayout) {
|
||||
LoginHeroPanel(model: model, compactLayout: compactLayout)
|
||||
PairingConsoleCard(model: model, compactLayout: compactLayout)
|
||||
}
|
||||
.sheet(isPresented: $model.isScannerPresented) {
|
||||
QRScannerSheet(
|
||||
seededPayload: model.suggestedPairingPayload,
|
||||
title: "Scan linking QR",
|
||||
description: "Use the camera to scan the QR code from the web flow that activates this device as your passport.",
|
||||
navigationTitle: "Scan Linking QR",
|
||||
onCodeScanned: { payload in
|
||||
#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()
|
||||
|
||||
private var compactLayout: Bool {
|
||||
#if os(iOS)
|
||||
horizontalSizeClass == .compact
|
||||
#else
|
||||
false
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private struct LoginHeroPanel: View {
|
||||
#if os(macOS)
|
||||
private struct MacPairingView: View {
|
||||
@ObservedObject var model: AppViewModel
|
||||
let compactLayout: Bool
|
||||
|
||||
var body: some View {
|
||||
AppPanel(compactLayout: compactLayout, radius: AppLayout.largeCardRadius) {
|
||||
AppBadge(title: "Secure passport setup", tone: loginAccent)
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
HStack(spacing: 12) {
|
||||
Image(systemName: "shield.lefthalf.filled")
|
||||
.font(.title2)
|
||||
.foregroundStyle(IdP.tint)
|
||||
|
||||
Text("Turn this device into a passport for your idp.global identity")
|
||||
.font(.system(size: compactLayout ? 28 : 36, weight: .bold, design: .rounded))
|
||||
.lineLimit(3)
|
||||
|
||||
Text("Scan a linking QR code or paste a payload to activate this device as your passport for identity proofs and security alerts.")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Divider()
|
||||
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
LoginFeatureRow(icon: "qrcode.viewfinder", title: "Scan a QR code from the web flow")
|
||||
LoginFeatureRow(icon: "doc.text.viewfinder", title: "Paste a payload when you already have one")
|
||||
LoginFeatureRow(icon: "iphone.gen3", title: "Handle identity checks and alerts here")
|
||||
}
|
||||
|
||||
if model.isBootstrapping {
|
||||
ProgressView("Preparing preview passport...")
|
||||
.tint(loginAccent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct LoginFeatureRow: View {
|
||||
let icon: String
|
||||
let title: String
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
Image(systemName: icon)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(loginAccent)
|
||||
.frame(width: 28, height: 28)
|
||||
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PairingConsoleCard: View {
|
||||
@ObservedObject var model: AppViewModel
|
||||
let compactLayout: Bool
|
||||
|
||||
var body: some View {
|
||||
AppSectionCard(title: "Set up passport", compactLayout: compactLayout) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Link payload")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
|
||||
AppTextEditorField(
|
||||
text: $model.manualPairingPayload,
|
||||
minHeight: compactLayout ? 132 : 150
|
||||
)
|
||||
}
|
||||
|
||||
if model.isAuthenticating {
|
||||
HStack(spacing: 10) {
|
||||
ProgressView()
|
||||
Text("Activating this passport...")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Text("NFC, QR, and OTP proof methods become available after this passport is active.")
|
||||
.font(.footnote)
|
||||
.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))
|
||||
|
||||
if compactLayout {
|
||||
VStack(spacing: 12) {
|
||||
primaryButtons
|
||||
secondaryButtons
|
||||
}
|
||||
} else {
|
||||
VStack(spacing: 12) {
|
||||
HStack(spacing: 12) {
|
||||
primaryButtons
|
||||
VStack(spacing: 10) {
|
||||
Button("Use demo payload") {
|
||||
Task {
|
||||
await model.signInWithSuggestedPayload()
|
||||
}
|
||||
|
||||
secondaryButtons
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(PrimaryActionStyle())
|
||||
|
||||
@ViewBuilder
|
||||
private var primaryButtons: some View {
|
||||
Button {
|
||||
model.isScannerPresented = true
|
||||
} label: {
|
||||
Label("Scan QR", systemImage: "qrcode.viewfinder")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.controlSize(.large)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var secondaryButtons: some View {
|
||||
if compactLayout {
|
||||
VStack(spacing: 12) {
|
||||
usePayloadButton
|
||||
previewPayloadButton
|
||||
}
|
||||
} else {
|
||||
HStack(spacing: 12) {
|
||||
usePayloadButton
|
||||
previewPayloadButton
|
||||
Button("Link with payload") {
|
||||
Task {
|
||||
await model.signInWithManualPayload()
|
||||
}
|
||||
}
|
||||
.buttonStyle(SecondaryActionStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var usePayloadButton: some View {
|
||||
Button {
|
||||
Task {
|
||||
await model.signInWithManualPayload()
|
||||
}
|
||||
} label: {
|
||||
if model.isAuthenticating {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
Label("Link with payload", systemImage: "arrow.right.circle")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.controlSize(.large)
|
||||
.disabled(model.isAuthenticating)
|
||||
}
|
||||
|
||||
private var previewPayloadButton: some View {
|
||||
Button {
|
||||
Task {
|
||||
await model.signInWithSuggestedPayload()
|
||||
}
|
||||
} label: {
|
||||
Label("Use preview passport", systemImage: "wand.and.stars")
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.controlSize(.large)
|
||||
.padding(20)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user