| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- import SwiftUI
- /// Modal sheet for scanning nearby BLE audio recording devices and binding them to the logged-in user.
- struct DeviceScanView: View {
- @Environment(\.dismiss) private var dismiss
- @ObservedObject var bleManager: BLEManager = .shared
- @ObservedObject var authManager: AuthManager = .shared
-
- @State private var boundSuccessMessage: String?
-
- var body: some View {
- ZStack {
- Color.spaceBlack.ignoresSafeArea()
-
- VStack(spacing: 20) {
- // Top Header
- HStack {
- VStack(alignment: .leading, spacing: 4) {
- Text("搜索与绑定录音设备")
- .font(.system(size: 18, weight: .bold))
- .foregroundStyle(Color.primary)
-
- Text("请确保设备已开启蓝牙广播模式并靠拢 iPhone")
- .font(.system(size: 12))
- .foregroundStyle(Color.secondary)
- }
-
- Spacer()
-
- Button {
- bleManager.stopScanning()
- dismiss()
- } label: {
- Image(systemName: "xmark.circle.fill")
- .font(.system(size: 22))
- .foregroundStyle(Color.secondary.opacity(0.6))
- }
- }
- .padding(.horizontal, 20)
- .padding(.top, 20)
-
- // Scanning Status Indicator
- HStack(spacing: 12) {
- if bleManager.isScanning {
- ProgressView()
- .tint(Color.primary)
- } else {
- Image(systemName: "radiowaves.left.and.right")
- .font(.system(size: 16))
- .foregroundStyle(Color.primary)
- }
-
- Text(bleManager.isScanning ? "正在扫描附近的蓝牙录音设备..." : "扫描已停止")
- .font(.system(size: 13, weight: .medium))
- .foregroundStyle(Color.primary)
-
- Spacer()
-
- Button {
- if bleManager.isScanning {
- bleManager.stopScanning()
- } else {
- bleManager.startScanning()
- }
- } label: {
- Text(bleManager.isScanning ? "停止扫描" : "重新扫描")
- .font(.system(size: 12, weight: .regular))
- .foregroundStyle(Color.primary)
- .padding(.horizontal, 10)
- .padding(.vertical, 6)
- .overlay(
- RoundedRectangle(cornerRadius: 6)
- .stroke(Color.primary.opacity(0.3), lineWidth: 1)
- )
- }
- }
- .padding(14)
- .background(Color.cardBackground.opacity(0.4))
- .businessBorder(cornerRadius: 10)
- .padding(.horizontal, 20)
-
- // Success Toast Banner
- if let successMsg = boundSuccessMessage {
- HStack(spacing: 8) {
- Image(systemName: "checkmark.circle.fill")
- .foregroundStyle(Color.primary)
- Text(successMsg)
- .font(.system(size: 13, weight: .medium))
- .foregroundStyle(Color.primary)
- }
- .padding(.vertical, 10)
- .padding(.horizontal, 16)
- .background(Color.primary.opacity(0.1))
- .cornerRadius(8)
- .transition(.move(edge: .top).combined(with: .opacity))
- }
-
- // Discovered Devices List
- if bleManager.discoveredDevices.isEmpty {
- VStack(spacing: 12) {
- Spacer()
- Image(systemName: "antenna.radiowaves.left.and.right")
- .font(.system(size: 36, weight: .ultraLight))
- .foregroundStyle(Color.secondary.opacity(0.4))
-
- Text("未搜索到可用的蓝牙录音设备")
- .font(.system(size: 13))
- .foregroundStyle(Color.secondary)
- Spacer()
- }
- } else {
- ScrollView {
- VStack(spacing: 12) {
- ForEach(bleManager.discoveredDevices) { device in
- discoveredDeviceRow(device)
- }
- }
- .padding(.horizontal, 20)
- .padding(.bottom, 20)
- }
- }
- }
- }
- .onAppear {
- bleManager.startScanning()
- }
- .onDisappear {
- bleManager.stopScanning()
- }
- }
-
- private func discoveredDeviceRow(_ device: DiscoveredBLEDevice) -> some View {
- let isAlreadyBound = bleManager.boundDevices.contains(where: { $0.id == device.id })
-
- return HStack(spacing: 14) {
- ZStack {
- Circle()
- .fill(Color.primary.opacity(0.06))
- .frame(width: 42, height: 42)
-
- Image(systemName: "mic.fill")
- .font(.system(size: 16))
- .foregroundStyle(Color.primary)
- }
-
- VStack(alignment: .leading, spacing: 4) {
- Text(device.name)
- .font(.system(size: 14, weight: .medium))
- .foregroundStyle(Color.primary)
-
- HStack(spacing: 8) {
- Text("信号强度: \(device.rssi) dBm")
- .font(.system(size: 11, design: .monospaced))
- .foregroundStyle(Color.secondary)
-
- Text("•")
- .font(.system(size: 10))
- .foregroundStyle(Color.secondary)
-
- Text("设备标识: " + String(device.peripheralUUID.prefix(8)))
- .font(.system(size: 11, design: .monospaced))
- .foregroundStyle(Color.secondary)
- }
- }
-
- Spacer()
-
- if isAlreadyBound {
- Text("已绑定")
- .font(.system(size: 12, weight: .regular))
- .foregroundStyle(Color.secondary)
- .padding(.horizontal, 12)
- .padding(.vertical, 6)
- } else {
- Button {
- guard let userId = authManager.currentUser?.id else { return }
- let bound = bleManager.bindDevice(device, userId: userId)
- withAnimation {
- boundSuccessMessage = "已成功绑定设备「\(bound.name)」"
- }
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
- dismiss()
- }
- } label: {
- Text("绑定设备")
- .font(.system(size: 13, weight: .semibold))
- .foregroundStyle(Color.spaceBlack)
- .padding(.horizontal, 14)
- .padding(.vertical, 7)
- .background(Color.primary)
- .cornerRadius(6)
- }
- }
- }
- .padding(14)
- .background(Color.cardBackground.opacity(0.3))
- .businessBorder(cornerRadius: 10)
- }
- }
- #Preview {
- DeviceScanView()
- }
|