Преглед изворни кода

Improved ble device selection view

Jonas Björkert пре 1 година
родитељ
комит
909beab487

+ 0 - 13
LoopFollow/BackgroundRefresh/BT/BLEDevice.swift

@@ -32,17 +32,4 @@ struct BLEDevice: Identifiable, Codable, Equatable {
         self.lastSeen = lastSeen
         self.lastConnected = lastConnected
     }
-
-    func updateLastSeen() -> BLEDevice {
-        var updatedDevice = self
-        updatedDevice.lastSeen = Date()
-        return updatedDevice
-    }
-
-    func updateLastConnected() -> BLEDevice {
-        var updatedDevice = self
-        updatedDevice.lastConnected = Date()
-        updatedDevice.isConnected = true
-        return updatedDevice
-    }
 }

+ 20 - 15
LoopFollow/BackgroundRefresh/BT/BLEDeviceSelectionView.swift

@@ -12,23 +12,28 @@ struct BLEDeviceSelectionView: View {
 
     var body: some View {
         VStack {
-            Text("Scanning for \(selectedFilter.rawValue)")
-                .font(.headline)
-
             List {
-                ForEach(bleManager.devices.filter { selectedFilter.matches($0) && !isSelected($0) }, id: \.id) { device in
-                    HStack {
-                        VStack(alignment: .leading) {
-                            Text(device.name ?? "Unknown")
-                            Text("\(device.rssi) dBm")
-                                .foregroundColor(.secondary)
-                                .font(.footnote)
+                if bleManager.devices.filter({ selectedFilter.matches($0) && !isSelected($0) }).isEmpty {
+                    Text("No devices found yet. They'll appear here when discovered.")
+                        .foregroundColor(.secondary)
+                        .multilineTextAlignment(.center)
+                        .padding()
+                } else {
+                    ForEach(bleManager.devices.filter { selectedFilter.matches($0) && !isSelected($0) }, id: \.id) { device in
+                        HStack {
+                            VStack(alignment: .leading) {
+                                Text(device.name ?? "Unknown")
+
+                                Text("RSSI: \(device.rssi) dBm")
+                                    .foregroundColor(.secondary)
+                                    .font(.footnote)
+                            }
+                            Spacer()
+                        }
+                        .contentShape(Rectangle())
+                        .onTapGesture {
+                            onSelectDevice(device)
                         }
-                        Spacer()
-                    }
-                    .contentShape(Rectangle())
-                    .onTapGesture {
-                        onSelectDevice(device)
                     }
                 }
             }

+ 37 - 7
LoopFollow/BackgroundRefresh/BT/BLEManager.swift

@@ -23,10 +23,18 @@ class BLEManager: NSObject, ObservableObject {
             queue: .main
         )
         if let device = Storage.shared.selectedBLEDevice.value {
+            devices.append(device)
+            findAndUpdateDevice(with: device.id.uuidString) { device in
+                device.rssi = 0
+            }
             connect(device: device)
         }
     }
 
+    func getSelectedDevice() -> BLEDevice? {
+        return devices.first { $0.id == Storage.shared.selectedBLEDevice.value?.id }
+    }
+
     func startScanning() {
         guard centralManager.state == .poweredOn else {
             LogManager.shared.log(category: .bluetooth, message: "Not powered on, cannot start scan.")
@@ -53,12 +61,17 @@ class BLEManager: NSObject, ObservableObject {
             Storage.shared.backgroundRefreshType.value = matchedType
             Storage.shared.selectedBLEDevice.value = device
 
+            findAndUpdateDevice(with: device.id.uuidString) { device in
+                device.isConnected = false
+                device.lastConnected = nil
+            }
+
             switch matchedType {
             case .dexcom:
-                activeDevice = DexcomHeartbeatBluetoothDevice(bluetoothDeviceDelegate: self)
+                activeDevice = DexcomHeartbeatBluetoothDevice(address: device.id.uuidString, name: device.name, bluetoothDeviceDelegate: self)
                 activeDevice?.connect()
             case .rileyLink:
-                activeDevice = RileyLinkHeartbeatBluetoothDevice(bluetoothDeviceDelegate: self)
+                activeDevice = RileyLinkHeartbeatBluetoothDevice(address: device.id.uuidString, name: device.name, bluetoothDeviceDelegate: self)
                 activeDevice?.connect()
             case .silentTune, .none:
                 return
@@ -82,12 +95,16 @@ class BLEManager: NSObject, ObservableObject {
 
     private func addOrUpdateDevice(_ device: BLEDevice) {
         if let idx = devices.firstIndex(where: { $0.id == device.id }) {
-            devices[idx] = device.updateLastSeen()
+            var updatedDevice = devices[idx]
+            updatedDevice.rssi = device.rssi
+            updatedDevice.lastSeen = Date()
+            devices[idx] = updatedDevice
         } else {
             var newDevice = device
             newDevice.lastSeen = Date()
             devices.append(newDevice)
         }
+
         devices = devices
     }
 
@@ -131,23 +148,36 @@ extension BLEManager: CBCentralManagerDelegate {
 
         addOrUpdateDevice(device)
     }
+
+    func findAndUpdateDevice(with deviceAddress: String, update: (inout BLEDevice) -> Void) {
+        if let idx = devices.firstIndex(where: { $0.id.uuidString == deviceAddress }) {
+            var device = devices[idx]
+            update(&device)
+            devices[idx] = device
+
+            devices = devices
+        } else {
+            print("Device not found in devices array for update")
+        }
+    }
 }
 
 extension BLEManager: BluetoothDeviceDelegate {
     func didConnectTo(bluetoothDevice: BluetoothDevice) {
         LogManager.shared.log(category: .bluetooth, message: "Connected to: \(bluetoothDevice.deviceName ?? "Unknown")")
 
-        if var device = Storage.shared.selectedBLEDevice.value {
+        findAndUpdateDevice(with: bluetoothDevice.deviceAddress) { device in
             device.isConnected = true
-            Storage.shared.selectedBLEDevice.value = device.updateLastConnected()
+            device.lastConnected = Date()
         }
     }
 
     func didDisconnectFrom(bluetoothDevice: BluetoothDevice) {
         LogManager.shared.log(category: .bluetooth, message: "Disconnect from: \(bluetoothDevice.deviceName ?? "Unknown")")
-        if var device = Storage.shared.selectedBLEDevice.value {
+
+        findAndUpdateDevice(with: bluetoothDevice.deviceAddress) { device in
             device.isConnected = false
-            Storage.shared.selectedBLEDevice.value = device
+            device.lastConnected = Date()
         }
     }
 

+ 1 - 12
LoopFollow/BackgroundRefresh/BT/Devices/DexcomHeartbeatBluetoothDevice.swift

@@ -16,18 +16,7 @@ class DexcomHeartbeatBluetoothDevice: BluetoothDevice {
     private let CBUUID_Advertisement_G7 = "FEBC"
     private let CBUUID_ReceiveCharacteristic_G7 = "F8083535-849E-531C-C594-30F1F86A4EA5"
 
-    private var timeStampOfLastHeartBeat: Date
-
-    init(bluetoothDeviceDelegate: BluetoothDeviceDelegate) {
-        guard let selectedDevice = Storage.shared.selectedBLEDevice.value else {
-            fatalError("No selected BLE device found in storage.")
-        }
-
-        let address = selectedDevice.id.uuidString
-        let name = selectedDevice.name
-
-        self.timeStampOfLastHeartBeat = Date(timeIntervalSince1970: 0)
-
+    init(address:String, name:String?, bluetoothDeviceDelegate: BluetoothDeviceDelegate) {
         super.init(
             address: address,
             name: name,

+ 2 - 9
LoopFollow/BackgroundRefresh/BT/Devices/RileyLinkHeartbeatBluetoothDevice.swift

@@ -14,17 +14,10 @@ class RileyLinkHeartbeatBluetoothDevice: BluetoothDevice {
     private let CBUUID_ReceiveCharacteristic_TimerTick: String = "6E6C7910-B89E-43A5-78AF-50C5E2B86F7E"
     private let CBUUID_ReceiveCharacteristic_Data: String = "C842E849-5028-42E2-867C-016ADADA9155"
 
-    init(bluetoothDeviceDelegate: BluetoothDeviceDelegate) {
-        guard let selectedDevice = Storage.shared.selectedBLEDevice.value else {
-            fatalError("No selected BLE device found in storage.")
-        }
-
-        let address = selectedDevice.id.uuidString
-        let deviceName = selectedDevice.name
-
+    init(address:String, name:String?, bluetoothDeviceDelegate: BluetoothDeviceDelegate) {
         super.init(
             address: address,
-            name: deviceName,
+            name: name,
             CBUUID_Advertisement: nil,
             servicesCBUUIDs: [CBUUID(string: CBUUID_Service_RileyLink)],
             CBUUID_ReceiveCharacteristic: CBUUID_ReceiveCharacteristic_TimerTick,

+ 68 - 12
LoopFollow/BackgroundRefresh/BackgroundRefreshSettingsView.swift

@@ -8,9 +8,10 @@ import SwiftUI
 struct BackgroundRefreshSettingsView: View {
     @ObservedObject var viewModel: BackgroundRefreshSettingsViewModel
     @Environment(\.presentationMode) var presentationMode
+    @State private var forceRefresh = false
+    @State private var timer: Timer?
 
     @ObservedObject var bleManager = BLEManager.shared
-    @ObservedObject var selectedBLEDevice = Storage.shared.selectedBLEDevice
 
     var body: some View {
         NavigationView {
@@ -30,6 +31,12 @@ struct BackgroundRefreshSettingsView: View {
                     }
                 }
             }
+            .onAppear {
+                startTimer()
+            }
+            .onDisappear {
+                stopTimer()
+            }
         }
     }
 
@@ -76,7 +83,7 @@ struct BackgroundRefreshSettingsView: View {
 
     @ViewBuilder
     private var selectedDeviceSection: some View {
-        if let storedDevice = selectedBLEDevice.value {
+        if let storedDevice = bleManager.getSelectedDevice() {
             Section(header: Text("Selected Device")) {
                 VStack(alignment: .leading, spacing: 4) {
                     Text(storedDevice.name ?? "Unknown Device")
@@ -84,6 +91,13 @@ struct BackgroundRefreshSettingsView: View {
 
                     deviceConnectionStatus(for: storedDevice)
 
+                    if(storedDevice.rssi != 0)
+                    {
+                        Text("RSSI: \(storedDevice.rssi) dBm")
+                            .foregroundColor(.secondary)
+                            .font(.footnote)
+                    }
+
                     HStack {
                         Spacer()
                         Button(action: {
@@ -98,11 +112,22 @@ struct BackgroundRefreshSettingsView: View {
                 }
                 .padding(.vertical, 8)
             }
+            .id(forceRefresh)
+        }
+    }
+
+    private func formattedTimeString(from seconds: TimeInterval) -> String {
+        if seconds < 60 {
+            return "\(Int(seconds)) seconds"
+        } else {
+            let minutes = Int(seconds / 60)
+            let seconds = Int(seconds.truncatingRemainder(dividingBy: 60))
+            return "\(minutes):\(String(format: "%02d", seconds)) minutes"
         }
     }
 
     private var availableDevicesSection: some View {
-        Section(header: Text("Available Devices")) {
+        Section(header: scanningStatusHeader) {
             BLEDeviceSelectionView(
                 bleManager: bleManager,
                 selectedFilter: viewModel.backgroundRefreshType,
@@ -113,21 +138,52 @@ struct BackgroundRefreshSettingsView: View {
         }
     }
 
+    private var scanningStatusHeader: some View {
+        Text("Scanning for \(viewModel.backgroundRefreshType.rawValue)...")
+            .font(.subheadline)
+            .foregroundColor(.secondary)
+    }
+
     private func deviceConnectionStatus(for device: BLEDevice) -> some View {
+        let expectedConnectionTime: TimeInterval = bleManager.expectedHeartbeatInterval() ?? 300
+        let now = Date()
+        let timeSinceLastConnection = device.isConnected ? 0 : now.timeIntervalSince(device.lastConnected ?? now)
+
         if device.isConnected {
             return Text("Connected")
                 .foregroundColor(.green)
         } else if let lastConnected = device.lastConnected {
-            let date = dateTimeUtils.formattedDate(from: lastConnected)
-            return Text("Last connection: \(date)")
-                .foregroundColor(.orange)
-        } else if let item = bleManager.devices.first(where: { $0.id == device.id }) {
-            let date = dateTimeUtils.formattedDate(from: item.lastSeen)
-            return Text("Last seen: \(date)")
-                .foregroundColor(.orange)
+            let timeRatio = timeSinceLastConnection / expectedConnectionTime
+            let timeString = formattedTimeString(from: timeSinceLastConnection)
+
+            if timeRatio < 1.0 {
+                return Text("Disconnected for \(timeString)")
+                    .foregroundColor(.green)
+            } else if timeRatio <= 1.15 {
+                return Text("Disconnected for \(timeString)")
+                    .foregroundColor(.orange)
+            } else if timeRatio <= 3.0 {
+                return Text("Disconnected for \(timeString)")
+                    .foregroundColor(.red)
+            } else {
+                let date = dateTimeUtils.formattedDate(from: lastConnected)
+                return Text("Last connection: \(date)")
+                    .foregroundColor(.red)
+            }
         } else {
-            return Text("Not found")
-                .foregroundColor(.red)
+            return Text("Reconnecting...")
+                .foregroundColor(.orange)
         }
     }
+
+    private func startTimer() {
+        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
+            self.forceRefresh.toggle()
+        }
+    }
+
+    private func stopTimer() {
+        timer?.invalidate()
+        timer = nil
+    }
 }