| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- 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?
- @State private var deviceToRename: BoundDevice?
- @State private var proposedDeviceName = ""
-
- 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("解绑后该设备将从你的账号名下移除。")
- }
- .alert(
- "给微光取个名字",
- isPresented: Binding(
- get: { deviceToRename != nil },
- set: { if !$0 { deviceToRename = nil } }
- )
- ) {
- TextField("设备名称", text: $proposedDeviceName)
- Button("保存") {
- if let device = deviceToRename {
- bleManager.renameDevice(id: device.id, to: proposedDeviceName)
- }
- deviceToRename = nil
- }
- Button("取消", role: .cancel) {
- deviceToRename = nil
- }
- } message: {
- Text("这个名称只保存在你的 App 中,不会修改设备的蓝牙广播名。")
- }
- }
-
- // 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)
- }
- if let freeStorage = device.freeStorageMB {
- Text("剩余: \(freeStorage) MB")
- .font(.system(size: 11, design: .monospaced))
- .foregroundStyle(Color.secondary)
- }
- }
- }
-
- Spacer()
-
- VStack(spacing: 8) {
- Button {
- proposedDeviceName = device.name
- deviceToRename = device
- } label: {
- Text("重命名")
- .font(.system(size: 12, weight: .regular))
- .foregroundStyle(Color.primary)
- }
- Button {
- deviceToUnbind = device
- } label: {
- Text("解绑")
- .font(.system(size: 12, weight: .regular))
- .foregroundStyle(Color.recordingRed)
- }
- }
- .padding(.horizontal, 10)
- .padding(.vertical, 7)
- .overlay(
- RoundedRectangle(cornerRadius: 6)
- .stroke(Color.primary.opacity(0.2), lineWidth: 1)
- )
- }
- .padding(14)
- .background(Color.cardBackground.opacity(0.35))
- .businessBorder(cornerRadius: 10)
- }
- }
- #Preview {
- DeviceListView(onAddDeviceClicked: {})
- }
|