mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-22 00:44:26 +01:00
Implement screens for DFU
This commit is contained in:
@@ -279,11 +279,13 @@ fun HomeView(callback: (NavDestination) -> Unit) {
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
FeatureButton(
|
||||
R.drawable.ic_uart, R.string.uart_module,
|
||||
R.drawable.ic_dfu, R.string.dfu_module,
|
||||
R.string.uart_module_full
|
||||
) { callback(NavDestination.DFU) }
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ class HomeViewModel @Inject constructor(
|
||||
) : ViewModel() {
|
||||
|
||||
fun onDeviceSelected(device: DiscoveredBluetoothDevice) {
|
||||
deviceHolder.attachDevice(device.device)
|
||||
deviceHolder.attachDevice(device)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ dependencies {
|
||||
|
||||
implementation libs.nordic.ble.common
|
||||
implementation libs.nordic.log
|
||||
implementation libs.nordic.ui.scanner
|
||||
|
||||
implementation libs.lifecycle.service
|
||||
implementation libs.localbroadcastmanager
|
||||
|
||||
@@ -54,7 +54,7 @@ abstract class BleProfileService : LifecycleService() {
|
||||
* @return bluetooth device
|
||||
*/
|
||||
private val bluetoothDevice: BluetoothDevice by lazy {
|
||||
bluetoothDeviceHolder.device ?: throw IllegalArgumentException(
|
||||
bluetoothDeviceHolder.device?.device ?: throw IllegalArgumentException(
|
||||
"No device associated with the application."
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class SelectedBluetoothDeviceHolder @Inject constructor() {
|
||||
|
||||
var device: BluetoothDevice? = null
|
||||
var device: DiscoveredBluetoothDevice? = null
|
||||
private set
|
||||
|
||||
fun isBondingRequired(): Boolean {
|
||||
return device?.bondState == BluetoothDevice.BOND_NONE
|
||||
}
|
||||
|
||||
fun bondDevice() {
|
||||
device?.createBond()
|
||||
}
|
||||
|
||||
fun attachDevice(newDevice: BluetoothDevice) {
|
||||
fun attachDevice(newDevice: DiscoveredBluetoothDevice) {
|
||||
device = newDevice
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ dependencies {
|
||||
implementation libs.nordic.ble.common
|
||||
|
||||
implementation libs.nordic.log
|
||||
implementation libs.nordic.ui.scanner
|
||||
|
||||
implementation libs.bundles.compose
|
||||
implementation libs.androidx.core
|
||||
|
||||
@@ -8,7 +8,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class BPSDataHolder @Inject constructor() {
|
||||
internal class BPSRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(BPSData())
|
||||
val data: StateFlow<BPSData> = _data
|
||||
@@ -31,7 +31,7 @@ import no.nordicsemi.android.ble.common.callback.bps.BloodPressureMeasurementDat
|
||||
import no.nordicsemi.android.ble.common.callback.bps.IntermediateCuffPressureDataCallback
|
||||
import no.nordicsemi.android.ble.common.profile.bp.BloodPressureTypes
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import no.nordicsemi.android.bps.data.BPSDataHolder
|
||||
import no.nordicsemi.android.bps.data.BPSRepository
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import java.util.*
|
||||
@@ -50,7 +50,7 @@ private val ICP_CHARACTERISTIC_UUID = UUID.fromString("00002A36-0000-1000-8000-0
|
||||
@Singleton
|
||||
internal class BPSManager @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val dataHolder: BPSDataHolder
|
||||
private val dataHolder: BPSRepository
|
||||
) : BatteryManager(context) {
|
||||
|
||||
private var bpmCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.bps.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.bps.data.BPSDataHolder
|
||||
import no.nordicsemi.android.bps.data.BPSRepository
|
||||
import no.nordicsemi.android.bps.repository.BPSManager
|
||||
import no.nordicsemi.android.bps.view.BPSScreenViewEvent
|
||||
import no.nordicsemi.android.bps.view.DisconnectEvent
|
||||
@@ -14,7 +14,7 @@ import javax.inject.Inject
|
||||
internal class BPSViewModel @Inject constructor(
|
||||
private val bpsManager: BPSManager,
|
||||
private val deviceHolder: SelectedBluetoothDeviceHolder,
|
||||
private val dataHolder: BPSDataHolder
|
||||
private val dataHolder: BPSRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
@@ -27,7 +27,7 @@ internal class BPSViewModel @Inject constructor(
|
||||
|
||||
fun connectDevice() {
|
||||
deviceHolder.device?.let {
|
||||
bpsManager.connect(it)
|
||||
bpsManager.connect(it.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.enqueue()
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class CGMDataHolder @Inject constructor() {
|
||||
internal class CGMRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(CGMData())
|
||||
val data: StateFlow<CGMData> = _data.asStateFlow()
|
||||
@@ -57,7 +57,7 @@ private val RACP_UUID = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
internal class CGMManager(
|
||||
context: Context,
|
||||
private val dataHolder: CGMDataHolder
|
||||
private val repository: CGMRepository
|
||||
) : BatteryManager(context) {
|
||||
|
||||
private var cgmStatusCharacteristic: BluetoothGattCharacteristic? = null
|
||||
@@ -83,7 +83,7 @@ internal class CGMManager(
|
||||
private var sessionStartTime: Long = 0
|
||||
|
||||
override fun onBatteryLevelChanged(batteryLevel: Int) {
|
||||
dataHolder.emitNewBatteryLevel(batteryLevel)
|
||||
repository.emitNewBatteryLevel(batteryLevel)
|
||||
}
|
||||
|
||||
override fun getGattCallback(): BatteryManagerGattCallback {
|
||||
@@ -168,7 +168,7 @@ internal class CGMManager(
|
||||
sessionStartTime + timeOffset * 60000L // Sequence number is in minutes since Start Session
|
||||
val record = CGMRecord(timeOffset, glucoseConcentration, timestamp)
|
||||
records.put(record.sequenceNumber, record)
|
||||
dataHolder.emitNewRecords(records.toList())
|
||||
repository.emitNewRecords(records.toList())
|
||||
}
|
||||
|
||||
override fun onContinuousGlucoseMeasurementReceivedWithCrcError(
|
||||
@@ -235,10 +235,10 @@ internal class CGMManager(
|
||||
@RecordAccessControlPointCallback.RACPOpCode requestCode: Int
|
||||
) {
|
||||
when (requestCode) {
|
||||
RecordAccessControlPointCallback.RACP_OP_CODE_ABORT_OPERATION -> dataHolder.setRequestStatus(RequestStatus.ABORTED)
|
||||
RecordAccessControlPointCallback.RACP_OP_CODE_ABORT_OPERATION -> repository.setRequestStatus(RequestStatus.ABORTED)
|
||||
else -> {
|
||||
recordAccessRequestInProgress = false
|
||||
dataHolder.setRequestStatus(RequestStatus.SUCCESS)
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,7 +248,7 @@ internal class CGMManager(
|
||||
@RecordAccessControlPointCallback.RACPOpCode requestCode: Int
|
||||
) {
|
||||
recordAccessRequestInProgress = false
|
||||
dataHolder.setRequestStatus(RequestStatus.SUCCESS)
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
}
|
||||
|
||||
override fun onNumberOfRecordsReceived(
|
||||
@@ -274,7 +274,7 @@ internal class CGMManager(
|
||||
}
|
||||
} else {
|
||||
recordAccessRequestInProgress = false
|
||||
dataHolder.setRequestStatus(RequestStatus.SUCCESS)
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,9 +285,9 @@ internal class CGMManager(
|
||||
) {
|
||||
log(Log.WARN, "Record Access operation failed (error $errorCode)")
|
||||
if (errorCode == RecordAccessControlPointCallback.RACP_ERROR_OP_CODE_NOT_SUPPORTED) {
|
||||
dataHolder.setRequestStatus(RequestStatus.NOT_SUPPORTED)
|
||||
repository.setRequestStatus(RequestStatus.NOT_SUPPORTED)
|
||||
} else {
|
||||
dataHolder.setRequestStatus(RequestStatus.FAILED)
|
||||
repository.setRequestStatus(RequestStatus.FAILED)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -382,7 +382,7 @@ internal class CGMManager(
|
||||
fun requestLastRecord() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
@@ -398,7 +398,7 @@ internal class CGMManager(
|
||||
fun requestFirstRecord() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
@@ -426,7 +426,7 @@ internal class CGMManager(
|
||||
fun requestAllRecords() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
@@ -445,7 +445,7 @@ internal class CGMManager(
|
||||
if (records.size() == 0) {
|
||||
requestAllRecords()
|
||||
} else {
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
|
||||
// Obtain the last sequence number
|
||||
val sequenceNumber = records.keyAt(records.size() - 1) + 1
|
||||
@@ -468,7 +468,7 @@ internal class CGMManager(
|
||||
fun deleteAllRecords() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.deleteAllStoredRecords()
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.cgms.data.CGMDataHolder
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.cgms.data.WorkingMode
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
@@ -14,7 +14,7 @@ import javax.inject.Inject
|
||||
internal class CGMService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: CGMDataHolder
|
||||
lateinit var dataHolder: CGMRepository
|
||||
|
||||
override val manager: CGMManager by lazy { CGMManager(this, dataHolder) }
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.cgms.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.cgms.data.CGMDataHolder
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.cgms.view.CGMViewEvent
|
||||
import no.nordicsemi.android.cgms.view.DisconnectEvent
|
||||
import no.nordicsemi.android.cgms.view.OnWorkingModeSelected
|
||||
@@ -11,7 +11,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class CGMScreenViewModel @Inject constructor(
|
||||
private val dataHolder: CGMDataHolder
|
||||
private val dataHolder: CGMRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
|
||||
@@ -7,7 +7,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class CSCDataHolder @Inject constructor() {
|
||||
internal class CSCRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(CSCData())
|
||||
val data: StateFlow<CSCData> = _data
|
||||
@@ -29,7 +29,7 @@ import android.util.Log
|
||||
import androidx.annotation.FloatRange
|
||||
import no.nordicsemi.android.ble.common.callback.csc.CyclingSpeedAndCadenceMeasurementDataCallback
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import no.nordicsemi.android.csc.data.CSCDataHolder
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.csc.repository.CSCMeasurementParser.parse
|
||||
import no.nordicsemi.android.csc.view.CSCSettings
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
@@ -42,7 +42,7 @@ val CYCLING_SPEED_AND_CADENCE_SERVICE_UUID: UUID = UUID.fromString("00001816-000
|
||||
/** Cycling Speed and Cadence Measurement characteristic UUID. */
|
||||
private val CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
internal class CSCManager(context: Context, private val dataHolder: CSCDataHolder) : BatteryManager(context) {
|
||||
internal class CSCManager(context: Context, private val dataHolder: CSCRepository) : BatteryManager(context) {
|
||||
|
||||
private var cscMeasurementCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var wheelSize = CSCSettings.DefaultWheelSize.VALUE
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.csc.repository
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import no.nordicsemi.android.csc.data.CSCDataHolder
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
internal class CSCService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: CSCDataHolder
|
||||
lateinit var dataHolder: CSCRepository
|
||||
|
||||
override val manager: CSCManager by lazy { CSCManager(this, dataHolder) }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.csc.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.csc.data.CSCDataHolder
|
||||
import no.nordicsemi.android.csc.data.CSCRepository
|
||||
import no.nordicsemi.android.csc.view.CSCViewEvent
|
||||
import no.nordicsemi.android.csc.view.OnCloseSelectWheelSizeDialog
|
||||
import no.nordicsemi.android.csc.view.OnDisconnectButtonClick
|
||||
@@ -14,7 +14,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class CSCViewModel @Inject constructor(
|
||||
private val dataHolder: CSCDataHolder
|
||||
private val dataHolder: CSCRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
|
||||
@@ -13,6 +13,7 @@ dependencies {
|
||||
|
||||
implementation libs.nordic.log
|
||||
implementation libs.nordic.dfu
|
||||
implementation libs.nordic.ui.scanner
|
||||
|
||||
implementation libs.bundles.compose
|
||||
implementation libs.androidx.core
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package no.nordicsemi.dfu.data
|
||||
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import java.io.File
|
||||
|
||||
internal sealed class DFUData
|
||||
|
||||
internal object NoFileSelectedState : DFUData()
|
||||
|
||||
internal data class FileReadyState(val file: File, val isUploading: Boolean) : DFUData()
|
||||
internal data class FileReadyState(
|
||||
val file: File,
|
||||
val device: DiscoveredBluetoothDevice,
|
||||
val isUploading: Boolean = false
|
||||
) : DFUData()
|
||||
|
||||
internal object UploadSuccessState : DFUData()
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package no.nordicsemi.dfu.data
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DFUDataHolder @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(NoFileSelectedState)
|
||||
val data: StateFlow<DFUData> = _data
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package no.nordicsemi.dfu.data
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DFURepository @Inject constructor(
|
||||
private val deviceHolder: SelectedBluetoothDeviceHolder
|
||||
) {
|
||||
|
||||
private val _data = MutableStateFlow<DFUData>(NoFileSelectedState)
|
||||
val data: StateFlow<DFUData> = _data
|
||||
|
||||
fun initFile(file: File) {
|
||||
_data.value = FileReadyState(file, deviceHolder.device!!)
|
||||
}
|
||||
|
||||
fun install() {
|
||||
val state = _data.value as FileReadyState
|
||||
_data.value = state.copy(isUploading = true)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,29 @@
|
||||
package no.nordicsemi.dfu.view
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.dfu.data.*
|
||||
|
||||
@Composable
|
||||
internal fun DFUContentView(state: DFUData, onEvent: (DFUViewEvent) -> Unit) {
|
||||
Box(modifier = Modifier.padding(16.dp)) {
|
||||
when (state) {
|
||||
NoFileSelectedState -> DFUSelectFileView()
|
||||
NoFileSelectedState -> DFUSelectFileView(onEvent)
|
||||
is FileReadyState -> FileReadyView(state, onEvent)
|
||||
UploadFailureState -> DFUErrorView(onEvent)
|
||||
UploadSuccessState -> DFUSuccessView(onEvent)
|
||||
UploadFailureState -> DFUErrorView(onEvent)
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FileReadyView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) {
|
||||
when (state.isUploading) {
|
||||
true -> DFUInstallingView(onEvent)
|
||||
false -> DFUSummaryView(onEvent)
|
||||
false -> DFUSummaryView(state, onEvent)
|
||||
true -> DFUInstallingView(state, onEvent)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
package no.nordicsemi.dfu.view
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.dfu.R
|
||||
|
||||
@Composable
|
||||
internal fun DFUErrorView(onEvent: (DFUViewEvent) -> Unit) {
|
||||
|
||||
Column {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_fail_circle),
|
||||
contentDescription = stringResource(id = R.string.dfu_failure_icon_description)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(16.dp))
|
||||
|
||||
Button(onClick = { onEvent(OnPauseButtonClick) }) {
|
||||
Text(text = stringResource(id = R.string.dfu_done))
|
||||
Text(text = stringResource(id = R.string.dfu_close))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
package no.nordicsemi.dfu.view
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.net.toUri
|
||||
import no.nordicsemi.android.dfu.DfuServiceInitiator
|
||||
import no.nordicsemi.android.material.you.CircularProgressIndicator
|
||||
import no.nordicsemi.dfu.R
|
||||
import no.nordicsemi.dfu.data.FileReadyState
|
||||
import no.nordicsemi.dfu.repository.DFUService
|
||||
|
||||
@Composable
|
||||
internal fun DFUInstallingView(onEvent: (DFUViewEvent) -> Unit) {
|
||||
internal fun DFUInstallingView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) {
|
||||
|
||||
Column {
|
||||
CircularProgressIndicator()
|
||||
@@ -24,4 +31,38 @@ internal fun DFUInstallingView(onEvent: (DFUViewEvent) -> Unit) {
|
||||
Text(text = stringResource(id = R.string.dfu_stop))
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
LaunchedEffect(state.isUploading) {
|
||||
if (state.isUploading) {
|
||||
install(context, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun install(context: Context, state: FileReadyState) {
|
||||
|
||||
val device = state.device
|
||||
|
||||
val fileName = state.file.name
|
||||
val fileLength = state.file.length()
|
||||
|
||||
val starter = DfuServiceInitiator(device.address)
|
||||
.setDeviceName(device.displayName())
|
||||
// .setKeepBond(keepBond)
|
||||
// .setForceDfu(forceDfu)
|
||||
// .setPacketsReceiptNotificationsEnabled(enablePRNs)
|
||||
// .setPacketsReceiptNotificationsValue(numberOfPackets)
|
||||
// .setPrepareDataObjectDelay(400)
|
||||
// .setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)
|
||||
// if (fileType == DfuService.TYPE_AUTO) {
|
||||
starter.setZip(state.file.toUri(), state.file.path)
|
||||
// if (scope != null) starter.setScope(scope)
|
||||
// } else {
|
||||
// starter.setBinOrHex(fileType, fileStreamUri, filePath)
|
||||
// .setInitFile(initFileStreamUri, initFilePath)
|
||||
// }
|
||||
starter.start(context, DFUService::class.java)
|
||||
}
|
||||
|
||||
@@ -1,38 +1,51 @@
|
||||
package no.nordicsemi.dfu.view
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.dfu.DfuBaseService
|
||||
import no.nordicsemi.android.utils.EMPTY
|
||||
import no.nordicsemi.android.theme.view.ScreenSection
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.dfu.R
|
||||
|
||||
@Composable
|
||||
internal fun DFUSelectFileView() {
|
||||
internal fun DFUSelectFileView(onEvent: (DFUViewEvent) -> Unit) {
|
||||
ScreenSection {
|
||||
SectionTitle(icon = Icons.Default.Settings, title = stringResource(id = R.string.dfu_choose_file))
|
||||
|
||||
val result = remember { mutableStateOf<Uri?>(null) }
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
result.value = it
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.dfu_choose_info),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
|
||||
ButtonsRow(onEvent)
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
@Composable
|
||||
private fun ButtonsRow(onEvent: (DFUViewEvent) -> Unit) {
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
onEvent(OnFileSelected(it!!))
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Button(onClick = { launcher.launch(DfuBaseService.MIME_TYPE_ZIP) }) {
|
||||
Text(text = stringResource(id = R.string.dfu_select_zip))
|
||||
}
|
||||
@@ -42,62 +55,3 @@ internal fun DFUSelectFileView() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChooseFileMangerDialog(onDismiss: () -> Unit) {
|
||||
val alias = remember { mutableStateOf(String.EMPTY) }
|
||||
val command = remember { mutableStateOf(String.EMPTY) }
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.dfu_macro_dialog_title))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Text(stringResource(id = R.string.dfu_macro_dialog_info))
|
||||
|
||||
FileManagerOption.values().forEach {
|
||||
FileManagerItem(item = it) {
|
||||
openFileMangerPlayStore(context, it.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { onDismiss() }
|
||||
) {
|
||||
Text(stringResource(id = R.string.dfu_macro_dialog_dismiss))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun openFileMangerPlayStore(context: Context, url: String) {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FileManagerItem(item: FileManagerOption, onItemSelected: (FileManagerOption) -> Unit) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onItemSelected(item) }
|
||||
) {
|
||||
Text(text = item.title)
|
||||
}
|
||||
}
|
||||
|
||||
enum class FileManagerOption(val title: String, val url: String) {
|
||||
DRIVE("Drive", "market://details?id=com.google.android.apps.docs"),
|
||||
FILE_MANAGER("File Manager", "market://details?id=com.rhmsoft.fm"),
|
||||
TOTAL_COMMANDER("Total Commander", "market://details?id=com.ghisler.android.TotalCommander"),
|
||||
OTHERS("Search for others", "market://search?q=file manager"),
|
||||
}
|
||||
|
||||
@@ -1,16 +1,30 @@
|
||||
package no.nordicsemi.dfu.view
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.dfu.R
|
||||
|
||||
@Composable
|
||||
internal fun DFUSuccessView(onEvent: (DFUViewEvent) -> Unit) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_success_circle),
|
||||
contentDescription = stringResource(id = R.string.dfu_success_icon_description)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(16.dp))
|
||||
|
||||
Column {
|
||||
Button(onClick = { onEvent(OnPauseButtonClick) }) {
|
||||
Text(text = stringResource(id = R.string.dfu_done))
|
||||
}
|
||||
|
||||
@@ -1,23 +1,93 @@
|
||||
package no.nordicsemi.dfu.view
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Notifications
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import no.nordicsemi.android.material.you.CircularProgressIndicator
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.theme.view.ScreenSection
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.dfu.R
|
||||
import no.nordicsemi.dfu.data.FileReadyState
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
internal fun DFUSummaryView(onEvent: (DFUViewEvent) -> Unit) {
|
||||
internal fun DFUSummaryView(state: FileReadyState, onEvent: (DFUViewEvent) -> Unit) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
DeviceDetailsView(state.device)
|
||||
|
||||
Column {
|
||||
CircularProgressIndicator()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
//todo add percentage indicator
|
||||
FileDetailsView(state.file)
|
||||
|
||||
Button(onClick = { onEvent(OnPauseButtonClick) }) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Button(onClick = { onEvent(OnInstallButtonClick) }) {
|
||||
Text(text = stringResource(id = R.string.dfu_install))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DeviceDetailsView(device: DiscoveredBluetoothDevice) {
|
||||
ScreenSection {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.ic_bluetooth),
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSecondary),
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
shape = CircleShape
|
||||
)
|
||||
.padding(8.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.padding(8.dp))
|
||||
|
||||
Column(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)) {
|
||||
Text(
|
||||
text = device.displayName(),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
Text(text = device.displayAddress(), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FileDetailsView(file: File) {
|
||||
val fileName = file.name
|
||||
val fileLength = file.length()
|
||||
|
||||
ScreenSection {
|
||||
SectionTitle(icon = Icons.Default.Notifications, title = stringResource(id = R.string.dfu_file_details))
|
||||
|
||||
Spacer(modifier = Modifier.padding(16.dp))
|
||||
|
||||
Text(text = fileName)
|
||||
|
||||
Spacer(modifier = Modifier.padding(4.dp))
|
||||
|
||||
Text(text = stringResource(id = R.string.dfu_file_size, fileLength))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package no.nordicsemi.dfu.view
|
||||
|
||||
import android.net.Uri
|
||||
|
||||
internal sealed class DFUViewEvent
|
||||
|
||||
internal data class OnFileSelected(val uri: String) : DFUViewEvent()
|
||||
internal data class OnFileSelected(val uri: Uri) : DFUViewEvent()
|
||||
|
||||
internal object OnInstallButtonClick : DFUViewEvent()
|
||||
|
||||
internal object OnPauseButtonClick : DFUViewEvent()
|
||||
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
package no.nordicsemi.dfu.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
||||
import no.nordicsemi.dfu.data.DFUDataHolder
|
||||
import no.nordicsemi.dfu.view.DFUViewEvent
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.dfu.data.DFURepository
|
||||
import no.nordicsemi.dfu.view.*
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class DFUViewModel @Inject constructor(
|
||||
private val dataHolder: DFUDataHolder
|
||||
private val repository: DFURepository,
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
val state = repository.data
|
||||
|
||||
fun onEvent(event: DFUViewEvent) {
|
||||
when (event) {
|
||||
OnDisconnectButtonClick -> finish()
|
||||
is OnFileSelected -> repository.initFile(createFile(event.uri))
|
||||
OnInstallButtonClick -> repository.install()
|
||||
OnPauseButtonClick -> finish()
|
||||
OnStopButtonClick -> finish()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun createFile(uri: Uri): File {
|
||||
return File(requireNotNull(uri.path))
|
||||
}
|
||||
}
|
||||
|
||||
9
profile_dfu/src/main/res/drawable/ic_fail_circle.xml
Normal file
9
profile_dfu/src/main/res/drawable/ic_fail_circle.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="80dp"
|
||||
android:height="80dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/nordicRed"
|
||||
android:pathData="M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z"/>
|
||||
</vector>
|
||||
9
profile_dfu/src/main/res/drawable/ic_success_circle.xml
Normal file
9
profile_dfu/src/main/res/drawable/ic_success_circle.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="80dp"
|
||||
android:height="80dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/nordicGrass"
|
||||
android:pathData="M12,2C6.5,2 2,6.5 2,12S6.5,22 12,22 22,17.5 22,12 17.5,2 12,2M10,17L5,12L6.41,10.59L10,14.17L17.59,6.58L19,8L10,17Z" />
|
||||
</vector>
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="dfu_title">DFU</string>
|
||||
|
||||
<string name="dfu_done">Done</string>
|
||||
<string name="dfu_close">Close</string>
|
||||
<string name="dfu_pause">Pause</string>
|
||||
<string name="dfu_stop">Stop</string>
|
||||
<string name="dfu_install">Install</string>
|
||||
@@ -10,8 +11,22 @@
|
||||
<string name="dfu_select_zip">Select .zip</string>
|
||||
<string name="dfu_select_hex">Select .hex</string>
|
||||
|
||||
<string name="dfu_file_details">File details</string>
|
||||
|
||||
<string name="dfu_file_size">%d bytes</string>
|
||||
<string name="dfu_file_type_zip">Distribution packet (ZIP)</string>
|
||||
<string name="dfu_file_type_soft_device">Soft Device</string>
|
||||
<string name="dfu_file_type_bootloader">Bootloader</string>
|
||||
<string name="dfu_file_type_application">Application</string>
|
||||
|
||||
<string name="dfu_choose_file">Choose file</string>
|
||||
<string name="dfu_choose_info">Please select .zip or .hex file with bootloader, application or soft device.</string>
|
||||
|
||||
<string name="dfu_macro_dialog_title">File managers</string>
|
||||
<string name="dfu_macro_dialog_info">Please select </string>
|
||||
<string name="dfu_macro_dialog_confirm">Confirm</string>
|
||||
<string name="dfu_macro_dialog_dismiss">Dismiss</string>
|
||||
|
||||
<string name="dfu_success_icon_description">Operation success</string>
|
||||
<string name="dfu_failure_icon_description">Operation failed</string>
|
||||
</resources>
|
||||
@@ -10,6 +10,7 @@ dependencies {
|
||||
|
||||
implementation libs.nordic.ble.common
|
||||
implementation libs.nordic.theme
|
||||
implementation libs.nordic.ui.scanner
|
||||
|
||||
implementation libs.nordic.log
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class GLSDataHolder @Inject constructor() {
|
||||
internal class GLSRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(GLSData())
|
||||
val data: StateFlow<GLSData> = _data.asStateFlow()
|
||||
@@ -70,7 +70,7 @@ private val RACP_CHARACTERISTIC = UUID.fromString("00002A52-0000-1000-8000-00805
|
||||
@Singleton
|
||||
internal class GLSManager @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val dataHolder: GLSDataHolder
|
||||
private val repository: GLSRepository
|
||||
) : BatteryManager(context) {
|
||||
|
||||
private var glucoseMeasurementCharacteristic: BluetoothGattCharacteristic? = null
|
||||
@@ -78,7 +78,7 @@ internal class GLSManager @Inject constructor(
|
||||
private var recordAccessControlPointCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
override fun onBatteryLevelChanged(batteryLevel: Int) {
|
||||
dataHolder.setNewBatteryLevel(batteryLevel)
|
||||
repository.setNewBatteryLevel(batteryLevel)
|
||||
}
|
||||
|
||||
override fun getGattCallback(): BatteryManagerGattCallback {
|
||||
@@ -135,7 +135,7 @@ internal class GLSManager @Inject constructor(
|
||||
status = status?.value ?: 0
|
||||
)
|
||||
|
||||
dataHolder.addNewRecord(record)
|
||||
repository.addNewRecord(record)
|
||||
}
|
||||
})
|
||||
setNotificationCallback(glucoseMeasurementContextCharacteristic)
|
||||
@@ -177,7 +177,7 @@ internal class GLSManager @Inject constructor(
|
||||
HbA1c = HbA1c ?: 0f
|
||||
)
|
||||
|
||||
dataHolder.addNewContext(context)
|
||||
repository.addNewContext(context)
|
||||
}
|
||||
})
|
||||
setIndicationCallback(recordAccessControlPointCharacteristic)
|
||||
@@ -192,14 +192,14 @@ internal class GLSManager @Inject constructor(
|
||||
RACP_OP_CODE_ABORT_OPERATION -> RequestStatus.ABORTED
|
||||
else -> RequestStatus.SUCCESS
|
||||
}
|
||||
dataHolder.setRequestStatus(status)
|
||||
repository.setRequestStatus(status)
|
||||
}
|
||||
|
||||
override fun onRecordAccessOperationCompletedWithNoRecordsFound(
|
||||
device: BluetoothDevice,
|
||||
@RACPOpCode requestCode: Int
|
||||
) {
|
||||
dataHolder.setRequestStatus(RequestStatus.SUCCESS)
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
}
|
||||
|
||||
override fun onNumberOfRecordsReceived(
|
||||
@@ -207,8 +207,8 @@ internal class GLSManager @Inject constructor(
|
||||
numberOfRecords: Int
|
||||
) {
|
||||
if (numberOfRecords > 0) {
|
||||
if (dataHolder.records().isNotEmpty()) {
|
||||
val sequenceNumber = dataHolder.records().last().sequenceNumber + 1 //TODO check if correct
|
||||
if (repository.records().isNotEmpty()) {
|
||||
val sequenceNumber = repository.records().last().sequenceNumber + 1 //TODO check if correct
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(
|
||||
@@ -224,7 +224,7 @@ internal class GLSManager @Inject constructor(
|
||||
.enqueue()
|
||||
}
|
||||
}
|
||||
dataHolder.setRequestStatus(RequestStatus.SUCCESS)
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
}
|
||||
|
||||
override fun onRecordAccessOperationError(
|
||||
@@ -234,9 +234,9 @@ internal class GLSManager @Inject constructor(
|
||||
) {
|
||||
log(Log.WARN, "Record Access operation failed (error $errorCode)")
|
||||
if (errorCode == RACP_ERROR_OP_CODE_NOT_SUPPORTED) {
|
||||
dataHolder.setRequestStatus(RequestStatus.NOT_SUPPORTED)
|
||||
repository.setRequestStatus(RequestStatus.NOT_SUPPORTED)
|
||||
} else {
|
||||
dataHolder.setRequestStatus(RequestStatus.FAILED)
|
||||
repository.setRequestStatus(RequestStatus.FAILED)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -284,10 +284,10 @@ internal class GLSManager @Inject constructor(
|
||||
* Clears the records list locally.
|
||||
*/
|
||||
private fun clear() {
|
||||
dataHolder.clearRecords()
|
||||
repository.clearRecords()
|
||||
val target = bluetoothDevice
|
||||
if (target != null) {
|
||||
dataHolder.setRequestStatus(RequestStatus.SUCCESS)
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ internal class GLSManager @Inject constructor(
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
val target = bluetoothDevice ?: return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportLastStoredRecord()
|
||||
@@ -323,7 +323,7 @@ internal class GLSManager @Inject constructor(
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
val target = bluetoothDevice ?: return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportFirstStoredRecord()
|
||||
@@ -347,7 +347,7 @@ internal class GLSManager @Inject constructor(
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
val target = bluetoothDevice ?: return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportNumberOfAllStoredRecords()
|
||||
@@ -375,13 +375,13 @@ internal class GLSManager @Inject constructor(
|
||||
fun refreshRecords() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
val target = bluetoothDevice ?: return
|
||||
if (dataHolder.records().isEmpty()) {
|
||||
if (repository.records().isEmpty()) {
|
||||
requestAllRecords()
|
||||
} else {
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
|
||||
// obtain the last sequence number
|
||||
val sequenceNumber = dataHolder.records().last().sequenceNumber + 1 //TODO check if correct
|
||||
val sequenceNumber = repository.records().last().sequenceNumber + 1 //TODO check if correct
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(sequenceNumber)
|
||||
@@ -425,7 +425,7 @@ internal class GLSManager @Inject constructor(
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
val target = bluetoothDevice ?: return
|
||||
clear()
|
||||
dataHolder.setRequestStatus(RequestStatus.PENDING)
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.deleteAllStoredRecords()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.gls.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.gls.data.GLSDataHolder
|
||||
import no.nordicsemi.android.gls.data.GLSRepository
|
||||
import no.nordicsemi.android.gls.data.WorkingMode
|
||||
import no.nordicsemi.android.gls.repository.GLSManager
|
||||
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
|
||||
@@ -13,7 +13,7 @@ import javax.inject.Inject
|
||||
internal class GLSViewModel @Inject constructor(
|
||||
private val glsManager: GLSManager,
|
||||
private val deviceHolder: SelectedBluetoothDeviceHolder,
|
||||
private val dataHolder: GLSDataHolder
|
||||
private val dataHolder: GLSRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
@@ -27,7 +27,7 @@ internal class GLSViewModel @Inject constructor(
|
||||
|
||||
fun connectDevice() {
|
||||
deviceHolder.device?.let {
|
||||
glsManager.connect(it)
|
||||
glsManager.connect(it.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.enqueue()
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class HRSDataHolder @Inject constructor() {
|
||||
internal class HRSRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(HRSData())
|
||||
val data: StateFlow<HRSData> = _data
|
||||
@@ -31,7 +31,7 @@ import no.nordicsemi.android.ble.common.callback.hr.BodySensorLocationDataCallba
|
||||
import no.nordicsemi.android.ble.common.callback.hr.HeartRateMeasurementDataCallback
|
||||
import no.nordicsemi.android.ble.common.profile.hr.BodySensorLocation
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import no.nordicsemi.android.hrs.data.HRSDataHolder
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import java.util.*
|
||||
@@ -46,7 +46,7 @@ private val HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A
|
||||
* All operations required to connect to device with BLE Heart Rate Service and reading
|
||||
* heart rate values are performed here.
|
||||
*/
|
||||
internal class HRSManager(context: Context, private val dataHolder: HRSDataHolder) : BatteryManager(context) {
|
||||
internal class HRSManager(context: Context, private val dataHolder: HRSRepository) : BatteryManager(context) {
|
||||
|
||||
private var heartRateCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var bodySensorLocationCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.hrs.service
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import no.nordicsemi.android.hrs.data.HRSDataHolder
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
internal class HRSService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: HRSDataHolder
|
||||
lateinit var dataHolder: HRSRepository
|
||||
|
||||
override val manager: HRSManager by lazy { HRSManager(this, dataHolder) }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.hrs.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.hrs.data.HRSDataHolder
|
||||
import no.nordicsemi.android.hrs.data.HRSRepository
|
||||
import no.nordicsemi.android.hrs.view.DisconnectEvent
|
||||
import no.nordicsemi.android.hrs.view.HRSScreenViewEvent
|
||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class HRSViewModel @Inject constructor(
|
||||
private val dataHolder: HRSDataHolder
|
||||
private val dataHolder: HRSRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class HTSDataHolder @Inject constructor() {
|
||||
internal class HTSRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(HTSData())
|
||||
val data: StateFlow<HTSData> = _data
|
||||
@@ -29,7 +29,7 @@ import no.nordicsemi.android.ble.common.callback.ht.TemperatureMeasurementDataCa
|
||||
import no.nordicsemi.android.ble.common.profile.ht.TemperatureType
|
||||
import no.nordicsemi.android.ble.common.profile.ht.TemperatureUnit
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import no.nordicsemi.android.hts.data.HTSDataHolder
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import java.util.*
|
||||
@@ -44,7 +44,7 @@ private val HT_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A1C-0000-
|
||||
*/
|
||||
internal class HTSManager internal constructor(
|
||||
context: Context,
|
||||
private val dataHolder: HTSDataHolder
|
||||
private val dataHolder: HTSRepository
|
||||
) : BatteryManager(context) {
|
||||
|
||||
private var htCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.hts.repository
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import no.nordicsemi.android.hts.data.HTSDataHolder
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
internal class HTSService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: HTSDataHolder
|
||||
lateinit var dataHolder: HTSRepository
|
||||
|
||||
override val manager: HTSManager by lazy { HTSManager(this, dataHolder) }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.hts.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.hts.data.HTSDataHolder
|
||||
import no.nordicsemi.android.hts.data.HTSRepository
|
||||
import no.nordicsemi.android.hts.view.DisconnectEvent
|
||||
import no.nordicsemi.android.hts.view.HTSScreenViewEvent
|
||||
import no.nordicsemi.android.hts.view.OnTemperatureUnitSelected
|
||||
@@ -11,7 +11,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class HTSViewModel @Inject constructor(
|
||||
private val dataHolder: HTSDataHolder
|
||||
private val dataHolder: HTSRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class PRXDataHolder @Inject constructor() {
|
||||
internal class PRXRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(PRXData())
|
||||
val data: StateFlow<PRXData> = _data
|
||||
@@ -33,7 +33,7 @@ import no.nordicsemi.android.ble.common.data.alert.AlertLevelData
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import no.nordicsemi.android.ble.error.GattError
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
import no.nordicsemi.android.prx.data.PRXDataHolder
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import java.util.*
|
||||
|
||||
@@ -48,7 +48,7 @@ val ALERT_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A06-0000-1000-8000-0
|
||||
|
||||
internal class PRXManager(
|
||||
context: Context,
|
||||
private val dataHolder: PRXDataHolder
|
||||
private val dataHolder: PRXRepository
|
||||
) : BatteryManager(context) {
|
||||
|
||||
// Client characteristics.
|
||||
|
||||
@@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.prx.data.AlarmLevel
|
||||
import no.nordicsemi.android.prx.data.DisableAlarm
|
||||
import no.nordicsemi.android.prx.data.EnableAlarm
|
||||
import no.nordicsemi.android.prx.data.PRXDataHolder
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import javax.inject.Inject
|
||||
@@ -16,7 +16,7 @@ import javax.inject.Inject
|
||||
internal class PRXService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: PRXDataHolder
|
||||
lateinit var dataHolder: PRXRepository
|
||||
|
||||
@Inject
|
||||
lateinit var alarmHandler: AlarmHandler
|
||||
|
||||
@@ -3,7 +3,7 @@ package no.nordicsemi.android.prx.viewmodel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.prx.data.DisableAlarm
|
||||
import no.nordicsemi.android.prx.data.EnableAlarm
|
||||
import no.nordicsemi.android.prx.data.PRXDataHolder
|
||||
import no.nordicsemi.android.prx.data.PRXRepository
|
||||
import no.nordicsemi.android.prx.view.DisconnectEvent
|
||||
import no.nordicsemi.android.prx.view.PRXScreenViewEvent
|
||||
import no.nordicsemi.android.prx.view.TurnOffAlert
|
||||
@@ -14,7 +14,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class PRXViewModel @Inject constructor(
|
||||
private val dataHolder: PRXDataHolder
|
||||
private val dataHolder: PRXRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class RSCSDataHolder @Inject constructor() {
|
||||
internal class RSCSRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(RSCSData())
|
||||
val data: StateFlow<RSCSData> = _data
|
||||
@@ -28,7 +28,7 @@ import android.content.Context
|
||||
import no.nordicsemi.android.ble.common.callback.rsc.RunningSpeedAndCadenceMeasurementDataCallback
|
||||
import no.nordicsemi.android.ble.data.Data
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
import no.nordicsemi.android.rscs.data.RSCSDataHolder
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import java.util.*
|
||||
|
||||
@@ -40,7 +40,7 @@ private val RSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A53-0000
|
||||
|
||||
internal class RSCSManager internal constructor(
|
||||
context: Context,
|
||||
private val dataHolder: RSCSDataHolder
|
||||
private val dataHolder: RSCSRepository
|
||||
) : BatteryManager(context) {
|
||||
|
||||
private var rscMeasurementCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.rscs.service
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import no.nordicsemi.android.rscs.data.RSCSDataHolder
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
internal class RSCSService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: RSCSDataHolder
|
||||
lateinit var dataHolder: RSCSRepository
|
||||
|
||||
override val manager: RSCSManager by lazy { RSCSManager(this, dataHolder) }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package no.nordicsemi.android.rscs.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.rscs.data.RSCSDataHolder
|
||||
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||
import no.nordicsemi.android.rscs.view.DisconnectEvent
|
||||
import no.nordicsemi.android.rscs.view.RSCScreenViewEvent
|
||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
||||
@@ -10,7 +10,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class RSCSViewModel @Inject constructor(
|
||||
private val dataHolder: RSCSDataHolder
|
||||
private val dataHolder: RSCSRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
|
||||
@@ -9,7 +9,7 @@ import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class UARTDataHolder @Inject constructor() {
|
||||
internal class UARTRepository @Inject constructor() {
|
||||
|
||||
private val _data = MutableStateFlow(UARTData())
|
||||
val data = _data.asStateFlow()
|
||||
@@ -29,7 +29,7 @@ import android.text.TextUtils
|
||||
import no.nordicsemi.android.ble.WriteRequest
|
||||
import no.nordicsemi.android.log.LogContract
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import no.nordicsemi.android.uart.data.UARTDataHolder
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
import no.nordicsemi.android.utils.EMPTY
|
||||
import java.util.*
|
||||
|
||||
@@ -42,7 +42,7 @@ private val UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0
|
||||
/** TX characteristic UUID */
|
||||
private val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
|
||||
internal class UARTManager(context: Context, private val dataHolder: UARTDataHolder) : BatteryManager(context) {
|
||||
internal class UARTManager(context: Context, private val dataHolder: UARTRepository) : BatteryManager(context) {
|
||||
|
||||
private var rxCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var txCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
@@ -5,14 +5,14 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import no.nordicsemi.android.uart.data.UARTDataHolder
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class UARTService : ForegroundBleService() {
|
||||
|
||||
@Inject
|
||||
lateinit var dataHolder: UARTDataHolder
|
||||
lateinit var dataHolder: UARTRepository
|
||||
|
||||
override val manager: UARTManager by lazy { UARTManager(this, dataHolder) }
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package no.nordicsemi.android.uart.viewmodel
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
||||
import no.nordicsemi.android.uart.data.UARTDataHolder
|
||||
import no.nordicsemi.android.uart.data.UARTRepository
|
||||
import no.nordicsemi.android.uart.data.UARTServiceCommand
|
||||
import no.nordicsemi.android.uart.view.*
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
@@ -10,7 +10,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class UARTViewModel @Inject constructor(
|
||||
private val dataHolder: UARTDataHolder
|
||||
private val dataHolder: UARTRepository
|
||||
) : CloseableViewModel() {
|
||||
|
||||
val state = dataHolder.data
|
||||
|
||||
Reference in New Issue
Block a user