proofmode 0.9.0

Capture, share, and preserve verifiable photos and videos
Documentation
import SwiftUI
import MapKit

struct ProofDetailView: View {
    let proof: Proof
    @StateObject private var proofModeService = ProofModeService()
    @State private var verificationResult: String?
    @State private var showingShareSheet = false
    @State private var shareItems: [Any] = []
    @State private var proofFilesList: [String] = []
    @State private var mapRegion: MKCoordinateRegion?
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // Image
                if let image = proof.fullImage {
                    Image(uiImage: image)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(maxHeight: 300)
                        .cornerRadius(12)
                } else {
                    RoundedRectangle(cornerRadius: 12)
                        .fill(Color.gray.opacity(0.3))
                        .frame(height: 200)
                        .overlay(
                            Image(systemName: "photo")
                                .font(.system(size: 50))
                                .foregroundColor(.gray)
                        )
                }
                
                // Basic Info
                VStack(alignment: .leading, spacing: 12) {
                    InfoRow(label: "File Name", value: proof.fileName)
                    InfoRow(label: "Created", value: proof.formattedDate)
                    InfoRow(label: "Hash", value: String(proof.hash.prefix(20)) + "...")
                    InfoRow(label: "File Size", value: formatFileSize(proof.fileSize))
                }
                .padding()
                .background(Color.gray.opacity(0.05))
                .cornerRadius(12)
                
                // Verification Section
                VStack(spacing: 16) {
                    Button(action: {
                        verifyProof()
                    }) {
                        HStack {
                            if proofModeService.isVerifying {
                                ProgressView()
                                    .progressViewStyle(CircularProgressViewStyle(tint: .white))
                                    .scaleEffect(0.8)
                            } else {
                                Image(systemName: "checkmark.shield")
                            }
                            Text(proofModeService.isVerifying ? "Verifying..." : "Verify Proof")
                                .fontWeight(.semibold)
                        }
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(12)
                    }
                    .disabled(proofModeService.isVerifying)
                    
                    // Progress indicator
                    if proofModeService.isVerifying {
                        VStack(spacing: 8) {
                            ProgressView(value: proofModeService.progress)
                                .progressViewStyle(LinearProgressViewStyle())
                            
                            if !proofModeService.status.isEmpty {
                                Text(proofModeService.status)
                                    .font(.caption)
                                    .foregroundColor(.secondary)
                            }
                        }
                        .padding(.horizontal)
                    }
                    
                    // Verification Result
                    if let result = verificationResult {
                        VerificationResultView(result: result)
                    }
                }
                .padding()

                // Share Proof Files
                VStack(spacing: 12) {
                    Button(action: {
                        shareProofFiles()
                    }) {
                        HStack {
                            Image(systemName: "square.and.arrow.up")
                            Text("Share Proof Files")
                                .fontWeight(.semibold)
                        }
                        .frame(maxWidth: .infinity)
                        .padding()
                        .background(Color.orange)
                        .foregroundColor(.white)
                        .cornerRadius(12)
                    }

                    if !proofFilesList.isEmpty {
                        VStack(alignment: .leading, spacing: 4) {
                            Text("Files on disk:")
                                .font(.caption)
                                .fontWeight(.semibold)
                                .foregroundColor(.secondary)
                            ForEach(proofFilesList, id: \.self) { name in
                                Text(name)
                                    .font(.system(.caption2, design: .monospaced))
                                    .foregroundColor(.primary)
                            }
                        }
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding()
                        .background(Color.gray.opacity(0.05))
                        .cornerRadius(8)
                    }
                }
                .padding()
            }
            .padding()
        }
        .navigationBarTitleDisplayMode(.inline)
        .sheet(isPresented: $showingShareSheet) {
            ShareSheet(items: shareItems)
        }
        .onAppear {
            setupMapRegion()
            loadProofFilesList()
        }
    }
    
    private func setupMapRegion() {
        if let location = proof.location {
            mapRegion = MKCoordinateRegion(
                center: CLLocationCoordinate2D(
                    latitude: location.latitude,
                    longitude: location.longitude
                ),
                span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
            )
        }
    }
    
    private func verifyProof() {
        print("DEBUG: Starting verification for \(proof.fileName)")
        verificationResult = nil // Reset previous result
        
        proofModeService.verifyProof(proof) { result in
            switch result {
            case .success(let verificationOutput):
                print("DEBUG: Verification successful, output length: \(verificationOutput.count)")
                print("DEBUG: Verification output: \(verificationOutput)")
                verificationResult = verificationOutput.isEmpty ? "Verification completed but no output received" : verificationOutput
            case .failure(let error):
                print("DEBUG: Verification failed with error: \(error)")
                verificationResult = "Verification failed: \(error.localizedDescription)"
            }
        }
    }
    
    private func loadProofFilesList() {
        guard let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            proofFilesList = ["ERROR: No documents directory"]
            return
        }
        let proofmodeDir = documentsPath.appendingPathComponent("proofmode")
        let hashDir = proofmodeDir.appendingPathComponent(proof.hash)

        var info: [String] = []
        info.append("Hash: \(proof.hash)")
        info.append("Path: \(hashDir.path)")
        info.append("Dir exists: \(FileManager.default.fileExists(atPath: hashDir.path))")

        // Also check if proofmode dir exists and list its contents
        if FileManager.default.fileExists(atPath: proofmodeDir.path) {
            if let subdirs = try? FileManager.default.contentsOfDirectory(atPath: proofmodeDir.path) {
                info.append("Proof dirs: \(subdirs.count)")
                for sub in subdirs.prefix(5) {
                    info.append("  \(sub)")
                }
            }
        } else {
            info.append("proofmode/ dir does not exist")
        }

        do {
            let files = try FileManager.default.contentsOfDirectory(at: hashDir, includingPropertiesForKeys: [.fileSizeKey])
            for url in files.sorted(by: { $0.lastPathComponent < $1.lastPathComponent }) {
                let size = (try? url.resourceValues(forKeys: [.fileSizeKey]).fileSize) ?? 0
                info.append("\(url.lastPathComponent) (\(size) bytes)")
            }
        } catch {
            info.append("No files: \(error.localizedDescription)")
        }

        proofFilesList = info
    }

    private func shareProofFiles() {
        var items: [Any] = []

        // Always include the original image if available
        if let imageData = proof.imageData {
            items.append(imageData as Any)
        }

        // Add proof sidecar files if they exist
        if let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
            let hashDir = documentsPath.appendingPathComponent("proofmode").appendingPathComponent(proof.hash)
            if let files = try? FileManager.default.contentsOfDirectory(at: hashDir, includingPropertiesForKeys: nil) {
                for file in files {
                    items.append(file as Any)
                }
            }
        }

        shareItems = items
        showingShareSheet = true
    }

    private func exportProof() {
        // Export proof bundle
        let exportData = proof.exportData()
        let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let exportPath = documentsPath.appendingPathComponent("\(proof.fileName).proof")
        
        do {
            try exportData.write(to: exportPath)
            print("Proof exported to: \(exportPath)")
        } catch {
            print("Export failed: \(error)")
        }
    }
    
    private func formatFileSize(_ bytes: Int64) -> String {
        let formatter = ByteCountFormatter()
        formatter.countStyle = .file
        return formatter.string(fromByteCount: bytes)
    }
}

struct InfoRow: View {
    let label: String
    let value: String
    
    var body: some View {
        HStack {
            Text(label)
                .foregroundColor(.secondary)
            Spacer()
            Text(value)
                .fontWeight(.medium)
                .multilineTextAlignment(.trailing)
        }
        .font(.subheadline)
    }
}

struct VerificationResultView: View {
    let result: String
    
    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            ScrollView {
                Text(result)
                    .font(.system(.caption, design: .monospaced))
                    .textSelection(.enabled)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            .frame(maxHeight: 300)
            .padding()
            .background(Color.gray.opacity(0.05))
            .cornerRadius(8)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .stroke(Color.gray.opacity(0.3), lineWidth: 1)
            )
            
            HStack {
                Button("Copy Results") {
                    UIPasteboard.general.string = result
                }
                .font(.caption)
                .foregroundColor(.blue)
                
                Spacer()
                
                Text("Tap to select text")
                    .font(.caption2)
                    .foregroundColor(.secondary)
            }
        }
        .padding()
        .background(Color.blue.opacity(0.05))
        .cornerRadius(12)
    }
}

struct ShareSheet: UIViewControllerRepresentable {
    let items: [Any]
    
    func makeUIViewController(context: Context) -> UIActivityViewController {
        UIActivityViewController(activityItems: items, applicationActivities: nil)
    }
    
    func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {}
}