import SwiftUI import CoreImage.CIFilterBuiltins struct SafetyNumberView: View { let peerUserId: String let peerUsername: String var chatClient: ChatClient @State private var vm = VerificationVM() @State private var showQRScanner = false var body: some View { ScrollView { VStack(spacing: 24) { // Verification status badge VerificationStatusView(status: vm.verificationStatus) .padding(.top) // Safety number if let safetyNumber = vm.safetyNumber { VStack(spacing: 8) { Text("Safety Number") .font(.headline) Text("If both you and \(peerUsername) see the same number, your communication is secure.") .font(.caption) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal) Text(safetyNumber) .font(.system(.title2, design: .monospaced)) .padding() .background(.quaternary) .clipShape(RoundedRectangle(cornerRadius: 12)) } } // QR Code if let qrData = vm.qrCodeData { VStack(spacing: 8) { Text("Your QR Code") .font(.headline) if let qrImage = generateQRCode(from: qrData) { Image(uiImage: qrImage) .interpolation(.none) .resizable() .scaledToFit() .frame(width: 200, height: 200) .padding() .background(.white) .clipShape(RoundedRectangle(cornerRadius: 12)) } } } // Fingerprints VStack(spacing: 12) { if let myFP = vm.myFingerprint { VStack(spacing: 4) { Text("Your Fingerprint") .font(.subheadline.bold()) Text(myFP) .font(.system(.caption, design: .monospaced)) } } if let peerFP = vm.peerFingerprint { VStack(spacing: 4) { Text("\(peerUsername)'s Fingerprint") .font(.subheadline.bold()) Text(peerFP) .font(.system(.caption, design: .monospaced)) } } } // Actions VStack(spacing: 12) { if vm.verificationStatus != "verified" { Button { Task { await vm.verifyContact(peerUserId: peerUserId, chatClient: chatClient) } } label: { Label("Mark as Verified", systemImage: "checkmark.shield.fill") .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) Button { showQRScanner = true } label: { Label("Scan QR Code", systemImage: "qrcode.viewfinder") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) } else { Button(role: .destructive) { Task { await vm.unverifyContact(peerUserId: peerUserId, chatClient: chatClient) } } label: { Label("Remove Verification", systemImage: "xmark.shield") .frame(maxWidth: .infinity) } .buttonStyle(.bordered) } } .padding(.horizontal) // Scan result if let result = vm.scanResult { Text(result) .font(.callout) .foregroundStyle(vm.scanSuccess == true ? .green : .red) .padding() } } .padding() } .navigationTitle("Verify \(peerUsername)") .navigationBarTitleDisplayMode(.inline) .sheet(isPresented: $showQRScanner) { QRCodeScannerView { scannedData in showQRScanner = false Task { await vm.verifyQRCode(data: scannedData, chatClient: chatClient) } } } .task { await vm.loadVerification(peerUserId: peerUserId, chatClient: chatClient) } } private func generateQRCode(from data: Data) -> UIImage? { let context = CIContext() let filter = CIFilter.qrCodeGenerator() // Base64-encode binary data — raw binary gets corrupted by QR readers (UTF-8 re-encoding) let b64String = data.base64EncodedString() filter.setValue(b64String.data(using: .ascii), forKey: "inputMessage") filter.setValue("M", forKey: "inputCorrectionLevel") guard let outputImage = filter.outputImage else { return nil } let scale = 200.0 / outputImage.extent.width let scaledImage = outputImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else { return nil } return UIImage(cgImage: cgImage) } }