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