import SwiftUI struct ContentView: View { @StateObject private var networkService = NetworkService() @State private var targetAngle: Double = 30 @State private var showingConnectionSettings = false @State private var customHost = "" @State private var customPort = "" @State private var streamImage: UIImage? = nil @State private var isStreaming = false @State private var isVideoStreaming = false @State private var isAutoMode = true var body: some View { NavigationView { VStack(spacing: 20) { // Connection Status HStack { Circle() .fill(networkService.isConnected ? Color.green : Color.red) .frame(width: 12, height: 12) Text(networkService.connectionStatus) .font(.headline) Spacer() Button(action: { showingConnectionSettings = true }) { Image(systemName: "gearshape.fill") .font(.title2) .foregroundColor(.blue) } .frame(width: 44, height: 44) } .padding() // Video Stream Area VStack { HStack { Text("Camera Feed") .font(.headline) Spacer() Button(isVideoStreaming ? "Stop Stream" : "Start Stream") { toggleVideoStream() } .font(.caption) .padding(.horizontal, 12) .padding(.vertical, 6) .background(isVideoStreaming ? Color.red : Color.green) .foregroundColor(.white) .cornerRadius(15) } ZStack { Rectangle() .fill(Color.black.opacity(0.1)) .frame(height: 200) .cornerRadius(10) if let image = streamImage { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(height: 200) .cornerRadius(10) } else { VStack { Image(systemName: isVideoStreaming ? "video.fill" : "camera.fill") .font(.largeTitle) .foregroundColor(.gray) Text(isVideoStreaming ? "Live feed - Tap to capture photo" : (isStreaming ? "Capturing photo..." : "Tap to capture photo")) .font(.caption) .foregroundColor(.gray) } } } .onTapGesture { capturePhoto() } } .padding() // Control Panel VStack(spacing: 25) { // Mode Toggle HStack { Text("Mode:") .font(.headline) Spacer() Toggle(isOn: $isAutoMode) { Text(isAutoMode ? "Auto" : "Manual") .font(.headline) .foregroundColor(isAutoMode ? .green : .orange) } .onChange(of: isAutoMode) { _, newValue in let mode = newValue ? "auto" : "manual" let command = LauncherCommand.setMode(mode) networkService.sendCommand(command) } } .padding(.horizontal) // Home Button Button(action: homeDevice) { HStack { Image(systemName: "house.fill") Text("Home Device") } .font(.headline) .foregroundColor(.white) .frame(maxWidth: .infinity) .padding() .background(Color.purple) .cornerRadius(10) } .disabled(!networkService.isConnected) // Angle Control VStack { Text("Target Angle: \(Int(targetAngle))°") .font(.title2) Slider(value: $targetAngle, in: 0...60, step: 1) .accentColor(.blue) } // Aim Controls HStack(spacing: 20) { Button(action: aimLeft) { HStack { Image(systemName: "arrow.left") Text("Aim Left") } .font(.headline) .foregroundColor(.white) .frame(maxWidth: .infinity) .padding() .background(Color.blue) .cornerRadius(10) } .disabled(!networkService.isConnected) Button(action: aimRight) { HStack { Image(systemName: "arrow.right") Text("Aim Right") } .font(.headline) .foregroundColor(.white) .frame(maxWidth: .infinity) .padding() .background(Color.blue) .cornerRadius(10) } .disabled(!networkService.isConnected) } // Fire Button Button(action: fireOreo) { HStack { Image(systemName: "paperplane.fill") Text("FIRE OREO!") } .font(.title) .fontWeight(.bold) .foregroundColor(.white) .frame(maxWidth: .infinity) .padding(.vertical, 15) .background(Color.red) .cornerRadius(10) } .disabled(!networkService.isConnected) } .padding() Spacer() // Connection Button Button(action: toggleConnection) { Text(networkService.isConnected ? "Disconnect" : "Connect to Launcher") .font(.headline) .foregroundColor(.white) .frame(maxWidth: .infinity) .padding() .background(networkService.isConnected ? Color.gray : Color.green) .cornerRadius(10) } .padding() } .navigationTitle("🍪 OCTv2") .navigationBarTitleDisplayMode(.inline) } .sheet(isPresented: $showingConnectionSettings) { ConnectionSettingsView( networkService: networkService, customHost: $customHost, customPort: $customPort ) } } // MARK: - Actions private func toggleConnection() { if networkService.isConnected { networkService.disconnect() } else { let host = customHost.isEmpty ? nil : customHost let port = UInt16(customPort) ?? 0 networkService.connect(host: host ?? "", port: port) } } private func aimLeft() { let command = LauncherCommand.aimLeft() networkService.sendCommand(command) print("Aiming left") } private func aimRight() { let command = LauncherCommand.aimRight() networkService.sendCommand(command) print("Aiming right") } private func fireOreo() { let command = LauncherCommand.fire(angle: targetAngle) networkService.sendCommand(command) print("Firing Oreo at angle: \(targetAngle)°") } private func capturePhoto() { guard networkService.isConnected else { print("Cannot capture photo: Not connected to Pi") return } isStreaming = true let command = LauncherCommand.capturePhoto() networkService.sendCommand(command) networkService.requestPhoto { image in DispatchQueue.main.async { if image == nil { print("Failed to capture photo") } // If we're not video streaming, show the captured photo // If we are streaming, the photo was saved on Pi but video continues if !self.isVideoStreaming { self.streamImage = image } self.isStreaming = false } } print(isVideoStreaming ? "Capturing high-res photo while streaming" : "Capturing photo") } private func homeDevice() { let command = LauncherCommand.home() networkService.sendCommand(command) print("Homing device") } private func toggleVideoStream() { if isVideoStreaming { networkService.stopVideoStream() isVideoStreaming = false streamImage = nil } else { isVideoStreaming = true networkService.startVideoStream { image in self.streamImage = image } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }