import SwiftUI /// Component embedded in ProfileView displaying user's bound BLE recording devices and empty state. struct DeviceListView: View { @ObservedObject var bleManager: BLEManager = .shared @ObservedObject var authManager: AuthManager = .shared var onAddDeviceClicked: () -> Void @State private var deviceToUnbind: BoundDevice? private var userBoundDevices: [BoundDevice] { guard let userId = authManager.currentUser?.id else { return [] } return bleManager.devices(forUserId: userId) } var body: some View { VStack(alignment: .leading, spacing: 14) { // Header Row HStack { HStack(spacing: 8) { Image(systemName: "mic.badge.plus") .font(.system(size: 14, weight: .light)) .foregroundStyle(Color.secondary) Text("我的录音设备") .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Color.primary) } Spacer() Button { onAddDeviceClicked() } label: { HStack(spacing: 4) { Image(systemName: "plus") .font(.system(size: 11, weight: .bold)) Text("添加设备") .font(.system(size: 12, weight: .medium)) } .foregroundStyle(Color.primary) .padding(.horizontal, 10) .padding(.vertical, 5) .background(Color.primary.opacity(0.08)) .cornerRadius(6) } } // Content State if userBoundDevices.isEmpty { emptyDeviceCard } else { VStack(spacing: 10) { ForEach(userBoundDevices) { device in boundDeviceCard(device) } } } } .confirmationDialog( "确定要解绑设备「\(deviceToUnbind?.name ?? "")」吗?", isPresented: Binding( get: { deviceToUnbind != nil }, set: { if !$0 { deviceToUnbind = nil } } ), titleVisibility: .visible ) { Button("解绑设备", role: .destructive) { if let dev = deviceToUnbind { bleManager.unbindDevice(id: dev.id) } } Button("取消", role: .cancel) { } } message: { Text("解绑后该设备将从你的账号名下移除。") } } // MARK: - Empty Device Card private var emptyDeviceCard: some View { VStack(spacing: 14) { ZStack { Circle() .fill(Color.primary.opacity(0.04)) .frame(width: 56, height: 56) Image(systemName: "mic.slash") .font(.system(size: 24, weight: .light)) .foregroundStyle(Color.secondary.opacity(0.6)) } VStack(spacing: 4) { Text("暂未绑定录音设备") .font(.system(size: 14, weight: .medium)) .foregroundStyle(Color.primary) Text("通过低功耗蓝牙绑定设备后,可同步硬件录音及状态管理") .font(.system(size: 11)) .foregroundStyle(Color.secondary) .multilineTextAlignment(.center) } Button { onAddDeviceClicked() } label: { HStack(spacing: 6) { Image(systemName: "wave.3.right") .font(.system(size: 12)) Text("搜索并绑定蓝牙设备") .font(.system(size: 13, weight: .medium)) } .foregroundStyle(Color.spaceBlack) .padding(.horizontal, 16) .padding(.vertical, 9) .background(Color.primary) .cornerRadius(6) } .padding(.top, 4) } .frame(maxWidth: .infinity) .padding(.vertical, 24) .padding(.horizontal, 16) .background(Color.cardBackground.opacity(0.2)) .businessBorder(cornerRadius: 10) } // MARK: - Bound Device Card private func boundDeviceCard(_ device: BoundDevice) -> some View { HStack(spacing: 14) { // Icon ZStack { RoundedRectangle(cornerRadius: 8) .fill(Color.primary.opacity(0.06)) .frame(width: 44, height: 44) Image(systemName: "mic.fill") .font(.system(size: 18)) .foregroundStyle(Color.primary) } // Device Details VStack(alignment: .leading, spacing: 4) { HStack(spacing: 8) { Text(device.name) .font(.system(size: 14, weight: .semibold)) .foregroundStyle(Color.primary) // Connection Badge HStack(spacing: 4) { Circle() .fill(device.isConnected ? Color.green : Color.gray) .frame(width: 6, height: 6) Text(device.isConnected ? "已连接" : "未连接") .font(.system(size: 10, weight: .medium)) .foregroundStyle(device.isConnected ? Color.green : Color.secondary) } } HStack(spacing: 12) { if let battery = device.batteryLevel { HStack(spacing: 3) { Image(systemName: "battery.75") .font(.system(size: 10)) Text("\(battery)%") .font(.system(size: 11, design: .monospaced)) } .foregroundStyle(Color.secondary) } if let fw = device.firmwareVersion { Text("固件: \(fw)") .font(.system(size: 11, design: .monospaced)) .foregroundStyle(Color.secondary) } } } Spacer() // Unbind Button Button { deviceToUnbind = device } label: { Text("解绑") .font(.system(size: 12, weight: .regular)) .foregroundStyle(Color.recordingRed) .padding(.horizontal, 10) .padding(.vertical, 5) .overlay( RoundedRectangle(cornerRadius: 6) .stroke(Color.recordingRed.opacity(0.3), lineWidth: 1) ) } } .padding(14) .background(Color.cardBackground.opacity(0.35)) .businessBorder(cornerRadius: 10) } } #Preview { DeviceListView(onAddDeviceClicked: {}) }