mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-19 15:34:26 +01:00
Redesign service approach
This commit is contained in:
@@ -45,7 +45,6 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
||||
}
|
||||
|
||||
fun setValue(value: T) {
|
||||
Log.d("AAATESTAAA", "setValue()")
|
||||
_status.value = SuccessResult(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleService
|
||||
|
||||
private const val CHANNEL_ID = "FOREGROUND_BLE_SERVICE"
|
||||
|
||||
abstract class NotificationService : LifecycleService() {
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val result = super.onStartCommand(intent, flags, startId)
|
||||
startForegroundService()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
// when user has disconnected from the sensor, we have to cancel the notification that we've created some milliseconds before using unbindService
|
||||
cancelNotification()
|
||||
stopForegroundService()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the service as a foreground service
|
||||
*/
|
||||
private fun startForegroundService() {
|
||||
// when the activity closes we need to show the notification that user is connected to the peripheral sensor
|
||||
// We start the service as a foreground service as Android 8.0 (Oreo) onwards kills any running background services
|
||||
val notification = createNotification(R.string.csc_notification_connected_message, 0)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
} else {
|
||||
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
nm.notify(NOTIFICATION_ID, notification)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service as a foreground service
|
||||
*/
|
||||
private fun stopForegroundService() {
|
||||
// when the activity rebinds to the service, remove the notification and stop the foreground service
|
||||
// on devices running Android 8.0 (Oreo) or above
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
stopForeground(true)
|
||||
} else {
|
||||
cancelNotification()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the notification
|
||||
*
|
||||
* @param messageResId the message resource id. The message must have one String parameter,<br></br>
|
||||
* f.e. `<string name="name">%s is connected</string>`
|
||||
* @param defaults
|
||||
*/
|
||||
private fun createNotification(messageResId: Int, defaults: Int): Notification {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel(CHANNEL_ID)
|
||||
}
|
||||
|
||||
val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName)
|
||||
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
|
||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(getString(messageResId, "Device"))
|
||||
.setSmallIcon(R.drawable.ic_notification_icon)
|
||||
.setColor(ContextCompat.getColor(this, R.color.md_theme_primary))
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(channelName: String) {
|
||||
val channel = NotificationChannel(
|
||||
channelName,
|
||||
getString(R.string.channel_connected_devices_title),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
channel.description = getString(R.string.channel_connected_devices_description)
|
||||
channel.setShowBadge(false)
|
||||
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the existing notification. If there is no active notification this method does nothing
|
||||
*/
|
||||
private fun cancelNotification() {
|
||||
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
nm.cancel(NOTIFICATION_ID)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val NOTIFICATION_ID = 200
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -19,4 +20,21 @@ class ServiceManager @Inject constructor(
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun <T> startService(service: Class<T>, device: BluetoothDevice) {
|
||||
val intent = Intent(context, service).apply {
|
||||
putExtra(DEVICE_DATA, device)
|
||||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun <T> startService(service: Class<T>) {
|
||||
val intent = Intent(context, service)
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun <T> stopService(service: Class<T>) {
|
||||
val intent = Intent(context, service)
|
||||
context.stopService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,13 @@ package no.nordicsemi.android.bps.data
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.bps.repository.BPSManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import javax.inject.Inject
|
||||
@@ -32,19 +27,13 @@ internal class BPSRepository @Inject constructor(
|
||||
trySend(it)
|
||||
}.launchIn(scope)
|
||||
|
||||
try {
|
||||
manager.connect(device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
.enqueue()
|
||||
|
||||
awaitClose {
|
||||
launch {
|
||||
manager.disconnect().suspend()
|
||||
}
|
||||
manager.disconnect().enqueue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package no.nordicsemi.android.bps.view
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -29,8 +28,6 @@ fun BPSScreen() {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}
|
||||
|
||||
Log.d("AAATESTAAA", "state: $state")
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
|
||||
@@ -1,49 +1,64 @@
|
||||
package no.nordicsemi.android.cgms.data
|
||||
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.*
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.cgms.repository.CGMManager
|
||||
import no.nordicsemi.android.cgms.repository.CGMService
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class CGMRepository @Inject constructor() {
|
||||
internal class CGMRepository @Inject constructor(
|
||||
@ApplicationContext
|
||||
private val context: Context,
|
||||
private val serviceManager: ServiceManager,
|
||||
) {
|
||||
private var manager: CGMManager? = null
|
||||
|
||||
private val _data = MutableStateFlow(CGMData())
|
||||
val data: StateFlow<CGMData> = _data.asStateFlow()
|
||||
private val _data = MutableStateFlow<BleManagerResult<CGMData>>(ConnectingResult())
|
||||
val data = _data.asStateFlow()
|
||||
|
||||
private val _command = MutableSharedFlow<CGMServiceCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
||||
val command = _command.asSharedFlow()
|
||||
|
||||
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
||||
val status = _status.asStateFlow()
|
||||
|
||||
fun emitNewBatteryLevel(batterLevel: Int) {
|
||||
_data.tryEmit(_data.value.copy(batteryLevel = batterLevel))
|
||||
fun launch(device: BluetoothDevice) {
|
||||
serviceManager.startService(CGMService::class.java, device)
|
||||
}
|
||||
|
||||
fun emitNewRecords(records: List<CGMRecord>) {
|
||||
_data.tryEmit(_data.value.copy(records = records))
|
||||
}
|
||||
fun startManager(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val manager = CGMManager(context, scope)
|
||||
|
||||
fun setRequestStatus(requestStatus: RequestStatus) {
|
||||
_data.tryEmit(_data.value.copy(requestStatus = requestStatus))
|
||||
manager.dataHolder.status.onEach {
|
||||
_data.value = it
|
||||
Log.d("AAATESTAAA", "data: $it")
|
||||
}.launchIn(scope)
|
||||
|
||||
manager.connect(device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
fun sendNewServiceCommand(workingMode: CGMServiceCommand) {
|
||||
if (_command.subscriptionCount.value > 0) {
|
||||
_command.tryEmit(workingMode)
|
||||
} else {
|
||||
_status.tryEmit(BleManagerStatus.DISCONNECTED)
|
||||
}
|
||||
when (workingMode) {
|
||||
CGMServiceCommand.REQUEST_ALL_RECORDS -> manager?.requestAllRecords()
|
||||
CGMServiceCommand.REQUEST_LAST_RECORD -> manager?.requestLastRecord()
|
||||
CGMServiceCommand.REQUEST_FIRST_RECORD -> manager?.requestFirstRecord()
|
||||
CGMServiceCommand.DISCONNECT -> release()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
fun setNewStatus(status: BleManagerStatus) {
|
||||
_status.value = status
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_status.value = BleManagerStatus.CONNECTING
|
||||
_data.tryEmit(CGMData())
|
||||
private fun release() {
|
||||
serviceManager.stopService(CGMService::class.java)
|
||||
manager?.disconnect()?.enqueue()
|
||||
manager = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.util.SparseArray
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.ble.BleManager
|
||||
import no.nordicsemi.android.ble.common.callback.RecordAccessControlPointResponse
|
||||
import no.nordicsemi.android.ble.common.callback.cgm.CGMFeatureResponse
|
||||
import no.nordicsemi.android.ble.common.callback.cgm.CGMSpecificOpsControlPointResponse
|
||||
@@ -43,10 +43,10 @@ import no.nordicsemi.android.ble.common.profile.cgm.CGMSpecificOpsControlPointCa
|
||||
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
|
||||
import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.ble.ktx.suspendForValidResponse
|
||||
import no.nordicsemi.android.cgms.data.CGMData
|
||||
import no.nordicsemi.android.cgms.data.CGMRecord
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.cgms.data.RequestStatus
|
||||
import no.nordicsemi.android.service.BatteryManager
|
||||
import no.nordicsemi.android.service.ConnectionObserverAdapter
|
||||
import java.util.*
|
||||
|
||||
val CGMS_SERVICE_UUID: UUID = UUID.fromString("0000181F-0000-1000-8000-00805f9b34fb")
|
||||
@@ -57,11 +57,13 @@ private val CGM_OPS_CONTROL_POINT_UUID = UUID.fromString("00002AAC-0000-1000-800
|
||||
|
||||
private val RACP_UUID = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
|
||||
private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
internal class CGMManager(
|
||||
context: Context,
|
||||
scope: CoroutineScope,
|
||||
private val repository: CGMRepository
|
||||
) : BatteryManager(context, scope) {
|
||||
private val scope: CoroutineScope
|
||||
) : BleManager(context) {
|
||||
|
||||
private var cgmStatusCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var cgmFeatureCharacteristic: BluetoothGattCharacteristic? = null
|
||||
@@ -69,6 +71,7 @@ internal class CGMManager(
|
||||
private var cgmSpecificOpsControlPointCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private var recordAccessControlPointCharacteristic: BluetoothGattCharacteristic? = null
|
||||
private val records: SparseArray<CGMRecord> = SparseArray<CGMRecord>()
|
||||
private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
|
||||
|
||||
private var secured = false
|
||||
|
||||
@@ -80,15 +83,22 @@ internal class CGMManager(
|
||||
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
|
||||
}
|
||||
|
||||
override fun onBatteryLevelChanged(batteryLevel: Int) {
|
||||
repository.emitNewBatteryLevel(batteryLevel)
|
||||
private val data = MutableStateFlow(CGMData())
|
||||
val dataHolder = ConnectionObserverAdapter<CGMData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun getGattCallback(): BatteryManagerGattCallback {
|
||||
override fun getGattCallback(): BleManagerGattCallback {
|
||||
return CGMManagerGattCallback()
|
||||
}
|
||||
|
||||
private inner class CGMManagerGattCallback : BatteryManagerGattCallback() {
|
||||
private inner class CGMManagerGattCallback : BleManagerGattCallback() {
|
||||
override fun initialize() {
|
||||
super.initialize()
|
||||
|
||||
@@ -98,6 +108,12 @@ internal class CGMManager(
|
||||
this@CGMManager.secured = response.features.e2eCrcSupported
|
||||
}
|
||||
|
||||
scope.launch(exceptionHandler) {
|
||||
val response =
|
||||
readCharacteristic(cgmFeatureCharacteristic).suspendForValidResponse<CGMFeatureResponse>()
|
||||
this@CGMManager.secured = response.features.e2eCrcSupported
|
||||
}
|
||||
|
||||
scope.launch(exceptionHandler) {
|
||||
val response =
|
||||
readCharacteristic(cgmStatusCharacteristic).suspendForValidResponse<CGMStatusResponse>()
|
||||
@@ -115,7 +131,8 @@ internal class CGMManager(
|
||||
val timestamp = sessionStartTime + it.timeOffset * 60000L
|
||||
val record = CGMRecord(it.timeOffset, it.glucoseConcentration, timestamp)
|
||||
records.put(record.sequenceNumber, record)
|
||||
repository.emitNewRecords(records.toList())
|
||||
|
||||
data.value = data.value.copy(records = records.toList())
|
||||
}.launchIn(scope)
|
||||
|
||||
setIndicationCallback(cgmSpecificOpsControlPointCharacteristic).asValidResponseFlow<CGMSpecificOpsControlPointResponse>()
|
||||
@@ -152,15 +169,10 @@ internal class CGMManager(
|
||||
}
|
||||
}.launchIn(scope)
|
||||
|
||||
scope.launch(exceptionHandler) {
|
||||
enableNotifications(cgmMeasurementCharacteristic).suspend()
|
||||
}
|
||||
scope.launch(exceptionHandler) {
|
||||
enableIndications(cgmSpecificOpsControlPointCharacteristic).suspend()
|
||||
}
|
||||
scope.launch(exceptionHandler) {
|
||||
enableIndications(recordAccessControlPointCharacteristic).suspend()
|
||||
}
|
||||
enableNotifications(cgmMeasurementCharacteristic).enqueue()
|
||||
enableIndications(cgmSpecificOpsControlPointCharacteristic).enqueue()
|
||||
enableIndications(recordAccessControlPointCharacteristic).enqueue()
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
|
||||
if (sessionStartTime == 0L) {
|
||||
scope.launch(exceptionHandler) {
|
||||
@@ -179,9 +191,7 @@ internal class CGMManager(
|
||||
val sequenceNumber = records.keyAt(records.size() - 1) + 1
|
||||
writeCharacteristic(
|
||||
recordAccessControlPointCharacteristic,
|
||||
RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(
|
||||
sequenceNumber
|
||||
),
|
||||
RecordAccessControlPointData.reportStoredRecordsGreaterThenOrEqualTo(sequenceNumber),
|
||||
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
).suspend()
|
||||
} else {
|
||||
@@ -193,32 +203,31 @@ internal class CGMManager(
|
||||
}
|
||||
} else {
|
||||
recordAccessRequestInProgress = false
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onNoRecordsFound() {
|
||||
recordAccessRequestInProgress = false
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.SUCCESS)
|
||||
}
|
||||
|
||||
private fun onOperationCompleted(response: RecordAccessControlPointResponse) {
|
||||
when (response.requestCode) {
|
||||
RecordAccessControlPointCallback.RACP_OP_CODE_ABORT_OPERATION -> repository.setRequestStatus(
|
||||
RequestStatus.ABORTED
|
||||
)
|
||||
RecordAccessControlPointCallback.RACP_OP_CODE_ABORT_OPERATION ->
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.ABORTED)
|
||||
else -> {
|
||||
recordAccessRequestInProgress = false
|
||||
repository.setRequestStatus(RequestStatus.SUCCESS)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.SUCCESS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError(response: RecordAccessControlPointResponse) {
|
||||
if (response.errorCode == RecordAccessControlPointCallback.RACP_ERROR_OP_CODE_NOT_SUPPORTED) {
|
||||
repository.setRequestStatus(RequestStatus.NOT_SUPPORTED)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.NOT_SUPPORTED)
|
||||
} else {
|
||||
repository.setRequestStatus(RequestStatus.FAILED)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.FAILED)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +264,7 @@ internal class CGMManager(
|
||||
fun requestLastRecord() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
clear()
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
scope.launch(exceptionHandler) {
|
||||
writeCharacteristic(
|
||||
@@ -269,7 +278,7 @@ internal class CGMManager(
|
||||
fun requestFirstRecord() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
clear()
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
scope.launch(exceptionHandler) {
|
||||
writeCharacteristic(
|
||||
@@ -283,7 +292,7 @@ internal class CGMManager(
|
||||
fun requestAllRecords() {
|
||||
if (recordAccessControlPointCharacteristic == null) return
|
||||
clear()
|
||||
repository.setRequestStatus(RequestStatus.PENDING)
|
||||
data.value = data.value.copy(requestStatus = RequestStatus.PENDING)
|
||||
recordAccessRequestInProgress = true
|
||||
scope.launch(exceptionHandler) {
|
||||
writeCharacteristic(
|
||||
|
||||
@@ -1,38 +1,27 @@
|
||||
package no.nordicsemi.android.cgms.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Intent
|
||||
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.CGMRepository
|
||||
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||
import no.nordicsemi.android.service.ForegroundBleService
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
internal class CGMService : ForegroundBleService() {
|
||||
internal class CGMService : NotificationService() {
|
||||
|
||||
@Inject
|
||||
lateinit var repository: CGMRepository
|
||||
|
||||
override val manager: CGMManager by lazy { CGMManager(this, scope, repository) }
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
// status.onEach {
|
||||
// val status = it.mapToSimpleManagerStatus()
|
||||
// repository.setNewStatus(status)
|
||||
// stopIfDisconnected(status)
|
||||
// }.launchIn(scope)
|
||||
repository.startManager(device, lifecycleScope)
|
||||
|
||||
repository.command.onEach {
|
||||
when (it) {
|
||||
CGMServiceCommand.REQUEST_ALL_RECORDS -> manager.requestAllRecords()
|
||||
CGMServiceCommand.REQUEST_LAST_RECORD -> manager.requestLastRecord()
|
||||
CGMServiceCommand.REQUEST_FIRST_RECORD -> manager.requestFirstRecord()
|
||||
CGMServiceCommand.DISCONNECT -> stopSelf()
|
||||
}.exhaustive
|
||||
}.launchIn(scope)
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,13 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.cgms.R
|
||||
import no.nordicsemi.android.cgms.viewmodel.CGMScreenViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.scanner.DeviceConnectingView
|
||||
import no.nordicsemi.android.theme.view.scanner.DeviceDisconnectedView
|
||||
import no.nordicsemi.android.theme.view.scanner.NoDeviceView
|
||||
import no.nordicsemi.android.theme.view.scanner.Reason
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
|
||||
@Composable
|
||||
fun CGMScreen() {
|
||||
@@ -23,10 +29,17 @@ fun CGMScreen() {
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
// when (state) {
|
||||
// is DisplayDataState -> CGMContentView(state.data) { viewModel.onEvent(it) }
|
||||
// LoadingState -> DeviceConnectingView()
|
||||
// }.exhaustive
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is ConnectingResult -> DeviceConnectingView()
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS)
|
||||
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE)
|
||||
is ReadyResult -> DeviceConnectingView()
|
||||
is SuccessResult -> CGMContentView(state.result.data) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,6 @@ internal sealed class CGMViewEvent
|
||||
|
||||
internal data class OnWorkingModeSelected(val workingMode: CGMServiceCommand) : CGMViewEvent()
|
||||
|
||||
internal object NavigateUp : CGMViewEvent()
|
||||
|
||||
internal object DisconnectEvent : CGMViewEvent()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package no.nordicsemi.android.cgms.view
|
||||
|
||||
import no.nordicsemi.android.cgms.data.CGMData
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
|
||||
internal sealed class CGMViewState
|
||||
internal sealed class BPSViewState
|
||||
|
||||
internal object LoadingState : CGMViewState()
|
||||
|
||||
internal data class DisplayDataState(val data: CGMData) : CGMViewState()
|
||||
internal data class WorkingState(val result: BleManagerResult<CGMData>) : BPSViewState()
|
||||
internal object NoDeviceState : BPSViewState()
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
package no.nordicsemi.android.cgms.viewmodel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||
import no.nordicsemi.android.cgms.repository.CGMS_SERVICE_UUID
|
||||
import no.nordicsemi.android.cgms.repository.CGMService
|
||||
import no.nordicsemi.android.cgms.view.*
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.service.BleManagerStatus
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.android.utils.getDevice
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import no.nordicsemi.ui.scanner.ScannerDestinationId
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class CGMScreenViewModel @Inject constructor(
|
||||
private val repository: CGMRepository,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val navigationManager: NavigationManager
|
||||
) : ViewModel() {
|
||||
|
||||
val state = repository.data.combine(repository.status) { data, status ->
|
||||
// when (status) {
|
||||
// BleManagerStatus.CONNECTING -> LoadingState
|
||||
// BleManagerStatus.OK,
|
||||
// BleManagerStatus.DISCONNECTED -> DisplayDataState(data)
|
||||
// }
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, LoadingState)
|
||||
private val _state = MutableStateFlow<BPSViewState>(NoDeviceState)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
navigationManager.navigateTo(ScannerDestinationId, UUIDArgument(CGMS_SERVICE_UUID))
|
||||
@@ -41,17 +37,16 @@ internal class CGMScreenViewModel @Inject constructor(
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
repository.status.onEach {
|
||||
if (it == BleManagerStatus.DISCONNECTED) {
|
||||
navigationManager.navigateUp()
|
||||
}
|
||||
repository.data.onEach {
|
||||
_state.value = WorkingState(it)
|
||||
Log.d("AAATESTAAA", "vm data: $it")
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> serviceManager.startService(CGMService::class.java, args.getDevice())
|
||||
is SuccessDestinationResult -> connectDevice(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -59,16 +54,15 @@ internal class CGMScreenViewModel @Inject constructor(
|
||||
when (event) {
|
||||
DisconnectEvent -> disconnect()
|
||||
is OnWorkingModeSelected -> repository.sendNewServiceCommand(event.workingMode)
|
||||
NavigateUp -> navigationManager.navigateUp()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun connectDevice(deviceHolder: DiscoveredBluetoothDevice) {
|
||||
repository.launch(deviceHolder.device)
|
||||
}
|
||||
|
||||
private fun disconnect() {
|
||||
repository.sendNewServiceCommand(CGMServiceCommand.DISCONNECT)
|
||||
repository.clear()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
repository.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
@@ -37,7 +37,7 @@ dependencyResolutionManagement {
|
||||
alias('compose-activity').to('androidx.activity:activity-compose:1.4.0')
|
||||
alias('compose-lifecycle').to('androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0')
|
||||
|
||||
version('compose', '1.0.5')
|
||||
version('compose', '1.1.0')
|
||||
alias('compose-livedata').to('androidx.compose.runtime', 'runtime-livedata').versionRef('compose')
|
||||
alias('compose-ui').to('androidx.compose.ui', 'ui').versionRef('compose')
|
||||
alias('compose-material').to('androidx.compose.material3:material3:1.0.0-alpha04')
|
||||
|
||||
Reference in New Issue
Block a user