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