import Foundation import SwiftUI private let watchAccent = AppTheme.accent private let watchGold = AppTheme.warmAccent struct WatchRootView: View { @ObservedObject var model: AppViewModel var body: some View { NavigationStack { Group { if model.session == nil { WatchPairingView(model: model) } else { WatchDashboardView(model: model) } } .navigationBarTitleDisplayMode(.inline) } .tint(watchAccent) } } private struct WatchPairingView: View { @ObservedObject var model: AppViewModel var body: some View { ScrollView { VStack(alignment: .leading, spacing: 12) { AppPanel(compactLayout: true, radius: 22) { AppBadge(title: "Preview passport", tone: watchAccent) Text("Prove identity from your wrist") .font(.title3.weight(.semibold)) Text("This preview connects directly to the mock service today.") .font(.footnote) .foregroundStyle(.secondary) HStack(spacing: 8) { AppStatusTag(title: "Wrist-ready", tone: watchAccent) AppStatusTag(title: "Preview sync", tone: watchGold) } } if model.isBootstrapping { ProgressView("Preparing preview passport...") .frame(maxWidth: .infinity, alignment: .leading) } Button { Task { await model.signInWithSuggestedPayload() } } label: { if model.isAuthenticating { ProgressView() .frame(maxWidth: .infinity) } else { Label("Use Preview Passport", systemImage: "qrcode") .frame(maxWidth: .infinity) } } .buttonStyle(.borderedProminent) .disabled(model.isBootstrapping || model.suggestedPairingPayload.isEmpty || model.isAuthenticating) AppPanel(compactLayout: true, radius: 18) { Text("What works today") .font(.headline) Text("The watch shows pending identity checks, recent alerts, and quick actions.") .font(.footnote) .foregroundStyle(.secondary) } } .padding(.horizontal, 8) .padding(.bottom, 20) } .navigationTitle("Set Up Watch") } } private struct WatchInfoPill: View { let title: String let value: String let tone: Color var body: some View { VStack(alignment: .leading, spacing: 2) { Text(title) .font(.caption2) .foregroundStyle(.secondary) Text(value) .font(.caption.weight(.semibold)) .foregroundStyle(.primary) } .padding(.horizontal, 10) .padding(.vertical, 8) .frame(maxWidth: .infinity, alignment: .leading) .background(tone.opacity(0.10), in: RoundedRectangle(cornerRadius: 16, style: .continuous)) } } private struct WatchDashboardView: View { @ObservedObject var model: AppViewModel var body: some View { List { Section { WatchPassportCard(model: model) } Section("Pending") { if model.pendingRequests.isEmpty { Text("No checks waiting.") .foregroundStyle(.secondary) Button("Seed Identity Check") { Task { await model.simulateIncomingRequest() } } } else { ForEach(model.pendingRequests) { request in NavigationLink { WatchRequestDetailView(model: model, requestID: request.id) } label: { WatchRequestRow(request: request) } } } } Section("Recent Activity") { if model.notifications.isEmpty { Text("No recent alerts.") .foregroundStyle(.secondary) } else { ForEach(model.notifications.prefix(3)) { notification in NavigationLink { WatchNotificationDetailView(model: model, notificationID: notification.id) } label: { WatchNotificationRow(notification: notification) } } } } Section("Actions") { Button("Refresh") { Task { await model.refreshDashboard() } } .disabled(model.isRefreshing) Button("Send Test Alert") { Task { await model.sendTestNotification() } } if model.notificationPermission == .unknown || model.notificationPermission == .denied { Button("Enable Alerts") { Task { await model.requestNotificationAccess() } } } } Section("Account") { if let profile = model.profile { VStack(alignment: .leading, spacing: 4) { Text(profile.handle) .font(.headline) Text(profile.organization) .font(.footnote) .foregroundStyle(.secondary) } } VStack(alignment: .leading, spacing: 4) { Text("Notifications") .font(.headline) Text(model.notificationPermission.title) .font(.footnote) .foregroundStyle(.secondary) } Button("Sign Out", role: .destructive) { model.signOut() } } } .navigationTitle("Passport") .refreshable { await model.refreshDashboard() } } } private struct WatchPassportCard: View { @ObservedObject var model: AppViewModel var body: some View { VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 2) { Text(model.profile?.name ?? "Preview Session") .font(.headline) Text(model.pairedDeviceSummary) .font(.footnote) .foregroundStyle(.secondary) if let session = model.session { Text("Via \(session.pairingTransport.title)") .font(.caption2) .foregroundStyle(.secondary) } } HStack(spacing: 8) { WatchMetricPill(title: "Pending", value: "\(model.pendingRequests.count)", accent: watchAccent) WatchMetricPill(title: "Unread", value: "\(model.unreadNotificationCount)", accent: watchGold) } } .padding(.vertical, 6) } } private struct WatchMetricPill: View { let title: String let value: String let accent: Color var body: some View { VStack(alignment: .leading, spacing: 2) { Text(value) .font(.headline.monospacedDigit()) Text(title) .font(.caption2) .foregroundStyle(.secondary) } .padding(.horizontal, 10) .padding(.vertical, 8) .frame(maxWidth: .infinity, alignment: .leading) .background(accent.opacity(0.14), in: RoundedRectangle(cornerRadius: 14, style: .continuous)) } } private struct WatchRequestRow: View { let request: ApprovalRequest var body: some View { VStack(alignment: .leading, spacing: 4) { HStack(alignment: .top, spacing: 6) { Text(request.title) .font(.headline) .lineLimit(2) Spacer(minLength: 6) Image(systemName: request.risk == .elevated ? "exclamationmark.shield.fill" : "checkmark.shield.fill") .foregroundStyle(request.risk == .elevated ? .orange : watchAccent) } Text(request.source) .font(.footnote) .foregroundStyle(.secondary) Text(request.createdAt.watchRelativeString) .font(.caption2) .foregroundStyle(.secondary) } .padding(.vertical, 2) } } private struct WatchNotificationRow: View { let notification: AppNotification var body: some View { VStack(alignment: .leading, spacing: 4) { HStack(alignment: .top, spacing: 6) { Text(notification.title) .font(.headline) .lineLimit(2) Spacer(minLength: 6) if notification.isUnread { Circle() .fill(watchAccent) .frame(width: 8, height: 8) } } Text(notification.message) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(2) Text(notification.sentAt.watchRelativeString) .font(.caption2) .foregroundStyle(.secondary) } .padding(.vertical, 2) } } private struct WatchRequestDetailView: View { @ObservedObject var model: AppViewModel let requestID: ApprovalRequest.ID private var request: ApprovalRequest? { model.requests.first(where: { $0.id == requestID }) } var body: some View { Group { if let request { ScrollView { VStack(alignment: .leading, spacing: 12) { detailHeader( title: request.title, subtitle: request.source, badge: request.status.title ) Text(request.subtitle) .font(.footnote) .foregroundStyle(.secondary) VStack(alignment: .leading, spacing: 6) { Text("Trust Summary") .font(.headline) Text(request.trustHeadline) .font(.subheadline.weight(.semibold)) Text(request.trustDetail) .font(.footnote) .foregroundStyle(.secondary) Text(request.risk.guidance) .font(.footnote) .foregroundStyle(.secondary) } .padding(10) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) if !request.scopes.isEmpty { VStack(alignment: .leading, spacing: 8) { Text("Scopes") .font(.headline) ForEach(request.scopes, id: \.self) { scope in Label(scope, systemImage: "checkmark.seal.fill") .font(.footnote) } } } if request.status == .pending { if model.activeRequestID == request.id { ProgressView("Updating proof...") } else { Button("Verify") { Task { await model.approve(request) } } .buttonStyle(.borderedProminent) Button("Decline", role: .destructive) { Task { await model.reject(request) } } } } } .padding(.horizontal, 8) .padding(.bottom, 20) } } else { Text("This request is no longer available.") .foregroundStyle(.secondary) } } .navigationTitle("Identity Check") } private func detailHeader(title: String, subtitle: String, badge: String) -> some View { VStack(alignment: .leading, spacing: 6) { Text(title) .font(.headline) Text(subtitle) .font(.footnote) .foregroundStyle(.secondary) Text(badge) .font(.caption.weight(.semibold)) .padding(.horizontal, 8) .padding(.vertical, 4) .background(watchAccent.opacity(0.14), in: Capsule()) } } } private struct WatchNotificationDetailView: View { @ObservedObject var model: AppViewModel let notificationID: AppNotification.ID private var notification: AppNotification? { model.notifications.first(where: { $0.id == notificationID }) } var body: some View { Group { if let notification { ScrollView { VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 6) { Text(notification.title) .font(.headline) Text(notification.kind.title) .font(.footnote.weight(.semibold)) .foregroundStyle(watchAccent) Text(notification.sentAt.watchRelativeString) .font(.caption2) .foregroundStyle(.secondary) } Text(notification.message) .font(.footnote) .foregroundStyle(.secondary) VStack(alignment: .leading, spacing: 6) { Text("Alert posture") .font(.headline) Text(model.notificationPermission.summary) .font(.footnote) .foregroundStyle(.secondary) } .padding(10) .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 18, style: .continuous)) if notification.isUnread { Button("Mark Read") { Task { await model.markNotificationRead(notification) } } } } .padding(.horizontal, 8) .padding(.bottom, 20) } } else { Text("This activity item has already been cleared.") .foregroundStyle(.secondary) } } .navigationTitle("Activity") } } private extension Date { var watchRelativeString: String { WatchFormatters.relative.localizedString(for: self, relativeTo: .now) } } private enum WatchFormatters { static let relative: RelativeDateTimeFormatter = { let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .abbreviated return formatter }() }