diff --git a/Sources/SpeziDevices/PairedDevices.swift b/Sources/SpeziDevices/PairedDevices.swift index 37c92b5..8410edd 100644 --- a/Sources/SpeziDevices/PairedDevices.swift +++ b/Sources/SpeziDevices/PairedDevices.swift @@ -90,7 +90,7 @@ import SwiftUI /// - ``isConnected(device:)`` /// - ``updateName(for:name:)`` @Observable -public final class PairedDevices: @unchecked Sendable { +public final class PairedDevices { /// Determines if the device discovery sheet should be presented. @MainActor public var shouldPresentDevicePairing = false @@ -120,7 +120,7 @@ public final class PairedDevices: @unchecked Sendable { @Dependency(Bluetooth.self) @ObservationIgnored private var bluetooth: Bluetooth? @Dependency(ConfigureTipKit.self) @ObservationIgnored private var tipKit - private var modelContainer: ModelContainer? + @MainActor private var modelContainer: ModelContainer? /// Determine if Bluetooth is scanning to discovery nearby devices. /// @@ -142,6 +142,7 @@ public final class PairedDevices: @unchecked Sendable { /// Configures the Module. @_documentation(visibility: internal) + @MainActor public func configure() { if bluetooth == nil { self.logger.warning("PairedDevices Module initialized without Bluetooth dependency!") @@ -363,7 +364,7 @@ public final class PairedDevices: @unchecked Sendable { } -extension PairedDevices: Module, EnvironmentAccessible, DefaultInitializable {} +extension PairedDevices: Module, EnvironmentAccessible, DefaultInitializable, @unchecked Sendable {} // MARK: - Device Pairing diff --git a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices.png b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices.png index 48c0fbe..1a311c4 100644 Binary files a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices.png and b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices.png differ diff --git a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList.png b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList.png new file mode 100644 index 0000000..af6a153 Binary files /dev/null and b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList.png differ diff --git a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList.png.license b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList.png.license new file mode 100644 index 0000000..a648e99 --- /dev/null +++ b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList.png.license @@ -0,0 +1,5 @@ +This source file is part of the Stanford Spezi open-source project + +SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) + +SPDX-License-Identifier: MIT diff --git a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList~dark.png b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList~dark.png new file mode 100644 index 0000000..13cc1f6 Binary files /dev/null and b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList~dark.png differ diff --git a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList~dark.png.license b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList~dark.png.license new file mode 100644 index 0000000..a648e99 --- /dev/null +++ b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevicesList~dark.png.license @@ -0,0 +1,5 @@ +This source file is part of the Stanford Spezi open-source project + +SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) + +SPDX-License-Identifier: MIT diff --git a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices~dark.png b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices~dark.png index dc82569..b0efad3 100644 Binary files a/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices~dark.png and b/Sources/SpeziDevicesUI/SpeziDevicesUI.docc/Resources/PairedDevices~dark.png differ diff --git a/Sources/SpeziOmron/Devices/OmronBloodPressureCuff.swift b/Sources/SpeziOmron/Devices/OmronBloodPressureCuff.swift index 6d1e953..9b4ab0f 100644 --- a/Sources/SpeziOmron/Devices/OmronBloodPressureCuff.swift +++ b/Sources/SpeziOmron/Devices/OmronBloodPressureCuff.swift @@ -135,6 +135,7 @@ extension OmronBloodPressureCuff { systolic: MedFloat16 = 103, diastolic: MedFloat16 = 64, pulseRate: MedFloat16 = 62, + name: String = "BP5250", state: PeripheralState = .disconnected, nearby: Bool = true, manufacturerData: OmronManufacturerData = OmronManufacturerData(pairingMode: .pairingMode, users: [ @@ -145,7 +146,7 @@ extension OmronBloodPressureCuff { let device = OmronBloodPressureCuff() device.$id.inject(UUID()) - device.$name.inject("BP5250") + device.$name.inject(name) device.$state.inject(state) device.$nearby.inject(nearby) diff --git a/Tests/UITests/TestApp/DevicesTestView.swift b/Tests/UITests/TestApp/DevicesTestView.swift index a99828f..d765f81 100644 --- a/Tests/UITests/TestApp/DevicesTestView.swift +++ b/Tests/UITests/TestApp/DevicesTestView.swift @@ -36,47 +36,71 @@ struct DevicesTestView: View { @State private var didRegister = false @State private var device = MockDevice.createMockDevice() @State private var weightScale = OmronWeightScale.createMockDevice(manufacturerData: .omronManufacturerData(mode: .transferMode)) - @State private var bloodPressureCuff = OmronBloodPressureCuff.createMockDevice(manufacturerData: .omronManufacturerData(mode: .transferMode)) + @State private var bloodPressureCuffBP5250 = OmronBloodPressureCuff.createMockDevice( + manufacturerData: .omronManufacturerData(mode: .transferMode) + ) + @State private var bloodPressureCuffBP7000 = OmronBloodPressureCuff.createMockDevice( + name: "BP7000", + manufacturerData: .omronManufacturerData(mode: .transferMode) + ) @State private var viewState: ViewState = .idle + @ToolbarContentBuilder @MainActor private var toolbarContent: some ToolbarContent { + ToolbarItemGroup(placement: .secondaryAction) { + Button("Discover Device", systemImage: "plus.rectangle.fill.on.rectangle.fill") { + device.isInPairingMode = true + device.$advertisementData.inject(AdvertisementData()) // trigger onChange advertisement + } + AsyncButton(state: $viewState) { + try await device.connect() + try await weightScale.connect() + try await bloodPressureCuffBP5250.connect() + try await bloodPressureCuffBP7000.connect() + } label: { + Label("Connect", systemImage: "cable.connector") + } + AsyncButton { + await device.disconnect() + await weightScale.disconnect() + await bloodPressureCuffBP5250.disconnect() + await bloodPressureCuffBP7000.disconnect() + } label: { + Label("Disconnect", systemImage: "cable.connector.slash") + } + + omronDevicesMenu + } + } + + @MainActor private var omronDevicesMenu: some View { + Menu("Omron Devices", systemImage: "heart.text.square") { + Button("Discover Weight Scale", systemImage: "scalemass.fill") { + weightScale.$advertisementData.inject(AdvertisementData( + manufacturerData: OmronManufacturerData.omronManufacturerData(mode: .pairingMode).encode() + )) + } + Menu("Discover Blood Pressure Cuff", systemImage: "heart.fill") { + Button("BP 5250") { + bloodPressureCuffBP5250.$advertisementData.inject(AdvertisementData( + manufacturerData: OmronManufacturerData.omronManufacturerData(mode: .pairingMode).encode() + )) + } + + Button("BP 7000") { + bloodPressureCuffBP7000.$advertisementData.inject(AdvertisementData( + manufacturerData: OmronManufacturerData.omronManufacturerData(mode: .pairingMode).encode() + )) + } + } + } + } + var body: some View { NavigationStack { DevicesView(appName: "Example", pairingHint: "Enable pairing mode on the device.") .toolbar { - ToolbarItemGroup(placement: .secondaryAction) { - Button("Discover Device", systemImage: "plus.rectangle.fill.on.rectangle.fill") { - device.isInPairingMode = true - device.$advertisementData.inject(AdvertisementData()) // trigger onChange advertisement - } - AsyncButton(state: $viewState) { - try await device.connect() - try await weightScale.connect() - try await bloodPressureCuff.connect() - } label: { - Label("Connect", systemImage: "cable.connector") - } - AsyncButton { - await device.disconnect() - await weightScale.disconnect() - await bloodPressureCuff.disconnect() - } label: { - Label("Disconnect", systemImage: "cable.connector.slash") - } - - Menu("Omron Devices", systemImage: "heart.text.square") { - Button("Discover Weight Scale", systemImage: "scalemass.fill") { - weightScale.$advertisementData.inject(AdvertisementData( - manufacturerData: OmronManufacturerData.omronManufacturerData(mode: .pairingMode).encode() - )) - } - Button("Discovery Blood Pressure Cuff", systemImage: "heart.fill") { - bloodPressureCuff.$advertisementData.inject(AdvertisementData( - manufacturerData: OmronManufacturerData.omronManufacturerData(mode: .pairingMode).encode() - )) - } - } - } + toolbarContent } } .viewStateAlert(state: $viewState) @@ -87,16 +111,23 @@ struct DevicesTestView: View { moduleLoading.loadMockDevice(device) moduleLoading.loadMockDevice(weightScale) - moduleLoading.loadMockDevice(bloodPressureCuff) + moduleLoading.loadMockDevice(bloodPressureCuffBP5250) + moduleLoading.loadMockDevice(bloodPressureCuffBP7000) // simulator this being called in the configure method of the device pairedDevices.configure(device: device, accessing: device.$state, device.$advertisementData, device.$nearby) pairedDevices.configure(device: weightScale, accessing: weightScale.$state, weightScale.$advertisementData, weightScale.$nearby) pairedDevices.configure( - device: bloodPressureCuff, - accessing: bloodPressureCuff.$state, - bloodPressureCuff.$advertisementData, - bloodPressureCuff.$nearby + device: bloodPressureCuffBP5250, + accessing: bloodPressureCuffBP5250.$state, + bloodPressureCuffBP5250.$advertisementData, + bloodPressureCuffBP5250.$nearby + ) + pairedDevices.configure( + device: bloodPressureCuffBP7000, + accessing: bloodPressureCuffBP7000.$state, + bloodPressureCuffBP7000.$advertisementData, + bloodPressureCuffBP7000.$nearby ) didRegister = true }