Implement screens for DFU

This commit is contained in:
Sylwester Zieliński
2022-01-06 12:47:37 +01:00
parent c567836d01
commit c57a9c0c98
56 changed files with 384 additions and 222 deletions

View File

@@ -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))
}
}
}

View File

@@ -12,6 +12,6 @@ class HomeViewModel @Inject constructor(
) : ViewModel() {
fun onDeviceSelected(device: DiscoveredBluetoothDevice) {
deviceHolder.attachDevice(device.device)
deviceHolder.attachDevice(device)
}
}

View File

@@ -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

View File

@@ -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."
)
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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) }

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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))
}
}
}

View File

@@ -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)
}

View File

@@ -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"),
}

View File

@@ -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))
}

View File

@@ -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))
}
}

View File

@@ -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()

View File

@@ -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))
}
}

View 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>

View 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>

View File

@@ -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>

View File

@@ -10,6 +10,7 @@ dependencies {
implementation libs.nordic.ble.common
implementation libs.nordic.theme
implementation libs.nordic.ui.scanner
implementation libs.nordic.log

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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) }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) }
}

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) }
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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) }

View File

@@ -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