import SwiftUI /// 主界面:未连接时显示设置面板,连接成功后显示远程控制面板。 struct ContentView: View { @StateObject private var viewModel = ControllerViewModel() var body: some View { ZStack { if viewModel.isControlling { ControlPanelView(viewModel: viewModel) } else { SetupPanelView(viewModel: viewModel) } } .sheet(isPresented: $viewModel.showAuthSheet) { AuthSheetView { authType, authValue in viewModel.connect(authType: authType, authValue: authValue) } } .alert("提示", isPresented: Binding( get: { viewModel.alertMessage != nil }, set: { if !$0 { viewModel.alertMessage = nil } })) { Button("确定", role: .cancel) { viewModel.alertMessage = nil } } message: { Text(viewModel.alertMessage ?? "") } } } // MARK: - 设置面板 private struct SetupPanelView: View { @ObservedObject var viewModel: ControllerViewModel var body: some View { NavigationView { Form { Section("本机信息") { HStack { Text("本机设备 ID") Spacer() Text(viewModel.myDeviceId) .font(.system(.body, design: .monospaced)) .foregroundColor(.secondary) .textSelection(.enabled) } } Section("连接设置") { TextField("信令服务器地址 (wss://...)", text: $viewModel.serverUrl) .keyboardType(.URL) .autocapitalization(.none) .disableAutocorrection(true) TextField("目标设备 ID", text: $viewModel.targetDeviceId) .autocapitalization(.allCharacters) .disableAutocorrection(true) } Section { Button { viewModel.requestConnect() } label: { HStack { Spacer() if viewModel.isConnecting { ProgressView() .padding(.trailing, 8) } Text(viewModel.isConnecting ? "连接中..." : "连接") .fontWeight(.semibold) Spacer() } } .disabled(viewModel.isConnecting) if viewModel.isConnecting { Button(role: .destructive) { viewModel.disconnect() } label: { HStack { Spacer() Text("取消") Spacer() } } } } footer: { Text(viewModel.statusText) } } .navigationTitle("WebRTC 控制端") } .navigationViewStyle(.stack) } } // MARK: - 控制面板 private struct ControlPanelView: View { @ObservedObject var viewModel: ControllerViewModel var body: some View { VStack(spacing: 0) { topBar videoArea statsView navButtons } .background(Color.black.ignoresSafeArea()) } private var topBar: some View { HStack(spacing: 12) { Text(viewModel.statusText) .font(.footnote) .foregroundColor(.green) .lineLimit(1) Spacer() // 串流模式切换(WebRTC / 自编码) Toggle(isOn: Binding( get: { viewModel.streamMode == .selfCodec }, set: { viewModel.toggleStreamMode($0) })) { Text("自编码") .font(.footnote) .foregroundColor(.white) } .toggleStyle(.switch) .fixedSize() // 分辨率选择 Menu { ForEach(ResolutionOption.all) { option in Button { viewModel.selectResolution(option) } label: { if option == viewModel.selectedResolution { Label(option.title, systemImage: "checkmark") } else { Text(option.title) } } } } label: { Label(viewModel.selectedResolution.title, systemImage: "rectangle.compress.vertical") .font(.footnote) } Button(role: .destructive) { viewModel.disconnect() } label: { Text("断开") .font(.footnote) .fontWeight(.semibold) } .buttonStyle(.borderedProminent) .tint(.red) .controlSize(.small) } .padding(.horizontal, 12) .padding(.vertical, 6) .background(Color(white: 0.1)) } /// 视频区域:按远端画面宽高比自适应,触控层与画面精确对齐 private var videoArea: some View { GeometryReader { _ in ZStack { Color.black ZStack { // WebRTC 视频渲染(UIKit RTCMTLVideoView) RemoteVideoView(videoView: viewModel.remoteVideoView) .opacity(viewModel.streamMode == .webrtc ? 1 : 0) // 自编码 H.264 硬解渲染(UIKit AVSampleBufferDisplayLayer) SelfCodecDisplayViewRepresentable(view: viewModel.selfCodecView) .opacity(viewModel.streamMode == .selfCodec ? 1 : 0) // UIKit 触控捕获层 TouchOverlay( onMotionEvent: { action, x, y in viewModel.sendMotionEvent(action: action, x: x, y: y) }, onSwipe: { x1, y1, x2, y2, duration in viewModel.sendSwipe(x1: x1, y1: y1, x2: x2, y2: y2, duration: duration) }) } .aspectRatio(viewModel.videoAspect, contentMode: .fit) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } private var statsView: some View { HStack { Text(viewModel.statsText) .font(.system(size: 11, design: .monospaced)) .foregroundColor(Color(white: 0.75)) .multilineTextAlignment(.leading) Spacer() } .padding(.horizontal, 12) .padding(.vertical, 4) .background(Color(white: 0.08)) } private var navButtons: some View { HStack(spacing: 24) { navButton(title: "返回", systemImage: "arrow.uturn.backward") { viewModel.sendKey(ControllerViewModel.AndroidKey.back) } navButton(title: "主页", systemImage: "circle") { viewModel.sendKey(ControllerViewModel.AndroidKey.home) } navButton(title: "多任务", systemImage: "square.on.square") { viewModel.sendKey(ControllerViewModel.AndroidKey.appSwitch) } } .padding(.vertical, 8) .frame(maxWidth: .infinity) .background(Color(white: 0.1)) } private func navButton(title: String, systemImage: String, action: @escaping () -> Void) -> some View { Button(action: action) { VStack(spacing: 2) { Image(systemName: systemImage) .font(.system(size: 18)) Text(title) .font(.caption2) } .foregroundColor(.white) .frame(width: 64) } } }