mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-23 09:24:23 +01:00
Apply fixes to Toolbox
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
package no.nordicsemi.android.service
|
||||||
|
|
||||||
|
enum class BleManagerStatus {
|
||||||
|
CONNECTING, OK, DISCONNECTED
|
||||||
|
}
|
||||||
@@ -21,22 +21,34 @@
|
|||||||
*/
|
*/
|
||||||
package no.nordicsemi.android.service
|
package no.nordicsemi.android.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
import android.bluetooth.BluetoothDevice
|
import android.bluetooth.BluetoothDevice
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.LifecycleService
|
import androidx.lifecycle.LifecycleService
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import no.nordicsemi.android.ble.BleManager
|
import no.nordicsemi.android.ble.BleManager
|
||||||
import no.nordicsemi.android.log.ILogSession
|
import no.nordicsemi.android.log.ILogSession
|
||||||
import no.nordicsemi.android.log.Logger
|
import no.nordicsemi.android.log.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
abstract class BleProfileService : LifecycleService() {
|
abstract class BleProfileService : Service() {
|
||||||
|
|
||||||
|
protected val scope = CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
|
|
||||||
protected abstract val manager: BleManager
|
protected abstract val manager: BleManager
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
||||||
|
val status = _status.asStateFlow()
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var bluetoothDeviceHolder: SelectedBluetoothDeviceHolder
|
lateinit var bluetoothDeviceHolder: SelectedBluetoothDeviceHolder
|
||||||
|
|
||||||
@@ -71,6 +83,23 @@ abstract class BleProfileService : LifecycleService() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
handler = Handler()
|
handler = Handler()
|
||||||
|
|
||||||
|
manager.setConnectionObserver(object : ConnectionObserverAdapter() {
|
||||||
|
override fun onDeviceConnected(device: BluetoothDevice) {
|
||||||
|
super.onDeviceConnected(device)
|
||||||
|
_status.value = BleManagerStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) {
|
||||||
|
super.onDeviceDisconnected(device, reason)
|
||||||
|
_status.value = BleManagerStatus.DISCONNECTED
|
||||||
|
scope.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,6 +118,7 @@ abstract class BleProfileService : LifecycleService() {
|
|||||||
.useAutoConnect(shouldAutoConnect())
|
.useAutoConnect(shouldAutoConnect())
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
|
|
||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package no.nordicsemi.android.service
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import java.io.Closeable
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
|
||||||
|
override val coroutineContext: CoroutineContext = context
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
coroutineContext.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package no.nordicsemi.android.service
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import no.nordicsemi.android.ble.observer.ConnectionObserver
|
||||||
|
|
||||||
|
abstract class ConnectionObserverAdapter : ConnectionObserver {
|
||||||
|
|
||||||
|
override fun onDeviceConnecting(device: BluetoothDevice) { }
|
||||||
|
|
||||||
|
override fun onDeviceConnected(device: BluetoothDevice) { }
|
||||||
|
|
||||||
|
override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) { }
|
||||||
|
|
||||||
|
override fun onDeviceReady(device: BluetoothDevice) { }
|
||||||
|
|
||||||
|
override fun onDeviceDisconnecting(device: BluetoothDevice) { }
|
||||||
|
|
||||||
|
override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) { }
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ dependencies {
|
|||||||
implementation libs.nordic.theme
|
implementation libs.nordic.theme
|
||||||
|
|
||||||
implementation libs.bundles.compose
|
implementation libs.bundles.compose
|
||||||
|
implementation libs.bundles.icons
|
||||||
implementation libs.compose.lifecycle
|
implementation libs.compose.lifecycle
|
||||||
implementation libs.compose.activity
|
implementation libs.compose.activity
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package no.nordicsemi.android.theme.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.HourglassTop
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import no.nordicsemi.android.theme.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DeviceConnectingView() {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
ScreenSection {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.HourglassTop,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.onSecondary,
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
.padding(8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.device_connecting),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.device_explanation),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.device_please_wait),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun DeviceConnectingView_Preview() {
|
||||||
|
DeviceConnectingView()
|
||||||
|
}
|
||||||
@@ -10,4 +10,8 @@
|
|||||||
|
|
||||||
<string name="disconnect">DISCONNECT</string>
|
<string name="disconnect">DISCONNECT</string>
|
||||||
<string name="field_battery">Battery</string>
|
<string name="field_battery">Battery</string>
|
||||||
|
|
||||||
|
<string name="device_connecting">Connecting</string>
|
||||||
|
<string name="device_explanation">The mobile is trying to connect to peripheral device.</string>
|
||||||
|
<string name="device_please_wait">Please wait.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -2,6 +2,7 @@ package no.nordicsemi.android.cgms.data
|
|||||||
|
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import no.nordicsemi.android.service.BleManagerStatus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -11,9 +12,12 @@ internal class CGMRepository @Inject constructor() {
|
|||||||
private val _data = MutableStateFlow(CGMData())
|
private val _data = MutableStateFlow(CGMData())
|
||||||
val data: StateFlow<CGMData> = _data.asStateFlow()
|
val data: StateFlow<CGMData> = _data.asStateFlow()
|
||||||
|
|
||||||
private val _command = MutableSharedFlow<WorkingMode>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
private val _command = MutableSharedFlow<CGMServiceCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
||||||
val command = _command.asSharedFlow()
|
val command = _command.asSharedFlow()
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
||||||
|
val status = _status.asStateFlow()
|
||||||
|
|
||||||
fun emitNewBatteryLevel(batterLevel: Int) {
|
fun emitNewBatteryLevel(batterLevel: Int) {
|
||||||
_data.tryEmit(_data.value.copy(batteryLevel = batterLevel))
|
_data.tryEmit(_data.value.copy(batteryLevel = batterLevel))
|
||||||
}
|
}
|
||||||
@@ -26,10 +30,14 @@ internal class CGMRepository @Inject constructor() {
|
|||||||
_data.tryEmit(_data.value.copy(requestStatus = requestStatus))
|
_data.tryEmit(_data.value.copy(requestStatus = requestStatus))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestNewWorkingMode(workingMode: WorkingMode) {
|
fun sendNewServiceCommand(workingMode: CGMServiceCommand) {
|
||||||
_command.tryEmit(workingMode)
|
_command.tryEmit(workingMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setNewStatus(status: BleManagerStatus) {
|
||||||
|
_status.value = status
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
_data.tryEmit(CGMData())
|
_data.tryEmit(CGMData())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package no.nordicsemi.android.cgms.data
|
||||||
|
|
||||||
|
internal enum class CGMServiceCommand {
|
||||||
|
REQUEST_ALL_RECORDS,
|
||||||
|
REQUEST_LAST_RECORD,
|
||||||
|
REQUEST_FIRST_RECORD,
|
||||||
|
DISCONNECT
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package no.nordicsemi.android.cgms.data
|
|
||||||
|
|
||||||
internal enum class WorkingMode {
|
|
||||||
ALL,
|
|
||||||
LAST,
|
|
||||||
FIRST
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package no.nordicsemi.android.cgms.repository
|
package no.nordicsemi.android.cgms.repository
|
||||||
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||||
import no.nordicsemi.android.cgms.data.WorkingMode
|
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||||
import no.nordicsemi.android.service.ForegroundBleService
|
import no.nordicsemi.android.service.ForegroundBleService
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -14,19 +13,24 @@ import javax.inject.Inject
|
|||||||
internal class CGMService : ForegroundBleService() {
|
internal class CGMService : ForegroundBleService() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var dataHolder: CGMRepository
|
lateinit var repository: CGMRepository
|
||||||
|
|
||||||
override val manager: CGMManager by lazy { CGMManager(this, dataHolder) }
|
override val manager: CGMManager by lazy { CGMManager(this, repository) }
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
dataHolder.command.onEach {
|
status.onEach {
|
||||||
|
repository.setNewStatus(it)
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
repository.command.onEach {
|
||||||
when (it) {
|
when (it) {
|
||||||
WorkingMode.ALL -> manager.requestAllRecords()
|
CGMServiceCommand.REQUEST_ALL_RECORDS -> manager.requestAllRecords()
|
||||||
WorkingMode.LAST -> manager.requestLastRecord()
|
CGMServiceCommand.REQUEST_LAST_RECORD -> manager.requestLastRecord()
|
||||||
WorkingMode.FIRST -> manager.requestFirstRecord()
|
CGMServiceCommand.REQUEST_FIRST_RECORD -> manager.requestFirstRecord()
|
||||||
|
CGMServiceCommand.DISCONNECT -> stopSelf()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}.launchIn(lifecycleScope)
|
}.launchIn(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import no.nordicsemi.android.cgms.R
|
|||||||
import no.nordicsemi.android.cgms.data.CGMData
|
import no.nordicsemi.android.cgms.data.CGMData
|
||||||
import no.nordicsemi.android.cgms.data.CGMRecord
|
import no.nordicsemi.android.cgms.data.CGMRecord
|
||||||
import no.nordicsemi.android.cgms.data.RequestStatus
|
import no.nordicsemi.android.cgms.data.RequestStatus
|
||||||
import no.nordicsemi.android.cgms.data.WorkingMode
|
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||||
import no.nordicsemi.android.material.you.CircularProgressIndicator
|
import no.nordicsemi.android.material.you.CircularProgressIndicator
|
||||||
import no.nordicsemi.android.theme.view.BatteryLevelView
|
import no.nordicsemi.android.theme.view.BatteryLevelView
|
||||||
import no.nordicsemi.android.theme.view.ScreenSection
|
import no.nordicsemi.android.theme.view.ScreenSection
|
||||||
@@ -29,12 +29,10 @@ internal fun CGMContentView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(16.dp)
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
SettingsView(state, onEvent)
|
SettingsView(state, onEvent)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
@@ -71,10 +69,14 @@ private fun SettingsView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
|
|||||||
if (state.requestStatus == RequestStatus.PENDING) {
|
if (state.requestStatus == RequestStatus.PENDING) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
WorkingMode.values().forEach {
|
Button(onClick = { onEvent(OnWorkingModeSelected(CGMServiceCommand.REQUEST_ALL_RECORDS)) }) {
|
||||||
Button(onClick = { onEvent(OnWorkingModeSelected(it)) }) {
|
Text(stringResource(id = R.string.cgms__working_mode__all))
|
||||||
Text(it.toDisplayString())
|
}
|
||||||
}
|
Button(onClick = { onEvent(OnWorkingModeSelected(CGMServiceCommand.REQUEST_LAST_RECORD)) }) {
|
||||||
|
Text(stringResource(id = R.string.cgms__working_mode__last))
|
||||||
|
}
|
||||||
|
Button(onClick = { onEvent(OnWorkingModeSelected(CGMServiceCommand.REQUEST_FIRST_RECORD)) }) {
|
||||||
|
Text(stringResource(id = R.string.cgms__working_mode__first))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,10 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import no.nordicsemi.android.cgms.R
|
import no.nordicsemi.android.cgms.R
|
||||||
import no.nordicsemi.android.cgms.data.CGMRecord
|
import no.nordicsemi.android.cgms.data.CGMRecord
|
||||||
import no.nordicsemi.android.cgms.data.WorkingMode
|
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun WorkingMode.toDisplayString(): String {
|
|
||||||
return when (this) {
|
|
||||||
WorkingMode.ALL -> stringResource(id = R.string.cgms__working_mode__all)
|
|
||||||
WorkingMode.LAST -> stringResource(id = R.string.cgms__working_mode__last)
|
|
||||||
WorkingMode.FIRST -> stringResource(id = R.string.cgms__working_mode__first)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun CGMRecord.formattedTime(): String {
|
internal fun CGMRecord.formattedTime(): String {
|
||||||
val timeFormat = SimpleDateFormat("dd.MM.yyyy HH:mm", Locale.US)
|
val timeFormat = SimpleDateFormat("dd.MM.yyyy HH:mm", Locale.US)
|
||||||
return timeFormat.format(Date(timestamp))
|
return timeFormat.format(Date(timestamp))
|
||||||
|
|||||||
@@ -9,49 +9,43 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.cgms.R
|
import no.nordicsemi.android.cgms.R
|
||||||
import no.nordicsemi.android.cgms.data.CGMData
|
|
||||||
import no.nordicsemi.android.cgms.repository.CGMService
|
import no.nordicsemi.android.cgms.repository.CGMService
|
||||||
import no.nordicsemi.android.cgms.viewmodel.CGMScreenViewModel
|
import no.nordicsemi.android.cgms.viewmodel.CGMScreenViewModel
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.utils.isServiceRunning
|
import no.nordicsemi.android.theme.view.DeviceConnectingView
|
||||||
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CGMScreen(finishAction: () -> Unit) {
|
fun CGMScreen(finishAction: () -> Unit) {
|
||||||
val viewModel: CGMScreenViewModel = hiltViewModel()
|
val viewModel: CGMScreenViewModel = hiltViewModel()
|
||||||
val state = viewModel.state.collectAsState().value
|
val state = viewModel.state.collectAsState().value
|
||||||
val isScreenActive = viewModel.isActive.collectAsState().value
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(isScreenActive) {
|
LaunchedEffect(state.isActive) {
|
||||||
if (!isScreenActive) {
|
if (state.isActive) {
|
||||||
finishAction()
|
|
||||||
}
|
|
||||||
if (context.isServiceRunning(CGMService::class.java.name)) {
|
|
||||||
val intent = Intent(context, CGMService::class.java)
|
|
||||||
context.stopService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect("start-service") {
|
|
||||||
if (!context.isServiceRunning(CGMService::class.java.name)) {
|
|
||||||
val intent = Intent(context, CGMService::class.java)
|
val intent = Intent(context, CGMService::class.java)
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
|
} else if (!state.isActive) {
|
||||||
|
finishAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CGMView(state) {
|
CGMView(state.viewState) {
|
||||||
viewModel.onEvent(it)
|
viewModel.onEvent(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CGMView(state: CGMData, onEvent: (CGMViewEvent) -> Unit) {
|
private fun CGMView(state: CGMViewState, onEvent: (CGMViewEvent) -> Unit) {
|
||||||
Column {
|
Column {
|
||||||
BackIconAppBar(stringResource(id = R.string.cgms_title)) {
|
BackIconAppBar(stringResource(id = R.string.cgms_title)) {
|
||||||
onEvent(DisconnectEvent)
|
onEvent(DisconnectEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
CGMContentView(state, onEvent)
|
when (state) {
|
||||||
|
is DisplayDataState -> CGMContentView(state.data, onEvent)
|
||||||
|
LoadingState -> DeviceConnectingView()
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package no.nordicsemi.android.cgms.view
|
package no.nordicsemi.android.cgms.view
|
||||||
|
|
||||||
import no.nordicsemi.android.cgms.data.WorkingMode
|
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||||
|
|
||||||
internal sealed class CGMViewEvent
|
internal sealed class CGMViewEvent
|
||||||
|
|
||||||
internal data class OnWorkingModeSelected(val workingMode: WorkingMode) : CGMViewEvent()
|
internal data class OnWorkingModeSelected(val workingMode: CGMServiceCommand) : CGMViewEvent()
|
||||||
|
|
||||||
internal object DisconnectEvent : CGMViewEvent()
|
internal object DisconnectEvent : CGMViewEvent()
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package no.nordicsemi.android.cgms.view
|
||||||
|
|
||||||
|
import no.nordicsemi.android.cgms.data.CGMData
|
||||||
|
|
||||||
|
internal data class CGMState(
|
||||||
|
val viewState: CGMViewState,
|
||||||
|
val isActive: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
internal sealed class CGMViewState
|
||||||
|
|
||||||
|
internal object LoadingState : CGMViewState()
|
||||||
|
|
||||||
|
internal data class DisplayDataState(val data: CGMData) : CGMViewState()
|
||||||
@@ -1,30 +1,45 @@
|
|||||||
package no.nordicsemi.android.cgms.viewmodel
|
package no.nordicsemi.android.cgms.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import no.nordicsemi.android.cgms.data.CGMRepository
|
import no.nordicsemi.android.cgms.data.CGMRepository
|
||||||
|
import no.nordicsemi.android.cgms.data.CGMServiceCommand
|
||||||
|
import no.nordicsemi.android.cgms.view.CGMState
|
||||||
import no.nordicsemi.android.cgms.view.CGMViewEvent
|
import no.nordicsemi.android.cgms.view.CGMViewEvent
|
||||||
import no.nordicsemi.android.cgms.view.DisconnectEvent
|
import no.nordicsemi.android.cgms.view.DisconnectEvent
|
||||||
|
import no.nordicsemi.android.cgms.view.DisplayDataState
|
||||||
|
import no.nordicsemi.android.cgms.view.LoadingState
|
||||||
import no.nordicsemi.android.cgms.view.OnWorkingModeSelected
|
import no.nordicsemi.android.cgms.view.OnWorkingModeSelected
|
||||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
import no.nordicsemi.android.service.BleManagerStatus
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
internal class CGMScreenViewModel @Inject constructor(
|
internal class CGMScreenViewModel @Inject constructor(
|
||||||
private val dataHolder: CGMRepository
|
private val repository: CGMRepository
|
||||||
) : CloseableViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val state = dataHolder.data
|
val state = repository.data.combine(repository.status) { data, status ->
|
||||||
|
when (status) {
|
||||||
|
BleManagerStatus.CONNECTING -> CGMState(LoadingState)
|
||||||
|
BleManagerStatus.OK -> CGMState(DisplayDataState(data))
|
||||||
|
BleManagerStatus.DISCONNECTED -> CGMState(DisplayDataState(data), false)
|
||||||
|
}
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.Lazily, CGMState(LoadingState))
|
||||||
|
|
||||||
fun onEvent(event: CGMViewEvent) {
|
fun onEvent(event: CGMViewEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
DisconnectEvent -> disconnect()
|
DisconnectEvent -> disconnect()
|
||||||
is OnWorkingModeSelected -> dataHolder.requestNewWorkingMode(event.workingMode)
|
is OnWorkingModeSelected -> repository.sendNewServiceCommand(event.workingMode)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun disconnect() {
|
private fun disconnect() {
|
||||||
finish()
|
repository.clear()
|
||||||
dataHolder.clear()
|
repository.sendNewServiceCommand(CGMServiceCommand.DISCONNECT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
@@ -26,24 +28,24 @@ internal fun CSCContentView(state: CSCData, onEvent: (CSCViewEvent) -> Unit) {
|
|||||||
SelectWheelSizeDialog { onEvent(it) }
|
SelectWheelSizeDialog { onEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
modifier = Modifier.padding(16.dp)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
SettingsSection(state, onEvent)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
SensorsReadingView(state = state)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = { onEvent(OnDisconnectButtonClick) }
|
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(id = R.string.disconnect))
|
SettingsSection(state, onEvent)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
SensorsReadingView(state = state)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { onEvent(OnDisconnectButtonClick) }
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.disconnect))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ internal class PRXService : ForegroundBleService() {
|
|||||||
DisableAlarm -> manager.writeImmediateAlert(false)
|
DisableAlarm -> manager.writeImmediateAlert(false)
|
||||||
EnableAlarm -> manager.writeImmediateAlert(true)
|
EnableAlarm -> manager.writeImmediateAlert(true)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}.launchIn(lifecycleScope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
dataHolder.data.onEach {
|
dataHolder.data.onEach {
|
||||||
if (it.localAlarmLevel != AlarmLevel.NONE) {
|
if (it.localAlarmLevel != AlarmLevel.NONE) {
|
||||||
@@ -47,7 +47,7 @@ internal class PRXService : ForegroundBleService() {
|
|||||||
} else {
|
} else {
|
||||||
alarmHandler.pauseAlarm()
|
alarmHandler.pauseAlarm()
|
||||||
}
|
}
|
||||||
}.launchIn(lifecycleScope)
|
}.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Nordic Semiconductor
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
||||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package no.nordicsemi.android.rscs.service
|
|
||||||
|
|
||||||
import no.nordicsemi.android.ble.data.Data
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
internal object RSCMeasurementParser {
|
|
||||||
|
|
||||||
private const val INSTANTANEOUS_STRIDE_LENGTH_PRESENT: Byte = 0x01 // 1 bit
|
|
||||||
private const val TOTAL_DISTANCE_PRESENT: Byte = 0x02 // 1 bit
|
|
||||||
private const val WALKING_OR_RUNNING_STATUS_BITS: Byte = 0x04 // 1 bit
|
|
||||||
|
|
||||||
fun parse(data: Data): String {
|
|
||||||
var offset = 0
|
|
||||||
val flags = data.value!![offset].toInt() // 1 byte
|
|
||||||
offset += 1
|
|
||||||
val islmPresent = flags and INSTANTANEOUS_STRIDE_LENGTH_PRESENT.toInt() > 0
|
|
||||||
val tdPreset = flags and TOTAL_DISTANCE_PRESENT.toInt() > 0
|
|
||||||
val running = flags and WALKING_OR_RUNNING_STATUS_BITS.toInt() > 0
|
|
||||||
val walking = !running
|
|
||||||
val instantaneousSpeed =
|
|
||||||
data.getIntValue(Data.FORMAT_UINT16, offset) as Float / 256.0f // 1/256 m/s
|
|
||||||
offset += 2
|
|
||||||
val instantaneousCadence = data.getIntValue(Data.FORMAT_UINT8, offset)!!
|
|
||||||
offset += 1
|
|
||||||
var instantaneousStrideLength = 0f
|
|
||||||
if (islmPresent) {
|
|
||||||
instantaneousStrideLength =
|
|
||||||
data.getIntValue(Data.FORMAT_UINT16, offset) as Float / 100.0f // 1/100 m
|
|
||||||
offset += 2
|
|
||||||
}
|
|
||||||
var totalDistance = 0f
|
|
||||||
if (tdPreset) {
|
|
||||||
totalDistance = data.getIntValue(Data.FORMAT_UINT32, offset) as Float / 10.0f
|
|
||||||
// offset += 4;
|
|
||||||
}
|
|
||||||
val builder = StringBuilder()
|
|
||||||
builder.append(
|
|
||||||
String.format(
|
|
||||||
Locale.US,
|
|
||||||
"Speed: %.2f m/s, Cadence: %d RPM,\n",
|
|
||||||
instantaneousSpeed,
|
|
||||||
instantaneousCadence
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (islmPresent) builder.append(
|
|
||||||
String.format(
|
|
||||||
Locale.US,
|
|
||||||
"Instantaneous Stride Length: %.2f m,\n",
|
|
||||||
instantaneousStrideLength
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (tdPreset) builder.append(
|
|
||||||
String.format(
|
|
||||||
Locale.US,
|
|
||||||
"Total Distance: %.1f m,\n",
|
|
||||||
totalDistance
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (walking) builder.append("Status: WALKING") else builder.append("Status: RUNNING")
|
|
||||||
return builder.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,8 +26,6 @@ import android.bluetooth.BluetoothGatt
|
|||||||
import android.bluetooth.BluetoothGattCharacteristic
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import no.nordicsemi.android.ble.common.callback.rsc.RunningSpeedAndCadenceMeasurementDataCallback
|
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.RSCSRepository
|
import no.nordicsemi.android.rscs.data.RSCSRepository
|
||||||
import no.nordicsemi.android.service.BatteryManager
|
import no.nordicsemi.android.service.BatteryManager
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -46,13 +44,6 @@ internal class RSCSManager internal constructor(
|
|||||||
private var rscMeasurementCharacteristic: BluetoothGattCharacteristic? = null
|
private var rscMeasurementCharacteristic: BluetoothGattCharacteristic? = null
|
||||||
|
|
||||||
private val callback = object : RunningSpeedAndCadenceMeasurementDataCallback() {
|
private val callback = object : RunningSpeedAndCadenceMeasurementDataCallback() {
|
||||||
override fun onDataReceived(device: BluetoothDevice, data: Data) {
|
|
||||||
log(
|
|
||||||
LogContract.Log.Level.APPLICATION,
|
|
||||||
"\"" + RSCMeasurementParser.parse(data).toString() + "\" received"
|
|
||||||
)
|
|
||||||
super.onDataReceived(device, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRSCMeasurementReceived(
|
override fun onRSCMeasurementReceived(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import no.nordicsemi.android.service.BleManagerStatus
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -17,6 +18,9 @@ internal class UARTRepository @Inject constructor() {
|
|||||||
private val _command = MutableSharedFlow<UARTServiceCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
private val _command = MutableSharedFlow<UARTServiceCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
||||||
val command = _command.asSharedFlow()
|
val command = _command.asSharedFlow()
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
||||||
|
val status = _status.asStateFlow()
|
||||||
|
|
||||||
fun addNewMacro(macro: UARTMacro) {
|
fun addNewMacro(macro: UARTMacro) {
|
||||||
_data.tryEmit(_data.value.copy(macros = _data.value.macros + macro))
|
_data.tryEmit(_data.value.copy(macros = _data.value.macros + macro))
|
||||||
}
|
}
|
||||||
@@ -39,4 +43,8 @@ internal class UARTRepository @Inject constructor() {
|
|||||||
fun sendNewCommand(command: UARTServiceCommand) {
|
fun sendNewCommand(command: UARTServiceCommand) {
|
||||||
_command.tryEmit(command)
|
_command.tryEmit(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setNewStatus(status: BleManagerStatus) {
|
||||||
|
_status.value = status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
package no.nordicsemi.android.uart.data
|
package no.nordicsemi.android.uart.data
|
||||||
|
|
||||||
data class UARTServiceCommand(val command: String)
|
internal sealed class UARTServiceCommand
|
||||||
|
|
||||||
|
internal data class SendTextCommand(val command: String) : UARTServiceCommand()
|
||||||
|
|
||||||
|
internal object DisconnectCommand : UARTServiceCommand()
|
||||||
|
|||||||
@@ -1,26 +1,35 @@
|
|||||||
package no.nordicsemi.android.uart.repository
|
package no.nordicsemi.android.uart.repository
|
||||||
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.ForegroundBleService
|
import no.nordicsemi.android.service.ForegroundBleService
|
||||||
|
import no.nordicsemi.android.uart.data.DisconnectCommand
|
||||||
|
import no.nordicsemi.android.uart.data.SendTextCommand
|
||||||
import no.nordicsemi.android.uart.data.UARTRepository
|
import no.nordicsemi.android.uart.data.UARTRepository
|
||||||
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
internal class UARTService : ForegroundBleService() {
|
internal class UARTService : ForegroundBleService() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var dataHolder: UARTRepository
|
lateinit var repository: UARTRepository
|
||||||
|
|
||||||
override val manager: UARTManager by lazy { UARTManager(this, dataHolder) }
|
override val manager: UARTManager by lazy { UARTManager(this, repository) }
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
dataHolder.command.onEach {
|
status.onEach {
|
||||||
manager.send(it.command)
|
repository.setNewStatus(it)
|
||||||
}.launchIn(lifecycleScope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
repository.command.onEach {
|
||||||
|
when (it) {
|
||||||
|
DisconnectCommand -> stopSelf()
|
||||||
|
is SendTextCommand -> manager.send(it.command)
|
||||||
|
}.exhaustive
|
||||||
|
}.launchIn(scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,46 +9,40 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
|
import no.nordicsemi.android.theme.view.DeviceConnectingView
|
||||||
import no.nordicsemi.android.uart.R
|
import no.nordicsemi.android.uart.R
|
||||||
import no.nordicsemi.android.uart.data.UARTData
|
|
||||||
import no.nordicsemi.android.uart.repository.UARTService
|
import no.nordicsemi.android.uart.repository.UARTService
|
||||||
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
|
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
|
||||||
import no.nordicsemi.android.utils.isServiceRunning
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UARTScreen(finishAction: () -> Unit) {
|
fun UARTScreen(finishAction: () -> Unit) {
|
||||||
val viewModel: UARTViewModel = hiltViewModel()
|
val viewModel: UARTViewModel = hiltViewModel()
|
||||||
val state = viewModel.state.collectAsState().value
|
val state = viewModel.state.collectAsState().value
|
||||||
val isScreenActive = viewModel.isActive.collectAsState().value
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
LaunchedEffect(isScreenActive) {
|
LaunchedEffect(state.isActive) {
|
||||||
if (!isScreenActive) {
|
if (state.isActive) {
|
||||||
finishAction()
|
|
||||||
}
|
|
||||||
if (context.isServiceRunning(UARTService::class.java.name)) {
|
|
||||||
val intent = Intent(context, UARTService::class.java)
|
|
||||||
context.stopService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect("start-service") {
|
|
||||||
if (!context.isServiceRunning(UARTService::class.java.name)) {
|
|
||||||
val intent = Intent(context, UARTService::class.java)
|
val intent = Intent(context, UARTService::class.java)
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
|
} else if (!state.isActive) {
|
||||||
|
finishAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UARTView(state) { viewModel.onEvent(it) }
|
UARTView(state.viewState) { viewModel.onEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UARTView(state: UARTData, onEvent: (UARTViewEvent) -> Unit) {
|
private fun UARTView(state: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
|
||||||
Column {
|
Column {
|
||||||
BackIconAppBar(stringResource(id = R.string.uart_title)) {
|
BackIconAppBar(stringResource(id = R.string.uart_title)) {
|
||||||
onEvent(OnDisconnectButtonClick)
|
onEvent(OnDisconnectButtonClick)
|
||||||
}
|
}
|
||||||
|
|
||||||
UARTContentView(state) { onEvent(it) }
|
when (state) {
|
||||||
|
is DisplayDataState -> UARTContentView(state.data) { onEvent(it) }
|
||||||
|
LoadingState -> DeviceConnectingView()
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package no.nordicsemi.android.uart.view
|
||||||
|
|
||||||
|
import no.nordicsemi.android.uart.data.UARTData
|
||||||
|
|
||||||
|
internal data class UARTState(
|
||||||
|
val viewState: UARTViewState,
|
||||||
|
val isActive: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
internal sealed class UARTViewState
|
||||||
|
|
||||||
|
internal object LoadingState : UARTViewState()
|
||||||
|
|
||||||
|
internal data class DisplayDataState(val data: UARTData) : UARTViewState()
|
||||||
@@ -1,26 +1,46 @@
|
|||||||
package no.nordicsemi.android.uart.viewmodel
|
package no.nordicsemi.android.uart.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import no.nordicsemi.android.service.BleManagerStatus
|
||||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
||||||
|
import no.nordicsemi.android.uart.data.DisconnectCommand
|
||||||
|
import no.nordicsemi.android.uart.data.SendTextCommand
|
||||||
import no.nordicsemi.android.uart.data.UARTRepository
|
import no.nordicsemi.android.uart.data.UARTRepository
|
||||||
import no.nordicsemi.android.uart.data.UARTServiceCommand
|
import no.nordicsemi.android.uart.view.DisplayDataState
|
||||||
import no.nordicsemi.android.uart.view.*
|
import no.nordicsemi.android.uart.view.LoadingState
|
||||||
|
import no.nordicsemi.android.uart.view.OnCreateMacro
|
||||||
|
import no.nordicsemi.android.uart.view.OnDeleteMacro
|
||||||
|
import no.nordicsemi.android.uart.view.OnDisconnectButtonClick
|
||||||
|
import no.nordicsemi.android.uart.view.OnRunMacro
|
||||||
|
import no.nordicsemi.android.uart.view.UARTState
|
||||||
|
import no.nordicsemi.android.uart.view.UARTViewEvent
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
internal class UARTViewModel @Inject constructor(
|
internal class UARTViewModel @Inject constructor(
|
||||||
private val dataHolder: UARTRepository
|
private val repository: UARTRepository
|
||||||
) : CloseableViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val state = dataHolder.data
|
val state = repository.data.combine(repository.status) { data, status ->
|
||||||
|
when (status) {
|
||||||
|
BleManagerStatus.CONNECTING -> UARTState(LoadingState)
|
||||||
|
BleManagerStatus.OK -> UARTState(DisplayDataState(data))
|
||||||
|
BleManagerStatus.DISCONNECTED -> UARTState(DisplayDataState(data), false)
|
||||||
|
}
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.Lazily, UARTState(LoadingState))
|
||||||
|
|
||||||
fun onEvent(event: UARTViewEvent) {
|
fun onEvent(event: UARTViewEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is OnCreateMacro -> dataHolder.addNewMacro(event.macro)
|
is OnCreateMacro -> repository.addNewMacro(event.macro)
|
||||||
is OnDeleteMacro -> dataHolder.deleteMacro(event.macro)
|
is OnDeleteMacro -> repository.deleteMacro(event.macro)
|
||||||
OnDisconnectButtonClick -> finish()
|
OnDisconnectButtonClick -> repository.sendNewCommand(DisconnectCommand)
|
||||||
is OnRunMacro -> dataHolder.sendNewCommand(UARTServiceCommand(event.macro.command))
|
is OnRunMacro -> repository.sendNewCommand(SendTextCommand(event.macro.command))
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ dependencyResolutionManagement {
|
|||||||
alias('compose-navigation').to('androidx.navigation:navigation-compose:2.4.0-alpha09')
|
alias('compose-navigation').to('androidx.navigation:navigation-compose:2.4.0-alpha09')
|
||||||
bundle('compose', ['compose-livedata', 'compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])
|
bundle('compose', ['compose-livedata', 'compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])
|
||||||
|
|
||||||
|
alias('material-icons').to('androidx.compose.material', 'material-icons-core').versionRef('compose')
|
||||||
|
alias('material-icons-extended').to('androidx.compose.material', 'material-icons-extended').versionRef('compose')
|
||||||
|
bundle('icons', ['material-icons', 'material-icons-extended'])
|
||||||
|
|
||||||
version('hilt', '2.38.1')
|
version('hilt', '2.38.1')
|
||||||
alias('hilt-android').to('com.google.dagger', 'hilt-android').versionRef('hilt')
|
alias('hilt-android').to('com.google.dagger', 'hilt-android').versionRef('hilt')
|
||||||
alias('hilt-compiler').to('com.google.dagger', 'hilt-compiler').versionRef('hilt')
|
alias('hilt-compiler').to('com.google.dagger', 'hilt-compiler').versionRef('hilt')
|
||||||
|
|||||||
Reference in New Issue
Block a user