Files
Kecalek_python/ios_client 0.8.5/Kecalek/Views/Verification/SafetyNumberView.swift
2026-03-14 12:43:56 +01:00

145 lines
5.9 KiB
Swift

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)
}
}