Files
boc/quixzoom-capture-pipeline/ios/QuixZoomCapture/Views/ContentView.swift
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

339 lines
11 KiB
Swift

import SwiftUI
/**
* QUIXZOOM Capture App Huvudvy
*
* 4 lägen:
* 1. Recording Mode Filma hela gatan
* 2. Guided Mode AI ger realtidsinstruktioner
* 3. Mission Mode Specifika uppgifter
* 4. Adaptive Mode AI planerar nästa observation
*/
struct ContentView: View {
@StateObject private var captureManager = CaptureManager()
@State private var selectedMode: CaptureSession.CaptureMode = .recording
@State private var showingSettings = false
var body: some View {
NavigationView {
VStack(spacing: 0) {
// Status bar
StatusBarView(captureManager: captureManager)
// Kamera-vy
CameraPreviewView(captureManager: captureManager)
.frame(maxHeight: .infinity)
// AI Instruktion (om aktiv)
if let instruction = captureManager.currentInstruction {
AIInstructionView(instruction: instruction)
}
// Kontroller
ControlPanelView(
captureManager: captureManager,
selectedMode: $selectedMode
)
}
.navigationTitle("QUIXZOOM Capture")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: { showingSettings = true }) {
Image(systemName: "gear")
}
}
}
.sheet(isPresented: $showingSettings) {
SettingsView()
}
}
}
}
// MARK: - Status Bar
struct StatusBarView: View {
@ObservedObject var captureManager: CaptureManager
var body: some View {
HStack(spacing: 16) {
// GPS-status
HStack(spacing: 4) {
Image(systemName: "location.fill")
.foregroundColor(gpsColor)
Text("\(Int(captureManager.gpsSignal * 100))%")
.font(.caption)
}
// Kvalitet
HStack(spacing: 4) {
Image(systemName: "checkmark.shield")
.foregroundColor(qualityColor)
Text("\(Int(captureManager.qualityScore * 100))%")
.font(.caption)
}
// Uppladdning
if captureManager.uploadProgress > 0 && captureManager.uploadProgress < 1 {
HStack(spacing: 4) {
ProgressView(value: captureManager.uploadProgress)
.frame(width: 40)
Text("\(Int(captureManager.uploadProgress * 100))%")
.font(.caption)
}
}
Spacer()
// Session-status
if let session = captureManager.currentSession {
Text(session.mode.rawValue.uppercased())
.font(.caption.bold())
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(session.status == .recording ? Color.red.opacity(0.2) : Color.gray.opacity(0.2))
.cornerRadius(4)
}
}
.padding(.horizontal)
.padding(.vertical, 8)
.background(Color(.systemBackground))
}
var gpsColor: Color {
if captureManager.gpsSignal > 0.8 { return .green }
if captureManager.gpsSignal > 0.5 { return .yellow }
return .red
}
var qualityColor: Color {
if captureManager.qualityScore > 0.8 { return .green }
if captureManager.qualityScore > 0.5 { return .yellow }
return .red
}
}
// MARK: - Camera Preview
struct CameraPreviewView: View {
@ObservedObject var captureManager: CaptureManager
var body: some View {
ZStack {
// Kamera-preview (placeholder)
Rectangle()
.fill(Color.black)
// Inspelnings-indikator
if captureManager.isRecording {
VStack {
HStack {
Circle()
.fill(Color.red)
.frame(width: 12, height: 12)
Text("REC")
.font(.caption.bold())
.foregroundColor(.red)
Spacer()
}
.padding()
Spacer()
}
}
// Grid overlay för komposition
GeometryReader { geometry in
Path { path in
let w = geometry.size.width
let h = geometry.size.height
// Vertikala linjer
path.move(to: CGPoint(x: w/3, y: 0))
path.addLine(to: CGPoint(x: w/3, y: h))
path.move(to: CGPoint(x: 2*w/3, y: 0))
path.addLine(to: CGPoint(x: 2*w/3, y: h))
// Horisontella linjer
path.move(to: CGPoint(x: 0, y: h/3))
path.addLine(to: CGPoint(x: w, y: h/3))
path.move(to: CGPoint(x: 0, y: 2*h/3))
path.addLine(to: CGPoint(x: w, y: 2*h/3))
}
.stroke(Color.white.opacity(0.3), lineWidth: 0.5)
}
}
}
}
// MARK: - AI Instruction View
struct AIInstructionView: View {
let instruction: AIInstruction
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Image(systemName: "brain.head.profile")
.foregroundColor(.blue)
Text("AI Instruction")
.font(.caption.bold())
.foregroundColor(.blue)
Spacer()
Text(instruction.priority.uppercased())
.font(.caption2.bold())
.padding(.horizontal, 6)
.padding(.vertical, 2)
.background(priorityColor.opacity(0.2))
.foregroundColor(priorityColor)
.cornerRadius(4)
}
Text(instruction.text)
.font(.body)
Text(instruction.reason)
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 4)
.padding(.horizontal)
}
var priorityColor: Color {
switch instruction.priority {
case "high": return .red
case "medium": return .orange
case "low": return .green
default: return .gray
}
}
}
// MARK: - Control Panel
struct ControlPanelView: View {
@ObservedObject var captureManager: CaptureManager
@Binding var selectedMode: CaptureSession.CaptureMode
var body: some View {
VStack(spacing: 16) {
// Lägesväljare
Picker("Mode", selection: $selectedMode) {
Text("Recording").tag(CaptureSession.CaptureMode.recording)
Text("Guided").tag(CaptureSession.CaptureMode.guided)
Text("Mission").tag(CaptureSession.CaptureMode.mission)
Text("Adaptive").tag(CaptureSession.CaptureMode.adaptive)
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal)
.disabled(captureManager.isRecording)
// Inspelningsknapp
Button(action: {
if captureManager.isRecording {
captureManager.stopSession()
} else {
let config = SessionConfig(
videoQuality: .p1080,
telemetryRate: 10,
uploadMode: .buffered,
missionType: selectedMode == .mission ? "crosswalks" : nil,
area: nil
)
captureManager.startSession(mode: selectedMode, config: config)
}
}) {
ZStack {
Circle()
.fill(captureManager.isRecording ? Color.red : Color.green)
.frame(width: 72, height: 72)
if captureManager.isRecording {
RoundedRectangle(cornerRadius: 4)
.fill(Color.white)
.frame(width: 24, height: 24)
} else {
Circle()
.fill(Color.white)
.frame(width: 60, height: 60)
}
}
}
// Status-text
if let session = captureManager.currentSession {
Text("Mission: \(session.missionId)")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding(.vertical)
.background(Color(.systemBackground))
}
}
// MARK: - Settings View
struct SettingsView: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
List {
Section(header: Text("Video")) {
Picker("Quality", selection: .constant(1)) {
Text("720p").tag(0)
Text("1080p").tag(1)
Text("4K").tag(2)
}
Toggle("Stabilization", isOn: .constant(true))
}
Section(header: Text("Telemetry")) {
Picker("Rate", selection: .constant(10)) {
Text("5 Hz").tag(5)
Text("10 Hz").tag(10)
Text("20 Hz").tag(20)
}
Toggle("IMU", isOn: .constant(true))
Toggle("Compass", isOn: .constant(true))
Toggle("Altimeter", isOn: .constant(true))
}
Section(header: Text("Upload")) {
Picker("Mode", selection: .constant(1)) {
Text("Realtime").tag(0)
Text("Buffered").tag(1)
Text("Manual").tag(2)
}
Toggle("WiFi Only", isOn: .constant(true))
}
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Done") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
}
}
// MARK: - Preview
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}