mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-20 07:54:20 +01:00
Merge pull request #113 from NordicSemiconductor/feature/uart_improvement
Feature/uart improvement
This commit is contained in:
@@ -34,6 +34,7 @@
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||
android:theme="@style/AppTheme.SplashScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -2,7 +2,6 @@ package no.nordicsemi.android.nrftoolbox
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -13,7 +12,6 @@ import no.nordicsemi.android.material.you.NordicActivity
|
||||
import no.nordicsemi.android.material.you.NordicTheme
|
||||
import no.nordicsemi.android.navigation.NavigationView
|
||||
import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals
|
||||
import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel
|
||||
import no.nordicsemi.ui.scanner.ScannerDestinations
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package no.nordicsemi.android.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothDevice
|
||||
|
||||
sealed class BleManagerResult <T> {
|
||||
|
||||
fun isRunning(): Boolean {
|
||||
@@ -15,8 +18,13 @@ sealed class BleManagerResult <T> {
|
||||
}
|
||||
}
|
||||
|
||||
class IdleResult<T> : BleManagerResult<T>()
|
||||
class ConnectingResult<T> : BleManagerResult<T>()
|
||||
data class SuccessResult<T>(val data: T) : BleManagerResult<T>()
|
||||
data class SuccessResult<T>(val device: BluetoothDevice, val data: T) : BleManagerResult<T>() {
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun deviceName(): String = device.name ?: device.address
|
||||
}
|
||||
|
||||
class LinkLossResult<T>(val data: T) : BleManagerResult<T>()
|
||||
class DisconnectedResult<T> : BleManagerResult<T>()
|
||||
|
||||
@@ -34,7 +34,7 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
||||
|
||||
override fun onDeviceReady(device: BluetoothDevice) {
|
||||
Log.d(TAG, "onDeviceReady()")
|
||||
_status.value = SuccessResult(lastValue!!)
|
||||
_status.value = SuccessResult(device, lastValue!!)
|
||||
}
|
||||
|
||||
override fun onDeviceDisconnecting(device: BluetoothDevice) {
|
||||
@@ -53,8 +53,8 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
||||
|
||||
fun setValue(value: T) {
|
||||
lastValue = value
|
||||
if (_status.value.isRunning()) {
|
||||
_status.value = SuccessResult(value)
|
||||
(_status.value as? SuccessResult)?.let {
|
||||
_status.value = SuccessResult(it.device, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,10 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SmallTopAppBar
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -56,6 +50,39 @@ fun TitleAppBar(text: String) {
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoggerBackIconAppBar(text: String, onClick: () -> Unit) {
|
||||
SmallTopAppBar(
|
||||
title = { Text(text) },
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.primary,
|
||||
containerColor = colorResource(id = R.color.appBarColor),
|
||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { onClick() }) {
|
||||
Icon(
|
||||
Icons.Default.ArrowBack,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
contentDescription = stringResource(id = R.string.back_screen),
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { onClick() }) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_logger),
|
||||
contentDescription = stringResource(id = R.string.open_logger),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BackIconAppBar(text: String, onClick: () -> Unit) {
|
||||
SmallTopAppBar(
|
||||
@@ -71,25 +98,16 @@ fun BackIconAppBar(text: String, onClick: () -> Unit) {
|
||||
IconButton(onClick = { onClick() }) {
|
||||
Icon(
|
||||
Icons.Default.ArrowBack,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
contentDescription = stringResource(id = R.string.back_screen),
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { onClick() }) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_logger),
|
||||
contentDescription = stringResource(id = R.string.back_screen),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoggerIconAppBar(text: String, onClick: () -> Unit, onLoggerClick: () -> Unit) {
|
||||
fun LoggerIconAppBar(text: String, onClick: () -> Unit, onDisconnectClick: () -> Unit, onLoggerClick: () -> Unit) {
|
||||
SmallTopAppBar(
|
||||
title = { Text(text) },
|
||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||
@@ -103,15 +121,25 @@ fun LoggerIconAppBar(text: String, onClick: () -> Unit, onLoggerClick: () -> Uni
|
||||
IconButton(onClick = { onClick() }) {
|
||||
Icon(
|
||||
Icons.Default.ArrowBack,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
contentDescription = stringResource(id = R.string.back_screen),
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
TextButton(
|
||||
onClick = { onDisconnectClick() },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
) {
|
||||
Text(stringResource(id = R.string.disconnect))
|
||||
}
|
||||
IconButton(onClick = { onLoggerClick() }) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_logger),
|
||||
contentDescription = stringResource(id = R.string.back_screen),
|
||||
contentDescription = stringResource(id = R.string.open_logger),
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
@@ -17,43 +18,16 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import no.nordicsemi.android.material.you.Card
|
||||
import no.nordicsemi.android.theme.R
|
||||
|
||||
@Composable
|
||||
fun StringListDialog(config: StringListDialogConfig) {
|
||||
Dialog(onDismissRequest = { config.onResult(FlowCanceled) }) {
|
||||
StringListView(config)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StringListView(config: StringListDialogConfig) {
|
||||
Card(
|
||||
modifier = Modifier.height(300.dp),
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
elevation = 0.dp
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = config.title ?: stringResource(id = R.string.dialog).toAnnotatedString(),
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { config.onResult(FlowCanceled) },
|
||||
title = { Text(text = config.title ?: stringResource(id = R.string.dialog).toAnnotatedString()) },
|
||||
text = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight(0.8f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
|
||||
@@ -81,17 +55,13 @@ fun StringListView(config: StringListDialogConfig) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
TextButton(onClick = { config.onResult(FlowCanceled) }) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.cancel),
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { config.onResult(FlowCanceled) }) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.cancel),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
<string name="app_name">nRF Toolbox</string>
|
||||
|
||||
<string name="dialog">Dialog</string>
|
||||
<string name="cancel">CANCEL</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
|
||||
<string name="go_up">Back</string>
|
||||
|
||||
<string name="close_app">Close the application.</string>
|
||||
<string name="back_screen">Close the current screen.</string>
|
||||
<string name="open_logger">Open logger application.</string>
|
||||
|
||||
<string name="disconnect">Disconnect</string>
|
||||
<string name="field_battery">Battery</string>
|
||||
|
||||
@@ -61,7 +61,7 @@ internal class BPSManager(
|
||||
val dataHolder = ConnectionObserverAdapter<BPSData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package no.nordicsemi.android.bps.repository
|
||||
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
@@ -14,6 +13,7 @@ import no.nordicsemi.android.bps.data.BPSManager
|
||||
import no.nordicsemi.android.logger.ToolboxLogger
|
||||
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
@@ -25,9 +25,9 @@ internal class BPSRepository @Inject constructor(
|
||||
|
||||
private var logger: ToolboxLogger? = null
|
||||
|
||||
fun downloadData(device: BluetoothDevice): Flow<BleManagerResult<BPSData>> = callbackFlow {
|
||||
fun downloadData(device: DiscoveredBluetoothDevice): Flow<BleManagerResult<BPSData>> = callbackFlow {
|
||||
val scope = this
|
||||
val createdLogger = toolboxLoggerFactory.create("BPS", device.address).also {
|
||||
val createdLogger = toolboxLoggerFactory.create("BPS", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = BPSManager(context, scope, createdLogger)
|
||||
@@ -36,7 +36,7 @@ internal class BPSRepository @Inject constructor(
|
||||
trySend(it)
|
||||
}.launchIn(scope)
|
||||
|
||||
manager.connect(device)
|
||||
manager.connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.enqueue()
|
||||
|
||||
@@ -15,7 +15,7 @@ import no.nordicsemi.android.bps.R
|
||||
import no.nordicsemi.android.bps.data.BPSData
|
||||
|
||||
@Composable
|
||||
internal fun BPSContentView(state: BPSData, onEvent: (BPSScreenViewEvent) -> Unit) {
|
||||
internal fun BPSContentView(state: BPSData, onEvent: (BPSViewEvent) -> Unit) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package no.nordicsemi.android.bps.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -9,14 +10,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.bps.R
|
||||
import no.nordicsemi.android.bps.data.BPSData
|
||||
import no.nordicsemi.android.bps.viewmodel.BPSViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
@@ -27,16 +29,13 @@ fun BPSScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(DisconnectEvent) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.bps_title), {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
AppBar(state = state, navigateUp = navigateUp, viewModel = viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
@@ -48,3 +47,20 @@ fun BPSScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: BPSViewState, navigateUp: () -> Unit, viewModel: BPSViewModel) {
|
||||
val toolbarName = (state as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<BPSData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.bps_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}, { viewModel.onEvent(DisconnectEvent) }) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package no.nordicsemi.android.bps.view
|
||||
|
||||
internal sealed class BPSScreenViewEvent
|
||||
|
||||
internal object DisconnectEvent : BPSScreenViewEvent()
|
||||
|
||||
internal object OpenLoggerEvent : BPSScreenViewEvent()
|
||||
@@ -0,0 +1,7 @@
|
||||
package no.nordicsemi.android.bps.view
|
||||
|
||||
internal sealed class BPSViewEvent
|
||||
|
||||
internal object DisconnectEvent : BPSViewEvent()
|
||||
|
||||
internal object OpenLoggerEvent : BPSViewEvent()
|
||||
@@ -2,8 +2,12 @@ package no.nordicsemi.android.bps.view
|
||||
|
||||
import no.nordicsemi.android.bps.data.BPSData
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
|
||||
internal sealed class BPSViewState
|
||||
|
||||
internal data class WorkingState(val result: BleManagerResult<BPSData>) : BPSViewState()
|
||||
internal data class WorkingState(
|
||||
val result: BleManagerResult<BPSData>
|
||||
) : BPSViewState()
|
||||
|
||||
internal object NoDeviceState : BPSViewState()
|
||||
|
||||
@@ -43,7 +43,7 @@ internal class BPSViewModel @Inject constructor(
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
fun onEvent(event: BPSScreenViewEvent) {
|
||||
fun onEvent(event: BPSViewEvent) {
|
||||
when (event) {
|
||||
DisconnectEvent -> navigationManager.navigateUp()
|
||||
OpenLoggerEvent -> repository.openLogger()
|
||||
@@ -51,7 +51,7 @@ internal class BPSViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun connectDevice(device: DiscoveredBluetoothDevice) {
|
||||
repository.downloadData(device.device).onEach {
|
||||
repository.downloadData(device).onEach {
|
||||
_state.value = WorkingState(it)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ internal class CGMManager(
|
||||
val dataHolder = ConnectionObserverAdapter<CGMData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -33,12 +34,12 @@ class CGMRepository @Inject constructor(
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
fun launch(device: DiscoveredBluetoothDevice) {
|
||||
serviceManager.startService(CGMService::class.java, device)
|
||||
}
|
||||
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("CGMS", device.address).also {
|
||||
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("CGMS", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = CGMManager(context, scope, createdLogger)
|
||||
@@ -53,9 +54,9 @@ class CGMRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun CGMManager.start(device: BluetoothDevice) {
|
||||
private suspend fun CGMManager.start(device: DiscoveredBluetoothDevice) {
|
||||
try {
|
||||
connect(device)
|
||||
connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -19,7 +20,7 @@ internal class CGMService : NotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
|
||||
@@ -9,32 +9,32 @@ import androidx.compose.ui.Modifier
|
||||
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.cgms.data.CGMData
|
||||
import no.nordicsemi.android.cgms.viewmodel.CGMViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
fun CGMScreen() {
|
||||
val viewModel: CGMScreenViewModel = hiltViewModel()
|
||||
val viewModel: CGMViewModel = hiltViewModel()
|
||||
val state = viewModel.state.collectAsState().value
|
||||
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.cgms_title), navigateUp) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
AppBar(state, navigateUp, viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
@@ -46,3 +46,18 @@ fun CGMScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: CGMViewState, navigateUp: () -> Unit, viewModel: CGMViewModel) {
|
||||
val toolbarName = (state as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<CGMData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.cgms_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package no.nordicsemi.android.cgms.view
|
||||
import no.nordicsemi.android.cgms.data.CGMData
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
|
||||
internal sealed class BPSViewState
|
||||
internal sealed class CGMViewState
|
||||
|
||||
internal data class WorkingState(val result: BleManagerResult<CGMData>) : BPSViewState()
|
||||
internal object NoDeviceState : BPSViewState()
|
||||
internal data class WorkingState(val result: BleManagerResult<CGMData>) : CGMViewState()
|
||||
internal object NoDeviceState : CGMViewState()
|
||||
|
||||
@@ -16,12 +16,12 @@ import no.nordicsemi.ui.scanner.ScannerDestinationId
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class CGMScreenViewModel @Inject constructor(
|
||||
internal class CGMViewModel @Inject constructor(
|
||||
private val repository: CGMRepository,
|
||||
private val navigationManager: NavigationManager
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow<BPSViewState>(NoDeviceState)
|
||||
private val _state = MutableStateFlow<CGMViewState>(NoDeviceState)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
@@ -58,7 +58,7 @@ internal class CGMScreenViewModel @Inject constructor(
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ internal class CSCManager(
|
||||
val dataHolder = ConnectionObserverAdapter<CSCData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -15,6 +15,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -34,12 +35,12 @@ class CSCRepository @Inject constructor(
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
fun launch(device: DiscoveredBluetoothDevice) {
|
||||
serviceManager.startService(CSCService::class.java, device)
|
||||
}
|
||||
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("CSC", device.address).also {
|
||||
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("CSC", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = CSCManager(context, scope, createdLogger)
|
||||
@@ -58,9 +59,9 @@ class CSCRepository @Inject constructor(
|
||||
manager?.setWheelSize(wheelSize)
|
||||
}
|
||||
|
||||
private suspend fun CSCManager.start(device: BluetoothDevice) {
|
||||
private suspend fun CSCManager.start(device: DiscoveredBluetoothDevice) {
|
||||
try {
|
||||
connect(device)
|
||||
connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -19,7 +20,7 @@ internal class CSCService : NotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
|
||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.csc.R
|
||||
import no.nordicsemi.android.csc.data.CSCData
|
||||
import no.nordicsemi.android.csc.viewmodel.CSCViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
@@ -27,14 +28,13 @@ fun CSCScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.csc_title), navigateUp) {
|
||||
viewModel.onEvent(OpenLogger)
|
||||
}
|
||||
AppBar(state, navigateUp, viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state.cscManagerState) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.cscManagerState.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
@@ -46,3 +46,18 @@ fun CSCScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: CSCViewState, navigateUp: () -> Unit, viewModel: CSCViewModel) {
|
||||
val toolbarName = (state.cscManagerState as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<CSCData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.csc_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(OnDisconnectButtonClick) }) {
|
||||
viewModel.onEvent(OpenLogger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ internal class CSCViewModel @Inject constructor(
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ internal class GLSManager(
|
||||
val dataHolder = ConnectionObserverAdapter<GLSData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.gls.R
|
||||
import no.nordicsemi.android.gls.details.viewmodel.GLSDetailsViewModel
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerBackIconAppBar
|
||||
|
||||
@Composable
|
||||
internal fun GLSDetailsScreen() {
|
||||
@@ -14,7 +14,7 @@ internal fun GLSDetailsScreen() {
|
||||
val record = viewModel.record
|
||||
|
||||
Column {
|
||||
BackIconAppBar(stringResource(id = R.string.gls_title)) {
|
||||
LoggerBackIconAppBar(stringResource(id = R.string.gls_title)) {
|
||||
viewModel.navigateBack()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.gls.R
|
||||
import no.nordicsemi.android.gls.data.GLSData
|
||||
import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
@@ -27,16 +28,13 @@ fun GLSScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(DisconnectEvent) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.gls_title), {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
AppBar(state, navigateUp, viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
@@ -48,3 +46,20 @@ fun GLSScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: GLSViewState, navigateUp: () -> Unit, viewModel: GLSViewModel) {
|
||||
val toolbarName = (state as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<GLSData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.gls_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, {
|
||||
viewModel.onEvent(DisconnectEvent)
|
||||
}, { viewModel.onEvent(DisconnectEvent) }) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package no.nordicsemi.android.gls.main.view
|
||||
import no.nordicsemi.android.gls.data.GLSData
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
|
||||
internal sealed class BPSViewState
|
||||
internal sealed class GLSViewState
|
||||
|
||||
internal data class WorkingState(val result: BleManagerResult<GLSData>) : BPSViewState()
|
||||
internal object NoDeviceState : BPSViewState()
|
||||
internal data class WorkingState(val result: BleManagerResult<GLSData>) : GLSViewState()
|
||||
internal object NoDeviceState : GLSViewState()
|
||||
|
||||
@@ -21,7 +21,7 @@ internal class GLSViewModel @Inject constructor(
|
||||
private val navigationManager: NavigationManager
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow<BPSViewState>(NoDeviceState)
|
||||
private val _state = MutableStateFlow<GLSViewState>(NoDeviceState)
|
||||
val state = _state.asStateFlow()
|
||||
|
||||
init {
|
||||
@@ -52,7 +52,7 @@ internal class GLSViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun connectDevice(device: DiscoveredBluetoothDevice) {
|
||||
repository.downloadData(device.device).onEach {
|
||||
repository.downloadData(device).onEach {
|
||||
_state.value = WorkingState(it)
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import no.nordicsemi.android.logger.ToolboxLogger
|
||||
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
@@ -30,14 +31,14 @@ internal class GLSRepository @Inject constructor(
|
||||
private var manager: GLSManager? = null
|
||||
private var logger: ToolboxLogger? = null
|
||||
|
||||
fun downloadData(device: BluetoothDevice): Flow<BleManagerResult<GLSData>> = callbackFlow {
|
||||
fun downloadData(device: DiscoveredBluetoothDevice): Flow<BleManagerResult<GLSData>> = callbackFlow {
|
||||
val scope = this
|
||||
val createdLogger = toolboxLoggerFactory.create("GLS", device.address).also {
|
||||
val createdLogger = toolboxLoggerFactory.create("GLS", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val managerInstance = manager ?: GLSManager(context, scope, createdLogger).apply {
|
||||
try {
|
||||
connect(device)
|
||||
connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
|
||||
@@ -61,7 +61,7 @@ internal class HRSManager(
|
||||
val dataHolder = ConnectionObserverAdapter<HRSData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -33,12 +34,12 @@ class HRSRepository @Inject constructor(
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
fun launch(device: DiscoveredBluetoothDevice) {
|
||||
serviceManager.startService(HRSService::class.java, device)
|
||||
}
|
||||
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("HRS", device.address).also {
|
||||
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("HRS", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = HRSManager(context, scope, createdLogger)
|
||||
@@ -57,9 +58,9 @@ class HRSRepository @Inject constructor(
|
||||
logger?.openLogger()
|
||||
}
|
||||
|
||||
private suspend fun HRSManager.start(device: BluetoothDevice) {
|
||||
private suspend fun HRSManager.start(device: DiscoveredBluetoothDevice) {
|
||||
try {
|
||||
connect(device)
|
||||
connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -19,7 +20,7 @@ internal class HRSService : NotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
|
||||
@@ -9,8 +9,10 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.hrs.R
|
||||
import no.nordicsemi.android.hrs.data.HRSData
|
||||
import no.nordicsemi.android.hrs.viewmodel.HRSViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
@@ -26,14 +28,13 @@ fun HRSScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.hrs_title), navigateUp) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
AppBar(state, navigateUp, viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
@@ -45,3 +46,18 @@ fun HRSScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: HRSViewState, navigateUp: () -> Unit, viewModel: HRSViewModel) {
|
||||
val toolbarName = (state as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<HRSData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.hrs_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ internal class HRSViewModel @Inject constructor(
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ internal class HTSManager internal constructor(
|
||||
val dataHolder = ConnectionObserverAdapter<HTSData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -33,12 +34,12 @@ class HTSRepository @Inject constructor(
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
fun launch(device: DiscoveredBluetoothDevice) {
|
||||
serviceManager.startService(HTSService::class.java, device)
|
||||
}
|
||||
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("HTS", device.address).also {
|
||||
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("HTS", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = HTSManager(context, scope, createdLogger)
|
||||
@@ -57,9 +58,9 @@ class HTSRepository @Inject constructor(
|
||||
logger?.openLogger()
|
||||
}
|
||||
|
||||
private suspend fun HTSManager.start(device: BluetoothDevice) {
|
||||
private suspend fun HTSManager.start(device: DiscoveredBluetoothDevice) {
|
||||
try {
|
||||
connect(device)
|
||||
connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -19,7 +20,7 @@ internal class HTSService : NotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
|
||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.hts.R
|
||||
import no.nordicsemi.android.hts.data.HTSData
|
||||
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
@@ -27,14 +28,13 @@ fun HTSScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.hts_title), navigateUp) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
AppBar(state, navigateUp, viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state.htsManagerState) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.htsManagerState.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
@@ -46,3 +46,19 @@ fun HTSScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: HTSViewState, navigateUp: () -> Unit, viewModel: HTSViewModel) {
|
||||
val toolbarName = (state.htsManagerState as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<HTSData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.hts_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ internal class HTSViewModel @Inject constructor(
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ internal class PRXManager(
|
||||
val dataHolder = ConnectionObserverAdapter<PRXData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -12,6 +12,7 @@ import no.nordicsemi.android.prx.data.PRXData
|
||||
import no.nordicsemi.android.prx.data.PRXManager
|
||||
import no.nordicsemi.android.prx.data.ProximityServerManager
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -34,13 +35,13 @@ class PRXRepository @Inject internal constructor(
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnectedWithoutLinkLoss = data.map { it.hasBeenDisconnectedWithoutLinkLoss() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
fun launch(device: DiscoveredBluetoothDevice) {
|
||||
serviceManager.startService(PRXService::class.java, device)
|
||||
proximityServerManager.open()
|
||||
}
|
||||
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("PRX", device.address).also {
|
||||
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("PRX", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = PRXManager(context, scope, createdLogger)
|
||||
@@ -52,7 +53,7 @@ class PRXRepository @Inject internal constructor(
|
||||
handleLocalAlarm(it)
|
||||
}.launchIn(scope)
|
||||
|
||||
manager.connect(device)
|
||||
manager.connect(device.device)
|
||||
.useAutoConnect(true)
|
||||
.retry(3, 100)
|
||||
.enqueue()
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -19,7 +20,7 @@ internal class PRXService : NotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
|
||||
@@ -10,14 +10,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.prx.R
|
||||
import no.nordicsemi.android.prx.data.PRXData
|
||||
import no.nordicsemi.android.prx.viewmodel.PRXViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
@@ -28,14 +29,13 @@ fun PRXScreen() {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.prx_title), navigateUp) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
AppBar(state, navigateUp, viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) }
|
||||
@@ -47,3 +47,18 @@ fun PRXScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: PRXViewState, navigateUp: () -> Unit, viewModel: PRXViewModel) {
|
||||
val toolbarName = (state as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<PRXData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.prx_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ internal class PRXViewModel @Inject constructor(
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ internal class RSCSManager internal constructor(
|
||||
val dataHolder = ConnectionObserverAdapter<RSCSData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
|
||||
@@ -14,6 +14,7 @@ import no.nordicsemi.android.rscs.data.RSCSManager
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -33,12 +34,12 @@ class RSCSRepository @Inject constructor(
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
fun launch(device: DiscoveredBluetoothDevice) {
|
||||
serviceManager.startService(RSCSService::class.java, device)
|
||||
}
|
||||
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("RSCS", device.address).also {
|
||||
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("RSCS", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = RSCSManager(context, scope, createdLogger)
|
||||
@@ -57,9 +58,9 @@ class RSCSRepository @Inject constructor(
|
||||
logger?.openLogger()
|
||||
}
|
||||
|
||||
private suspend fun RSCSManager.start(device: BluetoothDevice) {
|
||||
private suspend fun RSCSManager.start(device: DiscoveredBluetoothDevice) {
|
||||
try {
|
||||
connect(device)
|
||||
connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -19,7 +20,7 @@ internal class RSCSService : NotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
|
||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.rscs.R
|
||||
import no.nordicsemi.android.rscs.data.RSCSData
|
||||
import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
@@ -27,14 +28,13 @@ fun RSCSScreen() {
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.rscs_title), navigateUp) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
AppBar(state, navigateUp, viewModel)
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
@@ -46,3 +46,18 @@ fun RSCSScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: RSCSViewState, navigateUp: () -> Unit, viewModel: RSCSViewModel) {
|
||||
val toolbarName = (state as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<RSCSData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.rscs_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) {
|
||||
viewModel.onEvent(OpenLoggerEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ internal class RSCSViewModel @Inject constructor(
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
internal data class UARTData(
|
||||
val messages: List<UARTOutputRecord> = emptyList(),
|
||||
val messages: List<UARTRecord> = emptyList(),
|
||||
val batteryLevel: Int? = null,
|
||||
) {
|
||||
|
||||
val displayMessages = messages.reversed().take(10)
|
||||
val displayMessages = messages
|
||||
}
|
||||
|
||||
internal data class UARTOutputRecord(
|
||||
internal data class UARTRecord(
|
||||
val text: String,
|
||||
val type: UARTRecordType,
|
||||
val timestamp: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
enum class UARTRecordType {
|
||||
INPUT, OUTPUT
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import no.nordicsemi.android.ble.BleManager
|
||||
import no.nordicsemi.android.ble.WriteRequest
|
||||
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
|
||||
@@ -49,7 +49,8 @@ private val UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0
|
||||
private val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||
|
||||
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")
|
||||
private val BATTERY_LEVEL_CHARACTERISTIC_UUID =
|
||||
UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
|
||||
|
||||
internal class UARTManager(
|
||||
context: Context,
|
||||
@@ -68,7 +69,7 @@ internal class UARTManager(
|
||||
val dataHolder = ConnectionObserverAdapter<UARTData>()
|
||||
|
||||
init {
|
||||
setConnectionObserver(dataHolder)
|
||||
connectionObserver = dataHolder
|
||||
|
||||
data.onEach {
|
||||
dataHolder.setValue(it)
|
||||
@@ -87,18 +88,25 @@ internal class UARTManager(
|
||||
|
||||
@SuppressLint("WrongConstant")
|
||||
override fun initialize() {
|
||||
setNotificationCallback(txCharacteristic).asFlow().onEach {
|
||||
val text: String = it.getStringValue(0) ?: String.EMPTY
|
||||
log(10, "\"$text\" received")
|
||||
data.value = data.value.copy(messages = data.value.messages + UARTOutputRecord(text))
|
||||
}.launchIn(scope)
|
||||
setNotificationCallback(txCharacteristic).asFlow()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.map {
|
||||
val text: String = it.getStringValue(0) ?: String.EMPTY
|
||||
log(10, "\"$text\" received")
|
||||
val messages = data.value.messages + UARTRecord(text, UARTRecordType.OUTPUT)
|
||||
messages.takeLast(50)
|
||||
}
|
||||
.onEach {
|
||||
data.value = data.value.copy(messages = it)
|
||||
}.launchIn(scope)
|
||||
|
||||
requestMtu(517).enqueue()
|
||||
enableNotifications(txCharacteristic).enqueue()
|
||||
|
||||
setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow<BatteryLevelResponse>().onEach {
|
||||
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||
}.launchIn(scope)
|
||||
setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow<BatteryLevelResponse>()
|
||||
.onEach {
|
||||
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||
}.launchIn(scope)
|
||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||
}
|
||||
|
||||
@@ -114,7 +122,8 @@ internal class UARTManager(
|
||||
rxCharacteristic?.let {
|
||||
val rxProperties: Int = it.properties
|
||||
writeRequest = rxProperties and BluetoothGattCharacteristic.PROPERTY_WRITE > 0
|
||||
writeCommand = rxProperties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE > 0
|
||||
writeCommand =
|
||||
rxProperties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE > 0
|
||||
|
||||
// Set the WRITE REQUEST type when the characteristic supports it.
|
||||
// This will allow to send long write (also if the characteristic support it).
|
||||
@@ -141,20 +150,25 @@ internal class UARTManager(
|
||||
@SuppressLint("WrongConstant")
|
||||
fun send(text: String) {
|
||||
if (rxCharacteristic == null) return
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
scope.launchWithCatch {
|
||||
val writeType = if (useLongWrite) {
|
||||
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
} else {
|
||||
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
}
|
||||
val request: WriteRequest = writeCharacteristic(rxCharacteristic, text.toByteArray(), writeType)
|
||||
if (!useLongWrite) {
|
||||
request.split()
|
||||
}
|
||||
request.suspend()
|
||||
log(10, "\"$text\" sent")
|
||||
scope.launchWithCatch {
|
||||
val writeType = if (useLongWrite) {
|
||||
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
} else {
|
||||
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||
}
|
||||
val request: WriteRequest =
|
||||
writeCharacteristic(rxCharacteristic, text.toByteArray(), writeType)
|
||||
if (!useLongWrite) {
|
||||
request.split()
|
||||
}
|
||||
request.suspend()
|
||||
data.value = data.value.copy(
|
||||
messages = data.value.messages + UARTRecord(
|
||||
text,
|
||||
UARTRecordType.INPUT
|
||||
)
|
||||
)
|
||||
log(10, "\"$text\" sent")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@ import no.nordicsemi.android.ble.ktx.suspend
|
||||
import no.nordicsemi.android.logger.ToolboxLogger
|
||||
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.service.ConnectingResult
|
||||
import no.nordicsemi.android.service.IdleResult
|
||||
import no.nordicsemi.android.service.ServiceManager
|
||||
import no.nordicsemi.android.uart.data.*
|
||||
import no.nordicsemi.android.utils.EMPTY
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -22,12 +24,12 @@ class UARTRepository @Inject internal constructor(
|
||||
private val context: Context,
|
||||
private val serviceManager: ServiceManager,
|
||||
private val configurationDataSource: ConfigurationDataSource,
|
||||
private val toolboxLoggerFactory: ToolboxLoggerFactory
|
||||
private val toolboxLoggerFactory: ToolboxLoggerFactory,
|
||||
) {
|
||||
private var manager: UARTManager? = null
|
||||
private var logger: ToolboxLogger? = null
|
||||
|
||||
private val _data = MutableStateFlow<BleManagerResult<UARTData>>(ConnectingResult())
|
||||
private val _data = MutableStateFlow<BleManagerResult<UARTData>>(IdleResult())
|
||||
internal val data = _data.asStateFlow()
|
||||
|
||||
val isRunning = data.map { it.isRunning() }
|
||||
@@ -35,12 +37,12 @@ class UARTRepository @Inject internal constructor(
|
||||
|
||||
val lastConfigurationName = configurationDataSource.lastConfigurationName
|
||||
|
||||
fun launch(device: BluetoothDevice) {
|
||||
fun launch(device: DiscoveredBluetoothDevice) {
|
||||
serviceManager.startService(UARTService::class.java, device)
|
||||
}
|
||||
|
||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("UART", device.address).also {
|
||||
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||
val createdLogger = toolboxLoggerFactory.create("UART", device.address()).also {
|
||||
logger = it
|
||||
}
|
||||
val manager = UARTManager(context, scope, createdLogger)
|
||||
@@ -60,9 +62,8 @@ class UARTRepository @Inject internal constructor(
|
||||
}
|
||||
|
||||
fun runMacro(macro: UARTMacro) {
|
||||
macro.command?.parseWithNewLineChar(macro.newLineChar)?.let {
|
||||
manager?.send(it)
|
||||
}
|
||||
val command = macro.command?.parseWithNewLineChar(macro.newLineChar)
|
||||
manager?.send(command ?: String.EMPTY)
|
||||
}
|
||||
|
||||
fun clearItems() {
|
||||
@@ -77,9 +78,9 @@ class UARTRepository @Inject internal constructor(
|
||||
configurationDataSource.saveConfigurationName(name)
|
||||
}
|
||||
|
||||
private suspend fun UARTManager.start(device: BluetoothDevice) {
|
||||
private suspend fun UARTManager.start(device: DiscoveredBluetoothDevice) {
|
||||
try {
|
||||
connect(device)
|
||||
connect(device.device)
|
||||
.useAutoConnect(false)
|
||||
.retry(3, 100)
|
||||
.suspend()
|
||||
|
||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import no.nordicsemi.android.service.DEVICE_DATA
|
||||
import no.nordicsemi.android.service.NotificationService
|
||||
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -19,7 +20,7 @@ internal class UARTService : NotificationService() {
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
||||
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||
|
||||
repository.start(device, lifecycleScope)
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package no.nordicsemi.android.uart.view
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.material.you.RadioButtonGroup
|
||||
import no.nordicsemi.android.material.you.RadioButtonItem
|
||||
import no.nordicsemi.android.material.you.RadioGroupViewEntity
|
||||
import no.nordicsemi.android.material.you.ScreenSection
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.uart.data.MacroEol
|
||||
import no.nordicsemi.android.utils.EMPTY
|
||||
|
||||
@Composable
|
||||
internal fun InputSection(onEvent: (UARTViewEvent) -> Unit) {
|
||||
val text = rememberSaveable { mutableStateOf(String.EMPTY) }
|
||||
val hint = stringResource(id = R.string.uart_input_hint)
|
||||
val checkedItem = rememberSaveable { mutableStateOf(MacroEol.values()[0]) }
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
value = text.value,
|
||||
label = { Text(hint) },
|
||||
onValueChange = { newValue: String ->
|
||||
text.value = newValue
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
onEvent(OnRunInput(text.value, checkedItem.value))
|
||||
text.value = String.EMPTY
|
||||
},
|
||||
modifier = Modifier.padding(top = 6.dp)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.uart_send))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun EditInputSection(onEvent: (UARTViewEvent) -> Unit) {
|
||||
val checkedItem = rememberSaveable { mutableStateOf(MacroEol.values()[0]) }
|
||||
|
||||
val items = MacroEol.values().map {
|
||||
RadioButtonItem(it.toDisplayString(), it == checkedItem.value)
|
||||
}
|
||||
val viewEntity = RadioGroupViewEntity(items)
|
||||
|
||||
ScreenSection {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
SectionTitle(
|
||||
resId = R.drawable.ic_input,
|
||||
title = stringResource(R.string.uart_input),
|
||||
menu = {
|
||||
IconButton(onClick = { onEvent(MacroInputSwitchClick) }) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_macro),
|
||||
contentDescription = stringResource(id = R.string.uart_input_macro),
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_macro_dialog_eol),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
RadioButtonGroup(viewEntity) {
|
||||
val i = items.indexOf(it)
|
||||
checkedItem.value = MacroEol.values()[i]
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package no.nordicsemi.android.uart.view
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.material.you.ScreenSection
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.android.uart.R
|
||||
|
||||
@Composable
|
||||
internal fun MacroSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
|
||||
val showAddDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val showDeleteDialog = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (showAddDialog.value) {
|
||||
UARTAddConfigurationDialog(onEvent) { showAddDialog.value = false }
|
||||
}
|
||||
|
||||
if (showDeleteDialog.value) {
|
||||
DeleteConfigurationDialog(onEvent) { showDeleteDialog.value = false }
|
||||
}
|
||||
|
||||
if (viewState.showEditDialog) {
|
||||
UARTAddMacroDialog(viewState.selectedMacro) { onEvent(it) }
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
ScreenSection {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
SectionTitle(
|
||||
resId = R.drawable.ic_macro,
|
||||
title = stringResource(R.string.uart_macros),
|
||||
menu = {
|
||||
viewState.selectedConfiguration?.let {
|
||||
if (!viewState.isConfigurationEdited) {
|
||||
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
|
||||
Icon(
|
||||
Icons.Default.Edit,
|
||||
stringResource(id = R.string.uart_configuration_edit)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_pencil_off),
|
||||
stringResource(id = R.string.uart_configuration_edit)
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { showDeleteDialog.value = true }) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
stringResource(id = R.string.uart_configuration_delete)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
UARTConfigurationPicker(viewState, onEvent)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Button(onClick = { showAddDialog.value = true }) {
|
||||
Text(stringResource(id = R.string.uart_configuration_add))
|
||||
}
|
||||
}
|
||||
|
||||
viewState.selectedConfiguration?.let {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
UARTMacroView(it, viewState.isConfigurationEdited, onEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeleteConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_delete_dialog_title),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_info))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDismiss()
|
||||
onEvent(OnDeleteConfiguration)
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package no.nordicsemi.android.uart.view
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.uart.data.UARTRecord
|
||||
import no.nordicsemi.android.uart.data.UARTRecordType
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
internal fun OutputSection(records: List<UARTRecord>, onEvent: (UARTViewEvent) -> Unit) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
SectionTitle(
|
||||
resId = R.drawable.ic_output,
|
||||
title = stringResource(R.string.uart_output),
|
||||
modifier = Modifier,
|
||||
menu = { Menu(onEvent) }
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
val scrollState = rememberLazyListState()
|
||||
val scrollDown = remember {
|
||||
derivedStateOf { scrollState.isScrolledToTheEnd() }
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = scrollState
|
||||
) {
|
||||
if (records.isEmpty()) {
|
||||
item { Text(text = stringResource(id = R.string.uart_output_placeholder)) }
|
||||
} else {
|
||||
records.forEach {
|
||||
item {
|
||||
when (it.type) {
|
||||
UARTRecordType.INPUT -> MessageItemInput(record = it)
|
||||
UARTRecordType.OUTPUT -> MessageItemOutput(record = it)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(records, scrollDown.value) {
|
||||
if (!scrollDown.value || records.isEmpty()) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
launch {
|
||||
scrollState.scrollToItem(records.lastIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListState.isScrolledToTheEnd() = layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1
|
||||
|
||||
@Composable
|
||||
private fun MessageItemInput(record: UARTRecord) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Text(
|
||||
text = record.timeToString(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp, bottomStart = 10.dp))
|
||||
.background(MaterialTheme.colorScheme.secondary)
|
||||
.padding(8.dp),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Text(
|
||||
text = record.text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessageItemOutput(record: UARTRecord) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Text(
|
||||
text = record.timeToString(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(topStart = 10.dp, topEnd = 10.dp, bottomEnd = 10.dp))
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = record.text,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Menu(onEvent: (UARTViewEvent) -> Unit) {
|
||||
Row {
|
||||
IconButton(onClick = { onEvent(ClearOutputItems) }) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = stringResource(id = R.string.uart_clear_items),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val datFormatter = SimpleDateFormat("dd MMMM yyyy, HH:mm:ss", Locale.ENGLISH)
|
||||
|
||||
private fun UARTRecord.timeToString(): String {
|
||||
return datFormatter.format(timestamp)
|
||||
}
|
||||
@@ -1,21 +1,12 @@
|
||||
package no.nordicsemi.android.uart.view
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import no.nordicsemi.android.material.you.TextField
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.utils.EMPTY
|
||||
|
||||
@@ -24,47 +15,28 @@ internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDism
|
||||
val name = rememberSaveable { mutableStateOf(String.EMPTY) }
|
||||
val isError = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Dialog(onDismissRequest = { onDismiss() }) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
shadowElevation = 2.dp,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_configuration_dialog_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
|
||||
NameInput(name, isError)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = { onDismiss() }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
TextButton(onClick = {
|
||||
if (isNameValid(name.value)) {
|
||||
onDismiss()
|
||||
onEvent(OnAddConfiguration(name.value))
|
||||
} else {
|
||||
isError.value = true
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = { onDismiss() },
|
||||
title = { Text(stringResource(id = R.string.uart_configuration_dialog_title)) },
|
||||
text = { NameInput(name, isError) },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (isNameValid(name.value)) {
|
||||
onDismiss()
|
||||
onEvent(OnAddConfiguration(name.value))
|
||||
} else {
|
||||
isError.value = true
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { onDismiss() }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -72,14 +44,17 @@ private fun NameInput(
|
||||
name: MutableState<String>,
|
||||
isError: MutableState<Boolean>
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
TextField(
|
||||
text = name.value,
|
||||
hint = stringResource(id = R.string.uart_configuration_hint)
|
||||
) {
|
||||
isError.value = false
|
||||
name.value = it
|
||||
}
|
||||
Column {
|
||||
|
||||
OutlinedTextField(
|
||||
value = name.value,
|
||||
label = { Text(stringResource(id = R.string.uart_configuration_hint)) },
|
||||
singleLine = true,
|
||||
onValueChange = {
|
||||
isError.value = false
|
||||
name.value = it
|
||||
}
|
||||
)
|
||||
|
||||
val errorText = if (isError.value) {
|
||||
stringResource(id = R.string.uart_name_empty)
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
package no.nordicsemi.android.uart.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.GridCells
|
||||
import androidx.compose.foundation.lazy.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -25,11 +19,9 @@ import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import no.nordicsemi.android.material.you.RadioButtonGroup
|
||||
import no.nordicsemi.android.material.you.RadioButtonItem
|
||||
import no.nordicsemi.android.material.you.RadioGroupViewEntity
|
||||
import no.nordicsemi.android.material.you.TextField
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.uart.data.MacroEol
|
||||
import no.nordicsemi.android.uart.data.MacroIcon
|
||||
@@ -38,130 +30,85 @@ import no.nordicsemi.android.utils.EMPTY
|
||||
|
||||
private const val GRID_SIZE = 5
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun UARTAddMacroDialog(macro: UARTMacro?, onEvent: (UARTViewEvent) -> Unit) {
|
||||
val newLineChar = rememberSaveable { mutableStateOf(macro?.newLineChar ?: MacroEol.LF) }
|
||||
val command = rememberSaveable { mutableStateOf(macro?.command ?: String.EMPTY) }
|
||||
val isError = rememberSaveable { mutableStateOf(false) }
|
||||
val selectedIcon = rememberSaveable { mutableStateOf(macro?.icon ?: MacroIcon.values()[0]) }
|
||||
|
||||
Dialog(onDismissRequest = { onEvent(OnEditFinish) }) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
shadowElevation = 0.dp,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_macro_dialog_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
AlertDialog(
|
||||
onDismissRequest = { onEvent(OnEditFinish) },
|
||||
dismissButton = {
|
||||
TextButton(onClick = { onEvent(OnDeleteMacro) }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_delete))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onEvent(OnCreateMacro(UARTMacro(selectedIcon.value, command.value, newLineChar.value)))
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_macro_dialog_title),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
},
|
||||
text = {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(GRID_SIZE),
|
||||
modifier = Modifier.wrapContentHeight()
|
||||
) {
|
||||
item(span = { GridItemSpan(GRID_SIZE) }) {
|
||||
Column {
|
||||
NewLineCharSection(newLineChar.value) { newLineChar.value = it }
|
||||
|
||||
LazyVerticalGrid(
|
||||
cells = GridCells.Fixed(GRID_SIZE),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
item(span = { GridItemSpan(GRID_SIZE) }) {
|
||||
Column {
|
||||
NewLineCharSection(newLineChar.value) { newLineChar.value = it }
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
item(span = { GridItemSpan(GRID_SIZE) }) {
|
||||
CommandInput(command)
|
||||
}
|
||||
|
||||
items(20) { item ->
|
||||
val icon = MacroIcon.create(item)
|
||||
val background = if (selectedIcon.value == icon) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
|
||||
item(span = { GridItemSpan(GRID_SIZE) }) {
|
||||
CommandInput(command, isError)
|
||||
}
|
||||
|
||||
items(20) { item ->
|
||||
val icon = MacroIcon.create(item)
|
||||
val background = if (selectedIcon.value == icon) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = icon.toResId()),
|
||||
contentDescription = stringResource(id = R.string.uart_macro_icon),
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer),
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.clickable { selectedIcon.value = icon }
|
||||
.background(background)
|
||||
)
|
||||
}
|
||||
|
||||
item(span = { GridItemSpan(GRID_SIZE) }) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = { onEvent(OnEditFinish) }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
TextButton(onClick = { onEvent(OnDeleteMacro) }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_delete))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
TextButton(onClick = {
|
||||
if (isCommandValid(command.value)) {
|
||||
onEvent(
|
||||
OnCreateMacro(
|
||||
UARTMacro(
|
||||
selectedIcon.value,
|
||||
command.value,
|
||||
newLineChar.value
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
isError.value = true
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
}
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = icon.toResId()),
|
||||
contentDescription = stringResource(id = R.string.uart_macro_icon),
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer),
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.clickable { selectedIcon.value = icon }
|
||||
.background(background)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CommandInput(
|
||||
command: MutableState<String>,
|
||||
isError: MutableState<Boolean>
|
||||
) {
|
||||
private fun CommandInput(command: MutableState<String>) {
|
||||
Column {
|
||||
TextField(
|
||||
text = command.value,
|
||||
hint = stringResource(id = R.string.uart_macro_dialog_command)
|
||||
) {
|
||||
isError.value = false
|
||||
command.value = it
|
||||
}
|
||||
|
||||
if (isError.value) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_macro_error),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
value = command.value,
|
||||
label = { Text(stringResource(id = R.string.uart_macro_dialog_command)) },
|
||||
onValueChange = {
|
||||
command.value = it
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
@@ -186,7 +133,3 @@ private fun NewLineCharSection(checkedItem: MacroEol, onItemClick: (MacroEol) ->
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCommandValid(command: String): Boolean {
|
||||
return command.isNotBlank()
|
||||
}
|
||||
|
||||
@@ -1,265 +1,40 @@
|
||||
package no.nordicsemi.android.uart.view
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.material.you.*
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.uart.data.MacroEol
|
||||
import no.nordicsemi.android.material.you.Card
|
||||
import no.nordicsemi.android.uart.data.UARTData
|
||||
import no.nordicsemi.android.uart.data.UARTOutputRecord
|
||||
import no.nordicsemi.android.utils.EMPTY
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
internal fun UARTContentView(
|
||||
state: UARTData,
|
||||
viewState: UARTViewState,
|
||||
onEvent: (UARTViewEvent) -> Unit
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
InputSection(onEvent = onEvent)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
MacroSection(viewState, onEvent)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
OutputSection(state.displayMessages, onEvent)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Button(
|
||||
onClick = { onEvent(DisconnectEvent) }
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.disconnect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InputSection(onEvent: (UARTViewEvent) -> Unit) {
|
||||
val text = rememberSaveable { mutableStateOf(String.EMPTY) }
|
||||
val hint = stringResource(id = R.string.uart_input_hint)
|
||||
val checkedItem = rememberSaveable { mutableStateOf(MacroEol.values()[0]) }
|
||||
|
||||
val items = MacroEol.values().map {
|
||||
RadioButtonItem(it.toDisplayString(), it == checkedItem.value)
|
||||
}
|
||||
val viewEntity = RadioGroupViewEntity(items)
|
||||
|
||||
ScreenSection {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_input))
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_macro_dialog_eol),
|
||||
style = MaterialTheme.typography.labelLarge
|
||||
)
|
||||
|
||||
RadioButtonGroup(viewEntity) {
|
||||
val i = items.indexOf(it)
|
||||
checkedItem.value = MacroEol.values()[i]
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
TextField(text = text.value, hint = hint) {
|
||||
text.value = it
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Button(
|
||||
onClick = { onEvent(OnRunInput(text.value, checkedItem.value)) },
|
||||
modifier = Modifier.padding(top = 6.dp)
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.uart_send))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MacroSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
|
||||
val showAddDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val showDeleteDialog = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (showAddDialog.value) {
|
||||
UARTAddConfigurationDialog(onEvent) { showAddDialog.value = false }
|
||||
}
|
||||
|
||||
if (showDeleteDialog.value) {
|
||||
DeleteConfigurationDialog(onEvent) { showDeleteDialog.value = false }
|
||||
}
|
||||
|
||||
ScreenSection {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_macros))
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
UARTConfigurationPicker(viewState, onEvent)
|
||||
}
|
||||
|
||||
IconButton(onClick = { showAddDialog.value = true }) {
|
||||
Icon(Icons.Default.Add, stringResource(id = R.string.uart_configuration_add))
|
||||
}
|
||||
|
||||
viewState.selectedConfiguration?.let {
|
||||
|
||||
if (!viewState.isConfigurationEdited) {
|
||||
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
|
||||
Icon(
|
||||
Icons.Default.Edit,
|
||||
stringResource(id = R.string.uart_configuration_edit)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_pencil_off),
|
||||
stringResource(id = R.string.uart_configuration_edit)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
IconButton(onClick = { showDeleteDialog.value = true }) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
stringResource(id = R.string.uart_configuration_delete)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewState.selectedConfiguration?.let {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
UARTMacroView(it, viewState.isConfigurationEdited, onEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeleteConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_delete_dialog_title),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_info))
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
onDismiss()
|
||||
onEvent(OnDeleteConfiguration)
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(onClick = onDismiss) {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OutputSection(records: List<UARTOutputRecord>, onEvent: (UARTViewEvent) -> Unit) {
|
||||
ScreenSection {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||
) {
|
||||
SectionTitle(
|
||||
resId = R.drawable.ic_output,
|
||||
title = stringResource(R.string.uart_output),
|
||||
modifier = Modifier
|
||||
)
|
||||
|
||||
IconButton(onClick = { onEvent(ClearOutputItems) }) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = "Clear items.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
if (records.isEmpty()) {
|
||||
Text(text = stringResource(id = R.string.uart_output_placeholder))
|
||||
} else {
|
||||
records.forEach {
|
||||
MessageItem(record = it)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
OutputSection(state.displayMessages, onEvent)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
InputSection(onEvent = onEvent)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessageItem(record: UARTOutputRecord) {
|
||||
Column {
|
||||
Text(
|
||||
text = record.timeToString(),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.outline
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = record.text,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val datFormatter = SimpleDateFormat("dd MMMM yyyy, HH:mm:ss", Locale.ENGLISH)
|
||||
|
||||
private fun UARTOutputRecord.timeToString(): String {
|
||||
return datFormatter.format(timestamp)
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.uart.data.UARTConfiguration
|
||||
import no.nordicsemi.android.uart.data.UARTMacro
|
||||
|
||||
private val divider = 4.dp
|
||||
private val buttonSize = 80.dp
|
||||
|
||||
@Composable
|
||||
internal fun UARTMacroView(
|
||||
@@ -27,34 +27,42 @@ internal fun UARTMacroView(
|
||||
isEdited: Boolean,
|
||||
onEvent: (UARTViewEvent) -> Unit
|
||||
) {
|
||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
|
||||
Row {
|
||||
Item(configuration, isEdited, 0, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 1, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 2, onEvent)
|
||||
BoxWithConstraints {
|
||||
val buttonSize = if (maxWidth < 260.dp) {
|
||||
48.dp //Minimum touch area
|
||||
} else {
|
||||
80.dp
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
|
||||
Row {
|
||||
Item(configuration, isEdited, 3, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 4, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 5, onEvent)
|
||||
}
|
||||
Row {
|
||||
Item(configuration, isEdited, 0, buttonSize, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 1, buttonSize, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 2, buttonSize, onEvent)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
|
||||
Row {
|
||||
Item(configuration, isEdited, 3, buttonSize, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 4, buttonSize, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 5, buttonSize, onEvent)
|
||||
}
|
||||
|
||||
Row {
|
||||
Item(configuration, isEdited, 6, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 7, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 8, onEvent)
|
||||
|
||||
Row {
|
||||
Item(configuration, isEdited, 6, buttonSize, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 7, buttonSize, onEvent)
|
||||
Spacer(modifier = Modifier.size(divider))
|
||||
Item(configuration, isEdited, 8, buttonSize, onEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,14 +72,15 @@ private fun Item(
|
||||
configuration: UARTConfiguration,
|
||||
isEdited: Boolean,
|
||||
position: Int,
|
||||
buttonSize: Dp,
|
||||
onEvent: (UARTViewEvent) -> Unit
|
||||
) {
|
||||
val macro = configuration.macros.getOrNull(position)
|
||||
|
||||
if (macro == null) {
|
||||
EmptyButton(isEdited, position, onEvent)
|
||||
EmptyButton(isEdited, position, buttonSize, onEvent)
|
||||
} else {
|
||||
MacroButton(macro, position, isEdited, onEvent)
|
||||
MacroButton(macro, position, isEdited, buttonSize, onEvent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +89,7 @@ private fun MacroButton(
|
||||
macro: UARTMacro,
|
||||
position: Int,
|
||||
isEdited: Boolean,
|
||||
buttonSize: Dp,
|
||||
onEvent: (UARTViewEvent) -> Unit
|
||||
) {
|
||||
Image(
|
||||
@@ -104,6 +114,7 @@ private fun MacroButton(
|
||||
private fun EmptyButton(
|
||||
isEdited: Boolean,
|
||||
position: Int,
|
||||
buttonSize: Dp,
|
||||
onEvent: (UARTViewEvent) -> Unit
|
||||
) {
|
||||
Box(
|
||||
|
||||
@@ -8,15 +8,19 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import no.nordicsemi.android.material.you.PagerView
|
||||
import no.nordicsemi.android.material.you.PagerViewEntity
|
||||
import no.nordicsemi.android.material.you.PagerViewItem
|
||||
import no.nordicsemi.android.service.*
|
||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.uart.data.UARTData
|
||||
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
|
||||
import no.nordicsemi.android.utils.exhaustive
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||
import no.nordicsemi.ui.scanner.ui.Reason
|
||||
|
||||
@Composable
|
||||
@@ -24,29 +28,59 @@ fun UARTScreen() {
|
||||
val viewModel: UARTViewModel = hiltViewModel()
|
||||
val state = viewModel.state.collectAsState().value
|
||||
|
||||
if (state.showEditDialog) {
|
||||
UARTAddMacroDialog(state.selectedMacro) { viewModel.onEvent(it) }
|
||||
}
|
||||
|
||||
Column {
|
||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||
|
||||
LoggerIconAppBar(stringResource(id = R.string.uart_title), navigateUp) {
|
||||
viewModel.onEvent(OpenLogger)
|
||||
}
|
||||
AppBar(state = state, navigateUp = navigateUp) { viewModel.onEvent(it) }
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
when (state.uartManagerState) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.uartManagerState.result) {
|
||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
||||
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
|
||||
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
||||
is SuccessResult -> UARTContentView(state.uartManagerState.result.data, state) { viewModel.onEvent(it) }
|
||||
}
|
||||
}.exhaustive
|
||||
when (state.uartManagerState) {
|
||||
NoDeviceState -> NoDeviceView()
|
||||
is WorkingState -> when (state.uartManagerState.result) {
|
||||
is IdleResult,
|
||||
is ConnectingResult -> Scroll { DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } }
|
||||
is DisconnectedResult -> Scroll { DeviceDisconnectedView(Reason.USER, navigateUp) }
|
||||
is LinkLossResult -> Scroll { DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) }
|
||||
is MissingServiceResult -> Scroll { DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) }
|
||||
is UnknownErrorResult -> Scroll { DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) }
|
||||
is SuccessResult -> SuccessScreen(state.uartManagerState.result.data, state, viewModel)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppBar(state: UARTViewState, navigateUp: () -> Unit, onEvent: (UARTViewEvent) -> Unit) {
|
||||
val toolbarName = (state.uartManagerState as? WorkingState)?.let {
|
||||
(it.result as? SuccessResult<UARTData>)?.deviceName()
|
||||
}
|
||||
|
||||
if (toolbarName == null) {
|
||||
BackIconAppBar(stringResource(id = R.string.uart_title), navigateUp)
|
||||
} else {
|
||||
LoggerIconAppBar(toolbarName, navigateUp, { onEvent(DisconnectEvent) }) {
|
||||
onEvent(OpenLogger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SuccessScreen(data: UARTData, state: UARTViewState, viewModel: UARTViewModel) {
|
||||
val viewEntity = PagerViewEntity(
|
||||
listOf(
|
||||
PagerViewItem(stringResource(id = R.string.uart_input)) {
|
||||
UARTContentView(data) { viewModel.onEvent(it) }
|
||||
},
|
||||
PagerViewItem(stringResource(id = R.string.uart_macros)) {
|
||||
MacroSection(state) { viewModel.onEvent(it) }
|
||||
}
|
||||
)
|
||||
)
|
||||
PagerView(viewEntity)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Scroll(content: @Composable () -> Unit) {
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ internal data class UARTViewState(
|
||||
val selectedConfigurationName: String? = null,
|
||||
val isConfigurationEdited: Boolean = false,
|
||||
val configurations: List<UARTConfiguration> = emptyList(),
|
||||
val uartManagerState: HTSManagerState = NoDeviceState
|
||||
val uartManagerState: HTSManagerState = NoDeviceState,
|
||||
val isInputVisible: Boolean = true
|
||||
) {
|
||||
val showEditDialog: Boolean = editedPosition != null
|
||||
|
||||
@@ -25,6 +26,8 @@ internal data class UARTViewState(
|
||||
|
||||
internal sealed class HTSManagerState
|
||||
|
||||
internal data class WorkingState(val result: BleManagerResult<UARTData>) : HTSManagerState()
|
||||
internal data class WorkingState(
|
||||
val result: BleManagerResult<UARTData>
|
||||
) : HTSManagerState()
|
||||
|
||||
internal object NoDeviceState : HTSManagerState()
|
||||
|
||||
@@ -23,3 +23,5 @@ internal object DisconnectEvent : UARTViewEvent()
|
||||
|
||||
internal object NavigateUp : UARTViewEvent()
|
||||
internal object OpenLogger : UARTViewEvent()
|
||||
|
||||
internal object MacroInputSwitchClick : UARTViewEvent()
|
||||
|
||||
@@ -7,6 +7,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import no.nordicsemi.android.navigation.*
|
||||
import no.nordicsemi.android.service.IdleResult
|
||||
import no.nordicsemi.android.uart.data.UARTConfiguration
|
||||
import no.nordicsemi.android.uart.data.UARTMacro
|
||||
import no.nordicsemi.android.uart.data.UARTPersistentDataSource
|
||||
@@ -36,6 +37,9 @@ internal class UARTViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
repository.data.onEach {
|
||||
if (it is IdleResult) {
|
||||
return@onEach
|
||||
}
|
||||
_state.value = _state.value.copy(uartManagerState = WorkingState(it))
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
@@ -63,7 +67,7 @@ internal class UARTViewModel @Inject constructor(
|
||||
private fun handleArgs(args: DestinationResult) {
|
||||
when (args) {
|
||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
||||
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
@@ -83,9 +87,14 @@ internal class UARTViewModel @Inject constructor(
|
||||
ClearOutputItems -> repository.clearItems()
|
||||
OpenLogger -> repository.openLogger()
|
||||
is OnRunInput -> repository.sendText(event.text, event.newLineChar)
|
||||
MacroInputSwitchClick -> onMacroInputSwitch()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun onMacroInputSwitch() {
|
||||
_state.value = _state.value.copy(isInputVisible = !state.value.isInputVisible)
|
||||
}
|
||||
|
||||
private fun onEditConfiguration() {
|
||||
val isEdited = _state.value.isConfigurationEdited
|
||||
_state.value = _state.value.copy(isConfigurationEdited = !isEdited)
|
||||
|
||||
10
profile_uart/src/main/res/drawable/ic_macro.xml
Normal file
10
profile_uart/src/main/res/drawable/ic_macro.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z" />
|
||||
</vector>
|
||||
11
profile_uart/src/main/res/drawable/ic_sync_down.xml
Normal file
11
profile_uart/src/main/res/drawable/ic_sync_down.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3.8,12.18c-0.2,-0.86 -0.3,-1.76 -0.3,-2.68c0,-2.84 0.99,-5.45 2.63,-7.5L7.2,3.07C5.82,4.85 5,7.08 5,9.5c0,0.88 0.11,1.74 0.32,2.56l1.62,-1.62L8,11.5L4.5,15L1,11.5l1.06,-1.06L3.8,12.18zM13.85,11.62l-2.68,-5.37c-0.37,-0.74 -1.27,-1.04 -2.01,-0.67C8.41,5.96 8.11,6.86 8.48,7.6l4.81,9.6L10.05,18c-0.33,0.09 -0.59,0.33 -0.7,0.66L9,19.78l6.19,2.25c0.5,0.17 1.28,0.02 1.75,-0.22l5.51,-2.75c0.89,-0.45 1.32,-1.48 1,-2.42l-1.43,-4.27c-0.27,-0.82 -1.04,-1.37 -1.9,-1.37h-4.56c-0.31,0 -0.62,0.07 -0.89,0.21L13.85,11.62" />
|
||||
</vector>
|
||||
15
profile_uart/src/main/res/drawable/ic_sync_down_off.xml
Normal file
15
profile_uart/src/main/res/drawable/ic_sync_down_off.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3.8,12.18c-0.2,-0.86 -0.3,-1.76 -0.3,-2.68c0,-2.84 0.99,-5.45 2.63,-7.5L7.2,3.07C5.82,4.85 5,7.08 5,9.5c0,0.88 0.11,1.74 0.32,2.56l1.62,-1.62L8,11.5L4.5,15L1,11.5l1.06,-1.06L3.8,12.18zM13.85,11.62l-2.68,-5.37c-0.37,-0.74 -1.27,-1.04 -2.01,-0.67C8.41,5.96 8.11,6.86 8.48,7.6l4.81,9.6L10.05,18c-0.33,0.09 -0.59,0.33 -0.7,0.66L9,19.78l6.19,2.25c0.5,0.17 1.28,0.02 1.75,-0.22l5.51,-2.75c0.89,-0.45 1.32,-1.48 1,-2.42l-1.43,-4.27c-0.27,-0.82 -1.04,-1.37 -1.9,-1.37h-4.56c-0.31,0 -0.62,0.07 -0.89,0.21L13.85,11.62" />
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
|
||||
</vector>
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="uart_title">UART</string>
|
||||
|
||||
<string name="uart_no_macros_info">Please define a macro to send command to the device.</string>
|
||||
|
||||
<string name="uart_configuration_add">Add selected configuration.</string>
|
||||
<string name="uart_configuration_add">Add</string>
|
||||
<string name="uart_configuration_delete">Delete selected configuration.</string>
|
||||
<string name="uart_configuration_edit">Edit selected configuration.</string>
|
||||
|
||||
@@ -52,4 +52,13 @@
|
||||
<string name="uart_delete_dialog_info">Are you sure that you want to delete this configuration? Your data will be irretrievably lost.</string>
|
||||
<string name="uart_delete_dialog_confirm">Confirm</string>
|
||||
<string name="uart_delete_dialog_cancel">Cancel</string>
|
||||
|
||||
<string name="uart_input_macro">Click to switch between text input and macro input.</string>
|
||||
<string name="uart_clear_items">Clear items.</string>
|
||||
<string name="uart_scroll_down">Click to constantly scroll view to the latest available log.</string>
|
||||
<string name="uart_input_log">--> %s</string>
|
||||
<string name="uart_output_log" tools:ignore="TypographyDashes"><-- %s</string>
|
||||
|
||||
<string name="uart_settings">Settings</string>
|
||||
<string name="uart_settings_button">Go to settings screen.</string>
|
||||
</resources>
|
||||
|
||||
@@ -9,8 +9,8 @@ dependencyResolutionManagement {
|
||||
|
||||
versionCatalogs {
|
||||
libs {
|
||||
library('nordic-ble-common', 'no.nordicsemi.android:ble-common:2.4.0')
|
||||
library('nordic-ble-ktx', 'no.nordicsemi.android:ble-ktx:2.4.0')
|
||||
library('nordic-ble-common', 'no.nordicsemi.android:ble-common:2.4.1')
|
||||
library('nordic-ble-ktx', 'no.nordicsemi.android:ble-ktx:2.4.1')
|
||||
library('nordic-scanner', 'no.nordicsemi.android.support.v18:scanner:1.6.0')
|
||||
|
||||
library('nordic-log', 'no.nordicsemi.android:log:2.3.0')
|
||||
@@ -21,7 +21,7 @@ dependencyResolutionManagement {
|
||||
library('nordic-theme', 'no.nordicsemi.android.common', 'theme').versionRef('commonlibraries')
|
||||
|
||||
library('localbroadcastmanager', 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0')
|
||||
library('material', 'com.google.android.material:material:1.6.0-alpha02')
|
||||
library('material', 'com.google.android.material:material:1.6.0-rc01')
|
||||
|
||||
version('lifecycle', '2.4.1')
|
||||
library('lifecycle-activity', 'androidx.lifecycle', 'lifecycle-runtime-ktx').versionRef('lifecycle')
|
||||
@@ -37,9 +37,9 @@ dependencyResolutionManagement {
|
||||
library('datastore-protobuf', 'com.google.protobuf:protobuf-javalite:3.18.0')
|
||||
bundle('datastore', ['datastore-core', 'datastore-prefs', 'datastore-protobuf'])
|
||||
|
||||
version('compose', '1.1.0')
|
||||
version('compose', '1.2.0-alpha07')
|
||||
library('compose-ui', 'androidx.compose.ui', 'ui').versionRef('compose')
|
||||
library('compose-material', 'androidx.compose.material3:material3:1.0.0-alpha05')
|
||||
library('compose-material', 'androidx.compose.material3:material3:1.0.0-alpha09')
|
||||
library('compose-tooling-preview', 'androidx.compose.ui', 'ui-tooling-preview').versionRef('compose')
|
||||
library('compose-navigation', 'androidx.navigation:navigation-compose:2.4.1')
|
||||
bundle('compose', ['compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])
|
||||
|
||||
Reference in New Issue
Block a user