Redesign service approach

This commit is contained in:
Sylwester Zieliński
2022-02-10 16:37:22 +01:00
parent 7c69fe14a5
commit d12409cffd
14 changed files with 275 additions and 141 deletions

View File

@@ -45,7 +45,6 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
}
fun setValue(value: T) {
Log.d("AAATESTAAA", "setValue()")
_status.value = SuccessResult(value)
}
}

View File

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

View File

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

View File

@@ -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()
}
manager.connect(device)
.useAutoConnect(false)
.retry(3, 100)
.enqueue()
awaitClose {
launch {
manager.disconnect().suspend()
}
manager.disconnect().enqueue()
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,4 +6,6 @@ internal sealed class CGMViewEvent
internal data class OnWorkingModeSelected(val workingMode: CGMServiceCommand) : CGMViewEvent()
internal object NavigateUp : CGMViewEvent()
internal object DisconnectEvent : CGMViewEvent()

View File

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

View File

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

View File

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

View File

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