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:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
|
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||||
android:theme="@style/AppTheme.SplashScreen">
|
android:theme="@style/AppTheme.SplashScreen">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package no.nordicsemi.android.nrftoolbox
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
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.material.you.NordicTheme
|
||||||
import no.nordicsemi.android.navigation.NavigationView
|
import no.nordicsemi.android.navigation.NavigationView
|
||||||
import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals
|
import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals
|
||||||
import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel
|
|
||||||
import no.nordicsemi.ui.scanner.ScannerDestinations
|
import no.nordicsemi.ui.scanner.ScannerDestinations
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package no.nordicsemi.android.service
|
package no.nordicsemi.android.service
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
|
||||||
sealed class BleManagerResult <T> {
|
sealed class BleManagerResult <T> {
|
||||||
|
|
||||||
fun isRunning(): Boolean {
|
fun isRunning(): Boolean {
|
||||||
@@ -15,8 +18,13 @@ sealed class BleManagerResult <T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IdleResult<T> : BleManagerResult<T>()
|
||||||
class ConnectingResult<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 LinkLossResult<T>(val data: T) : BleManagerResult<T>()
|
||||||
class DisconnectedResult<T> : BleManagerResult<T>()
|
class DisconnectedResult<T> : BleManagerResult<T>()
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
|||||||
|
|
||||||
override fun onDeviceReady(device: BluetoothDevice) {
|
override fun onDeviceReady(device: BluetoothDevice) {
|
||||||
Log.d(TAG, "onDeviceReady()")
|
Log.d(TAG, "onDeviceReady()")
|
||||||
_status.value = SuccessResult(lastValue!!)
|
_status.value = SuccessResult(device, lastValue!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDeviceDisconnecting(device: BluetoothDevice) {
|
override fun onDeviceDisconnecting(device: BluetoothDevice) {
|
||||||
@@ -53,8 +53,8 @@ class ConnectionObserverAdapter<T> : ConnectionObserver {
|
|||||||
|
|
||||||
fun setValue(value: T) {
|
fun setValue(value: T) {
|
||||||
lastValue = value
|
lastValue = value
|
||||||
if (_status.value.isRunning()) {
|
(_status.value as? SuccessResult)?.let {
|
||||||
_status.value = SuccessResult(value)
|
_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.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.*
|
||||||
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.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.res.colorResource
|
import androidx.compose.ui.res.colorResource
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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
|
@Composable
|
||||||
fun BackIconAppBar(text: String, onClick: () -> Unit) {
|
fun BackIconAppBar(text: String, onClick: () -> Unit) {
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
@@ -71,25 +98,16 @@ fun BackIconAppBar(text: String, onClick: () -> Unit) {
|
|||||||
IconButton(onClick = { onClick() }) {
|
IconButton(onClick = { onClick() }) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.ArrowBack,
|
Icons.Default.ArrowBack,
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
contentDescription = stringResource(id = R.string.back_screen),
|
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
|
@Composable
|
||||||
fun LoggerIconAppBar(text: String, onClick: () -> Unit, onLoggerClick: () -> Unit) {
|
fun LoggerIconAppBar(text: String, onClick: () -> Unit, onDisconnectClick: () -> Unit, onLoggerClick: () -> Unit) {
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
title = { Text(text) },
|
title = { Text(text) },
|
||||||
colors = TopAppBarDefaults.smallTopAppBarColors(
|
colors = TopAppBarDefaults.smallTopAppBarColors(
|
||||||
@@ -103,15 +121,25 @@ fun LoggerIconAppBar(text: String, onClick: () -> Unit, onLoggerClick: () -> Uni
|
|||||||
IconButton(onClick = { onClick() }) {
|
IconButton(onClick = { onClick() }) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.ArrowBack,
|
Icons.Default.ArrowBack,
|
||||||
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
contentDescription = stringResource(id = R.string.back_screen),
|
contentDescription = stringResource(id = R.string.back_screen),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onDisconnectClick() },
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(stringResource(id = R.string.disconnect))
|
||||||
|
}
|
||||||
IconButton(onClick = { onLoggerClick() }) {
|
IconButton(onClick = { onLoggerClick() }) {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(id = R.drawable.ic_logger),
|
painterResource(id = R.drawable.ic_logger),
|
||||||
contentDescription = stringResource(id = R.string.back_screen),
|
contentDescription = stringResource(id = R.string.open_logger),
|
||||||
tint = MaterialTheme.colorScheme.onPrimary,
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
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.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
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
|
import no.nordicsemi.android.theme.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StringListDialog(config: StringListDialogConfig) {
|
fun StringListDialog(config: StringListDialogConfig) {
|
||||||
Dialog(onDismissRequest = { config.onResult(FlowCanceled) }) {
|
AlertDialog(
|
||||||
StringListView(config)
|
onDismissRequest = { config.onResult(FlowCanceled) },
|
||||||
}
|
title = { Text(text = config.title ?: stringResource(id = R.string.dialog).toAnnotatedString()) },
|
||||||
}
|
text = {
|
||||||
|
|
||||||
@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))
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight(0.8f)
|
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -81,17 +55,13 @@ fun StringListView(config: StringListDialogConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
Column(
|
confirmButton = {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
TextButton(onClick = { config.onResult(FlowCanceled) }) {
|
||||||
horizontalAlignment = Alignment.End
|
Text(
|
||||||
) {
|
text = stringResource(id = R.string.cancel),
|
||||||
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="app_name">nRF Toolbox</string>
|
||||||
|
|
||||||
<string name="dialog">Dialog</string>
|
<string name="dialog">Dialog</string>
|
||||||
<string name="cancel">CANCEL</string>
|
<string name="cancel">Cancel</string>
|
||||||
|
|
||||||
<string name="go_up">Back</string>
|
<string name="go_up">Back</string>
|
||||||
|
|
||||||
<string name="close_app">Close the application.</string>
|
<string name="close_app">Close the application.</string>
|
||||||
<string name="back_screen">Close the current screen.</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="disconnect">Disconnect</string>
|
||||||
<string name="field_battery">Battery</string>
|
<string name="field_battery">Battery</string>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ internal class BPSManager(
|
|||||||
val dataHolder = ConnectionObserverAdapter<BPSData>()
|
val dataHolder = ConnectionObserverAdapter<BPSData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
dataHolder.setValue(it)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package no.nordicsemi.android.bps.repository
|
package no.nordicsemi.android.bps.repository
|
||||||
|
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.android.scopes.ViewModelScoped
|
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.ToolboxLogger
|
||||||
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ViewModelScoped
|
@ViewModelScoped
|
||||||
@@ -25,9 +25,9 @@ internal class BPSRepository @Inject constructor(
|
|||||||
|
|
||||||
private var logger: ToolboxLogger? = null
|
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 scope = this
|
||||||
val createdLogger = toolboxLoggerFactory.create("BPS", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("BPS", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = BPSManager(context, scope, createdLogger)
|
val manager = BPSManager(context, scope, createdLogger)
|
||||||
@@ -36,7 +36,7 @@ internal class BPSRepository @Inject constructor(
|
|||||||
trySend(it)
|
trySend(it)
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
manager.connect(device)
|
manager.connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import no.nordicsemi.android.bps.R
|
|||||||
import no.nordicsemi.android.bps.data.BPSData
|
import no.nordicsemi.android.bps.data.BPSData
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun BPSContentView(state: BPSData, onEvent: (BPSScreenViewEvent) -> Unit) {
|
internal fun BPSContentView(state: BPSData, onEvent: (BPSViewEvent) -> Unit) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package no.nordicsemi.android.bps.view
|
package no.nordicsemi.android.bps.view
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -9,14 +10,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.bps.R
|
import no.nordicsemi.android.bps.R
|
||||||
|
import no.nordicsemi.android.bps.data.BPSData
|
||||||
import no.nordicsemi.android.bps.viewmodel.BPSViewModel
|
import no.nordicsemi.android.bps.viewmodel.BPSViewModel
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -27,16 +29,13 @@ fun BPSScreen() {
|
|||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(DisconnectEvent) }
|
val navigateUp = { viewModel.onEvent(DisconnectEvent) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.bps_title), {
|
AppBar(state = state, navigateUp = navigateUp, viewModel = viewModel)
|
||||||
viewModel.onEvent(DisconnectEvent)
|
|
||||||
}) {
|
|
||||||
viewModel.onEvent(OpenLoggerEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state) {
|
when (state) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.result) {
|
is WorkingState -> when (state.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, 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.bps.data.BPSData
|
||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
|
|
||||||
internal sealed class BPSViewState
|
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()
|
internal object NoDeviceState : BPSViewState()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ internal class BPSViewModel @Inject constructor(
|
|||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onEvent(event: BPSScreenViewEvent) {
|
fun onEvent(event: BPSViewEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
DisconnectEvent -> navigationManager.navigateUp()
|
DisconnectEvent -> navigationManager.navigateUp()
|
||||||
OpenLoggerEvent -> repository.openLogger()
|
OpenLoggerEvent -> repository.openLogger()
|
||||||
@@ -51,7 +51,7 @@ internal class BPSViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun connectDevice(device: DiscoveredBluetoothDevice) {
|
private fun connectDevice(device: DiscoveredBluetoothDevice) {
|
||||||
repository.downloadData(device.device).onEach {
|
repository.downloadData(device).onEach {
|
||||||
_state.value = WorkingState(it)
|
_state.value = WorkingState(it)
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ internal class CGMManager(
|
|||||||
val dataHolder = ConnectionObserverAdapter<CGMData>()
|
val dataHolder = ConnectionObserverAdapter<CGMData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
dataHolder.setValue(it)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
|||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
import no.nordicsemi.android.service.ConnectingResult
|
import no.nordicsemi.android.service.ConnectingResult
|
||||||
import no.nordicsemi.android.service.ServiceManager
|
import no.nordicsemi.android.service.ServiceManager
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -33,12 +34,12 @@ class CGMRepository @Inject constructor(
|
|||||||
val isRunning = data.map { it.isRunning() }
|
val isRunning = data.map { it.isRunning() }
|
||||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||||
|
|
||||||
fun launch(device: BluetoothDevice) {
|
fun launch(device: DiscoveredBluetoothDevice) {
|
||||||
serviceManager.startService(CGMService::class.java, device)
|
serviceManager.startService(CGMService::class.java, device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||||
val createdLogger = toolboxLoggerFactory.create("CGMS", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("CGMS", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = CGMManager(context, scope, createdLogger)
|
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 {
|
try {
|
||||||
connect(device)
|
connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.suspend()
|
.suspend()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.DEVICE_DATA
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
import no.nordicsemi.android.service.NotificationService
|
import no.nordicsemi.android.service.NotificationService
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -19,7 +20,7 @@ internal class CGMService : NotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||||
|
|
||||||
repository.start(device, lifecycleScope)
|
repository.start(device, lifecycleScope)
|
||||||
|
|
||||||
|
|||||||
@@ -9,32 +9,32 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.cgms.R
|
import no.nordicsemi.android.cgms.R
|
||||||
import no.nordicsemi.android.cgms.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.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CGMScreen() {
|
fun CGMScreen() {
|
||||||
val viewModel: CGMScreenViewModel = hiltViewModel()
|
val viewModel: CGMViewModel = hiltViewModel()
|
||||||
val state = viewModel.state.collectAsState().value
|
val state = viewModel.state.collectAsState().value
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.cgms_title), navigateUp) {
|
AppBar(state, navigateUp, viewModel)
|
||||||
viewModel.onEvent(OpenLoggerEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state) {
|
when (state) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.result) {
|
is WorkingState -> when (state.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, 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.cgms.data.CGMData
|
||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
|
|
||||||
internal sealed class BPSViewState
|
internal sealed class CGMViewState
|
||||||
|
|
||||||
internal data class WorkingState(val result: BleManagerResult<CGMData>) : BPSViewState()
|
internal data class WorkingState(val result: BleManagerResult<CGMData>) : CGMViewState()
|
||||||
internal object NoDeviceState : BPSViewState()
|
internal object NoDeviceState : CGMViewState()
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import no.nordicsemi.ui.scanner.ScannerDestinationId
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
internal class CGMScreenViewModel @Inject constructor(
|
internal class CGMViewModel @Inject constructor(
|
||||||
private val repository: CGMRepository,
|
private val repository: CGMRepository,
|
||||||
private val navigationManager: NavigationManager
|
private val navigationManager: NavigationManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _state = MutableStateFlow<BPSViewState>(NoDeviceState)
|
private val _state = MutableStateFlow<CGMViewState>(NoDeviceState)
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -58,7 +58,7 @@ internal class CGMScreenViewModel @Inject constructor(
|
|||||||
private fun handleArgs(args: DestinationResult) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ internal class CSCManager(
|
|||||||
val dataHolder = ConnectionObserverAdapter<CSCData>()
|
val dataHolder = ConnectionObserverAdapter<CSCData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
dataHolder.setValue(it)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
|||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
import no.nordicsemi.android.service.ConnectingResult
|
import no.nordicsemi.android.service.ConnectingResult
|
||||||
import no.nordicsemi.android.service.ServiceManager
|
import no.nordicsemi.android.service.ServiceManager
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -34,12 +35,12 @@ class CSCRepository @Inject constructor(
|
|||||||
val isRunning = data.map { it.isRunning() }
|
val isRunning = data.map { it.isRunning() }
|
||||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||||
|
|
||||||
fun launch(device: BluetoothDevice) {
|
fun launch(device: DiscoveredBluetoothDevice) {
|
||||||
serviceManager.startService(CSCService::class.java, device)
|
serviceManager.startService(CSCService::class.java, device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||||
val createdLogger = toolboxLoggerFactory.create("CSC", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("CSC", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = CSCManager(context, scope, createdLogger)
|
val manager = CSCManager(context, scope, createdLogger)
|
||||||
@@ -58,9 +59,9 @@ class CSCRepository @Inject constructor(
|
|||||||
manager?.setWheelSize(wheelSize)
|
manager?.setWheelSize(wheelSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun CSCManager.start(device: BluetoothDevice) {
|
private suspend fun CSCManager.start(device: DiscoveredBluetoothDevice) {
|
||||||
try {
|
try {
|
||||||
connect(device)
|
connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.suspend()
|
.suspend()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.DEVICE_DATA
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
import no.nordicsemi.android.service.NotificationService
|
import no.nordicsemi.android.service.NotificationService
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -19,7 +20,7 @@ internal class CSCService : NotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||||
|
|
||||||
repository.start(device, lifecycleScope)
|
repository.start(device, lifecycleScope)
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.csc.R
|
import no.nordicsemi.android.csc.R
|
||||||
|
import no.nordicsemi.android.csc.data.CSCData
|
||||||
import no.nordicsemi.android.csc.viewmodel.CSCViewModel
|
import no.nordicsemi.android.csc.viewmodel.CSCViewModel
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -27,14 +28,13 @@ fun CSCScreen() {
|
|||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.csc_title), navigateUp) {
|
AppBar(state, navigateUp, viewModel)
|
||||||
viewModel.onEvent(OpenLogger)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state.cscManagerState) {
|
when (state.cscManagerState) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.cscManagerState.result) {
|
is WorkingState -> when (state.cscManagerState.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, 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) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ internal class GLSManager(
|
|||||||
val dataHolder = ConnectionObserverAdapter<GLSData>()
|
val dataHolder = ConnectionObserverAdapter<GLSData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
dataHolder.setValue(it)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.gls.R
|
import no.nordicsemi.android.gls.R
|
||||||
import no.nordicsemi.android.gls.details.viewmodel.GLSDetailsViewModel
|
import no.nordicsemi.android.gls.details.viewmodel.GLSDetailsViewModel
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.LoggerBackIconAppBar
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun GLSDetailsScreen() {
|
internal fun GLSDetailsScreen() {
|
||||||
@@ -14,7 +14,7 @@ internal fun GLSDetailsScreen() {
|
|||||||
val record = viewModel.record
|
val record = viewModel.record
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
BackIconAppBar(stringResource(id = R.string.gls_title)) {
|
LoggerBackIconAppBar(stringResource(id = R.string.gls_title)) {
|
||||||
viewModel.navigateBack()
|
viewModel.navigateBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.gls.R
|
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.gls.main.viewmodel.GLSViewModel
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -27,16 +28,13 @@ fun GLSScreen() {
|
|||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(DisconnectEvent) }
|
val navigateUp = { viewModel.onEvent(DisconnectEvent) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.gls_title), {
|
AppBar(state, navigateUp, viewModel)
|
||||||
viewModel.onEvent(DisconnectEvent)
|
|
||||||
}) {
|
|
||||||
viewModel.onEvent(OpenLoggerEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state) {
|
when (state) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.result) {
|
is WorkingState -> when (state.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, 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.gls.data.GLSData
|
||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
|
|
||||||
internal sealed class BPSViewState
|
internal sealed class GLSViewState
|
||||||
|
|
||||||
internal data class WorkingState(val result: BleManagerResult<GLSData>) : BPSViewState()
|
internal data class WorkingState(val result: BleManagerResult<GLSData>) : GLSViewState()
|
||||||
internal object NoDeviceState : BPSViewState()
|
internal object NoDeviceState : GLSViewState()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ internal class GLSViewModel @Inject constructor(
|
|||||||
private val navigationManager: NavigationManager
|
private val navigationManager: NavigationManager
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
private val _state = MutableStateFlow<BPSViewState>(NoDeviceState)
|
private val _state = MutableStateFlow<GLSViewState>(NoDeviceState)
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -52,7 +52,7 @@ internal class GLSViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun connectDevice(device: DiscoveredBluetoothDevice) {
|
private fun connectDevice(device: DiscoveredBluetoothDevice) {
|
||||||
repository.downloadData(device.device).onEach {
|
repository.downloadData(device).onEach {
|
||||||
_state.value = WorkingState(it)
|
_state.value = WorkingState(it)
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import no.nordicsemi.android.logger.ToolboxLogger
|
|||||||
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ViewModelScoped
|
@ViewModelScoped
|
||||||
@@ -30,14 +31,14 @@ internal class GLSRepository @Inject constructor(
|
|||||||
private var manager: GLSManager? = null
|
private var manager: GLSManager? = null
|
||||||
private var logger: ToolboxLogger? = 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 scope = this
|
||||||
val createdLogger = toolboxLoggerFactory.create("GLS", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("GLS", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val managerInstance = manager ?: GLSManager(context, scope, createdLogger).apply {
|
val managerInstance = manager ?: GLSManager(context, scope, createdLogger).apply {
|
||||||
try {
|
try {
|
||||||
connect(device)
|
connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.suspend()
|
.suspend()
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ internal class HRSManager(
|
|||||||
val dataHolder = ConnectionObserverAdapter<HRSData>()
|
val dataHolder = ConnectionObserverAdapter<HRSData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
dataHolder.setValue(it)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
|||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
import no.nordicsemi.android.service.ConnectingResult
|
import no.nordicsemi.android.service.ConnectingResult
|
||||||
import no.nordicsemi.android.service.ServiceManager
|
import no.nordicsemi.android.service.ServiceManager
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -33,12 +34,12 @@ class HRSRepository @Inject constructor(
|
|||||||
val isRunning = data.map { it.isRunning() }
|
val isRunning = data.map { it.isRunning() }
|
||||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||||
|
|
||||||
fun launch(device: BluetoothDevice) {
|
fun launch(device: DiscoveredBluetoothDevice) {
|
||||||
serviceManager.startService(HRSService::class.java, device)
|
serviceManager.startService(HRSService::class.java, device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||||
val createdLogger = toolboxLoggerFactory.create("HRS", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("HRS", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = HRSManager(context, scope, createdLogger)
|
val manager = HRSManager(context, scope, createdLogger)
|
||||||
@@ -57,9 +58,9 @@ class HRSRepository @Inject constructor(
|
|||||||
logger?.openLogger()
|
logger?.openLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun HRSManager.start(device: BluetoothDevice) {
|
private suspend fun HRSManager.start(device: DiscoveredBluetoothDevice) {
|
||||||
try {
|
try {
|
||||||
connect(device)
|
connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.suspend()
|
.suspend()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.DEVICE_DATA
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
import no.nordicsemi.android.service.NotificationService
|
import no.nordicsemi.android.service.NotificationService
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -19,7 +20,7 @@ internal class HRSService : NotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||||
|
|
||||||
repository.start(device, lifecycleScope)
|
repository.start(device, lifecycleScope)
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.hrs.R
|
import no.nordicsemi.android.hrs.R
|
||||||
|
import no.nordicsemi.android.hrs.data.HRSData
|
||||||
import no.nordicsemi.android.hrs.viewmodel.HRSViewModel
|
import no.nordicsemi.android.hrs.viewmodel.HRSViewModel
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
@@ -26,14 +28,13 @@ fun HRSScreen() {
|
|||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.hrs_title), navigateUp) {
|
AppBar(state, navigateUp, viewModel)
|
||||||
viewModel.onEvent(OpenLoggerEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state) {
|
when (state) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.result) {
|
is WorkingState -> when (state.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, 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) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ internal class HTSManager internal constructor(
|
|||||||
val dataHolder = ConnectionObserverAdapter<HTSData>()
|
val dataHolder = ConnectionObserverAdapter<HTSData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
dataHolder.setValue(it)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
|||||||
import no.nordicsemi.android.service.BleManagerResult
|
import no.nordicsemi.android.service.BleManagerResult
|
||||||
import no.nordicsemi.android.service.ConnectingResult
|
import no.nordicsemi.android.service.ConnectingResult
|
||||||
import no.nordicsemi.android.service.ServiceManager
|
import no.nordicsemi.android.service.ServiceManager
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -33,12 +34,12 @@ class HTSRepository @Inject constructor(
|
|||||||
val isRunning = data.map { it.isRunning() }
|
val isRunning = data.map { it.isRunning() }
|
||||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||||
|
|
||||||
fun launch(device: BluetoothDevice) {
|
fun launch(device: DiscoveredBluetoothDevice) {
|
||||||
serviceManager.startService(HTSService::class.java, device)
|
serviceManager.startService(HTSService::class.java, device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||||
val createdLogger = toolboxLoggerFactory.create("HTS", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("HTS", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = HTSManager(context, scope, createdLogger)
|
val manager = HTSManager(context, scope, createdLogger)
|
||||||
@@ -57,9 +58,9 @@ class HTSRepository @Inject constructor(
|
|||||||
logger?.openLogger()
|
logger?.openLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun HTSManager.start(device: BluetoothDevice) {
|
private suspend fun HTSManager.start(device: DiscoveredBluetoothDevice) {
|
||||||
try {
|
try {
|
||||||
connect(device)
|
connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.suspend()
|
.suspend()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.DEVICE_DATA
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
import no.nordicsemi.android.service.NotificationService
|
import no.nordicsemi.android.service.NotificationService
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -19,7 +20,7 @@ internal class HTSService : NotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||||
|
|
||||||
repository.start(device, lifecycleScope)
|
repository.start(device, lifecycleScope)
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.hts.R
|
import no.nordicsemi.android.hts.R
|
||||||
|
import no.nordicsemi.android.hts.data.HTSData
|
||||||
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
|
import no.nordicsemi.android.hts.viewmodel.HTSViewModel
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -27,14 +28,13 @@ fun HTSScreen() {
|
|||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.hts_title), navigateUp) {
|
AppBar(state, navigateUp, viewModel)
|
||||||
viewModel.onEvent(OpenLoggerEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state.htsManagerState) {
|
when (state.htsManagerState) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.htsManagerState.result) {
|
is WorkingState -> when (state.htsManagerState.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, 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) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ internal class PRXManager(
|
|||||||
val dataHolder = ConnectionObserverAdapter<PRXData>()
|
val dataHolder = ConnectionObserverAdapter<PRXData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
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.PRXManager
|
||||||
import no.nordicsemi.android.prx.data.ProximityServerManager
|
import no.nordicsemi.android.prx.data.ProximityServerManager
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -34,13 +35,13 @@ class PRXRepository @Inject internal constructor(
|
|||||||
val isRunning = data.map { it.isRunning() }
|
val isRunning = data.map { it.isRunning() }
|
||||||
val hasBeenDisconnectedWithoutLinkLoss = data.map { it.hasBeenDisconnectedWithoutLinkLoss() }
|
val hasBeenDisconnectedWithoutLinkLoss = data.map { it.hasBeenDisconnectedWithoutLinkLoss() }
|
||||||
|
|
||||||
fun launch(device: BluetoothDevice) {
|
fun launch(device: DiscoveredBluetoothDevice) {
|
||||||
serviceManager.startService(PRXService::class.java, device)
|
serviceManager.startService(PRXService::class.java, device)
|
||||||
proximityServerManager.open()
|
proximityServerManager.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||||
val createdLogger = toolboxLoggerFactory.create("PRX", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("PRX", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = PRXManager(context, scope, createdLogger)
|
val manager = PRXManager(context, scope, createdLogger)
|
||||||
@@ -52,7 +53,7 @@ class PRXRepository @Inject internal constructor(
|
|||||||
handleLocalAlarm(it)
|
handleLocalAlarm(it)
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
|
|
||||||
manager.connect(device)
|
manager.connect(device.device)
|
||||||
.useAutoConnect(true)
|
.useAutoConnect(true)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.DEVICE_DATA
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
import no.nordicsemi.android.service.NotificationService
|
import no.nordicsemi.android.service.NotificationService
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -19,7 +20,7 @@ internal class PRXService : NotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||||
|
|
||||||
repository.start(device, lifecycleScope)
|
repository.start(device, lifecycleScope)
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.prx.R
|
import no.nordicsemi.android.prx.R
|
||||||
|
import no.nordicsemi.android.prx.data.PRXData
|
||||||
import no.nordicsemi.android.prx.viewmodel.PRXViewModel
|
import no.nordicsemi.android.prx.viewmodel.PRXViewModel
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -28,14 +29,13 @@ fun PRXScreen() {
|
|||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.prx_title), navigateUp) {
|
AppBar(state, navigateUp, viewModel)
|
||||||
viewModel.onEvent(OpenLoggerEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state) {
|
when (state) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.result) {
|
is WorkingState -> when (state.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) }
|
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) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ internal class RSCSManager internal constructor(
|
|||||||
val dataHolder = ConnectionObserverAdapter<RSCSData>()
|
val dataHolder = ConnectionObserverAdapter<RSCSData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
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.BleManagerResult
|
||||||
import no.nordicsemi.android.service.ConnectingResult
|
import no.nordicsemi.android.service.ConnectingResult
|
||||||
import no.nordicsemi.android.service.ServiceManager
|
import no.nordicsemi.android.service.ServiceManager
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -33,12 +34,12 @@ class RSCSRepository @Inject constructor(
|
|||||||
val isRunning = data.map { it.isRunning() }
|
val isRunning = data.map { it.isRunning() }
|
||||||
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
val hasBeenDisconnected = data.map { it.hasBeenDisconnected() }
|
||||||
|
|
||||||
fun launch(device: BluetoothDevice) {
|
fun launch(device: DiscoveredBluetoothDevice) {
|
||||||
serviceManager.startService(RSCSService::class.java, device)
|
serviceManager.startService(RSCSService::class.java, device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||||
val createdLogger = toolboxLoggerFactory.create("RSCS", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("RSCS", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = RSCSManager(context, scope, createdLogger)
|
val manager = RSCSManager(context, scope, createdLogger)
|
||||||
@@ -57,9 +58,9 @@ class RSCSRepository @Inject constructor(
|
|||||||
logger?.openLogger()
|
logger?.openLogger()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun RSCSManager.start(device: BluetoothDevice) {
|
private suspend fun RSCSManager.start(device: DiscoveredBluetoothDevice) {
|
||||||
try {
|
try {
|
||||||
connect(device)
|
connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.suspend()
|
.suspend()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.DEVICE_DATA
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
import no.nordicsemi.android.service.NotificationService
|
import no.nordicsemi.android.service.NotificationService
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -19,7 +20,7 @@ internal class RSCSService : NotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||||
|
|
||||||
repository.start(device, lifecycleScope)
|
repository.start(device, lifecycleScope)
|
||||||
|
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.rscs.R
|
import no.nordicsemi.android.rscs.R
|
||||||
|
import no.nordicsemi.android.rscs.data.RSCSData
|
||||||
import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel
|
import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel
|
||||||
import no.nordicsemi.android.service.*
|
import no.nordicsemi.android.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.android.utils.exhaustive
|
||||||
|
import no.nordicsemi.ui.scanner.ui.DeviceConnectingView
|
||||||
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -27,14 +28,13 @@ fun RSCSScreen() {
|
|||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
val navigateUp = { viewModel.onEvent(NavigateUpEvent) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.rscs_title), navigateUp) {
|
AppBar(state, navigateUp, viewModel)
|
||||||
viewModel.onEvent(OpenLoggerEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
when (state) {
|
when (state) {
|
||||||
NoDeviceState -> NoDeviceView()
|
NoDeviceState -> NoDeviceView()
|
||||||
is WorkingState -> when (state.result) {
|
is WorkingState -> when (state.result) {
|
||||||
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, 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) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
package no.nordicsemi.android.uart.data
|
package no.nordicsemi.android.uart.data
|
||||||
|
|
||||||
internal data class UARTData(
|
internal data class UARTData(
|
||||||
val messages: List<UARTOutputRecord> = emptyList(),
|
val messages: List<UARTRecord> = emptyList(),
|
||||||
val batteryLevel: Int? = null,
|
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 text: String,
|
||||||
|
val type: UARTRecordType,
|
||||||
val timestamp: Long = System.currentTimeMillis()
|
val timestamp: Long = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
enum class UARTRecordType {
|
||||||
|
INPUT, OUTPUT
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ import android.content.Context
|
|||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.withContext
|
||||||
import no.nordicsemi.android.ble.BleManager
|
import no.nordicsemi.android.ble.BleManager
|
||||||
import no.nordicsemi.android.ble.WriteRequest
|
import no.nordicsemi.android.ble.WriteRequest
|
||||||
import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
|
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 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_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(
|
internal class UARTManager(
|
||||||
context: Context,
|
context: Context,
|
||||||
@@ -68,7 +69,7 @@ internal class UARTManager(
|
|||||||
val dataHolder = ConnectionObserverAdapter<UARTData>()
|
val dataHolder = ConnectionObserverAdapter<UARTData>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setConnectionObserver(dataHolder)
|
connectionObserver = dataHolder
|
||||||
|
|
||||||
data.onEach {
|
data.onEach {
|
||||||
dataHolder.setValue(it)
|
dataHolder.setValue(it)
|
||||||
@@ -87,18 +88,25 @@ internal class UARTManager(
|
|||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
setNotificationCallback(txCharacteristic).asFlow().onEach {
|
setNotificationCallback(txCharacteristic).asFlow()
|
||||||
val text: String = it.getStringValue(0) ?: String.EMPTY
|
.flowOn(Dispatchers.IO)
|
||||||
log(10, "\"$text\" received")
|
.map {
|
||||||
data.value = data.value.copy(messages = data.value.messages + UARTOutputRecord(text))
|
val text: String = it.getStringValue(0) ?: String.EMPTY
|
||||||
}.launchIn(scope)
|
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()
|
requestMtu(517).enqueue()
|
||||||
enableNotifications(txCharacteristic).enqueue()
|
enableNotifications(txCharacteristic).enqueue()
|
||||||
|
|
||||||
setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow<BatteryLevelResponse>().onEach {
|
setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow<BatteryLevelResponse>()
|
||||||
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
.onEach {
|
||||||
}.launchIn(scope)
|
data.value = data.value.copy(batteryLevel = it.batteryLevel)
|
||||||
|
}.launchIn(scope)
|
||||||
enableNotifications(batteryLevelCharacteristic).enqueue()
|
enableNotifications(batteryLevelCharacteristic).enqueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +122,8 @@ internal class UARTManager(
|
|||||||
rxCharacteristic?.let {
|
rxCharacteristic?.let {
|
||||||
val rxProperties: Int = it.properties
|
val rxProperties: Int = it.properties
|
||||||
writeRequest = rxProperties and BluetoothGattCharacteristic.PROPERTY_WRITE > 0
|
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.
|
// Set the WRITE REQUEST type when the characteristic supports it.
|
||||||
// This will allow to send long write (also if the characteristic support it).
|
// This will allow to send long write (also if the characteristic support it).
|
||||||
@@ -141,20 +150,25 @@ internal class UARTManager(
|
|||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
fun send(text: String) {
|
fun send(text: String) {
|
||||||
if (rxCharacteristic == null) return
|
if (rxCharacteristic == null) return
|
||||||
if (!TextUtils.isEmpty(text)) {
|
scope.launchWithCatch {
|
||||||
scope.launchWithCatch {
|
val writeType = if (useLongWrite) {
|
||||||
val writeType = if (useLongWrite) {
|
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||||
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
} else {
|
||||||
} else {
|
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
||||||
BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
|
||||||
}
|
|
||||||
val request: WriteRequest = writeCharacteristic(rxCharacteristic, text.toByteArray(), writeType)
|
|
||||||
if (!useLongWrite) {
|
|
||||||
request.split()
|
|
||||||
}
|
|
||||||
request.suspend()
|
|
||||||
log(10, "\"$text\" sent")
|
|
||||||
}
|
}
|
||||||
|
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.ToolboxLogger
|
||||||
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
import no.nordicsemi.android.logger.ToolboxLoggerFactory
|
||||||
import no.nordicsemi.android.service.BleManagerResult
|
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.service.ServiceManager
|
||||||
import no.nordicsemi.android.uart.data.*
|
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.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -22,12 +24,12 @@ class UARTRepository @Inject internal constructor(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val serviceManager: ServiceManager,
|
private val serviceManager: ServiceManager,
|
||||||
private val configurationDataSource: ConfigurationDataSource,
|
private val configurationDataSource: ConfigurationDataSource,
|
||||||
private val toolboxLoggerFactory: ToolboxLoggerFactory
|
private val toolboxLoggerFactory: ToolboxLoggerFactory,
|
||||||
) {
|
) {
|
||||||
private var manager: UARTManager? = null
|
private var manager: UARTManager? = null
|
||||||
private var logger: ToolboxLogger? = 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()
|
internal val data = _data.asStateFlow()
|
||||||
|
|
||||||
val isRunning = data.map { it.isRunning() }
|
val isRunning = data.map { it.isRunning() }
|
||||||
@@ -35,12 +37,12 @@ class UARTRepository @Inject internal constructor(
|
|||||||
|
|
||||||
val lastConfigurationName = configurationDataSource.lastConfigurationName
|
val lastConfigurationName = configurationDataSource.lastConfigurationName
|
||||||
|
|
||||||
fun launch(device: BluetoothDevice) {
|
fun launch(device: DiscoveredBluetoothDevice) {
|
||||||
serviceManager.startService(UARTService::class.java, device)
|
serviceManager.startService(UARTService::class.java, device)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(device: BluetoothDevice, scope: CoroutineScope) {
|
fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) {
|
||||||
val createdLogger = toolboxLoggerFactory.create("UART", device.address).also {
|
val createdLogger = toolboxLoggerFactory.create("UART", device.address()).also {
|
||||||
logger = it
|
logger = it
|
||||||
}
|
}
|
||||||
val manager = UARTManager(context, scope, createdLogger)
|
val manager = UARTManager(context, scope, createdLogger)
|
||||||
@@ -60,9 +62,8 @@ class UARTRepository @Inject internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun runMacro(macro: UARTMacro) {
|
fun runMacro(macro: UARTMacro) {
|
||||||
macro.command?.parseWithNewLineChar(macro.newLineChar)?.let {
|
val command = macro.command?.parseWithNewLineChar(macro.newLineChar)
|
||||||
manager?.send(it)
|
manager?.send(command ?: String.EMPTY)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearItems() {
|
fun clearItems() {
|
||||||
@@ -77,9 +78,9 @@ class UARTRepository @Inject internal constructor(
|
|||||||
configurationDataSource.saveConfigurationName(name)
|
configurationDataSource.saveConfigurationName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun UARTManager.start(device: BluetoothDevice) {
|
private suspend fun UARTManager.start(device: DiscoveredBluetoothDevice) {
|
||||||
try {
|
try {
|
||||||
connect(device)
|
connect(device.device)
|
||||||
.useAutoConnect(false)
|
.useAutoConnect(false)
|
||||||
.retry(3, 100)
|
.retry(3, 100)
|
||||||
.suspend()
|
.suspend()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import no.nordicsemi.android.service.DEVICE_DATA
|
import no.nordicsemi.android.service.DEVICE_DATA
|
||||||
import no.nordicsemi.android.service.NotificationService
|
import no.nordicsemi.android.service.NotificationService
|
||||||
|
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -19,7 +20,7 @@ internal class UARTService : NotificationService() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
super.onStartCommand(intent, flags, startId)
|
||||||
|
|
||||||
val device = intent!!.getParcelableExtra<BluetoothDevice>(DEVICE_DATA)!!
|
val device = intent!!.getParcelableExtra<DiscoveredBluetoothDevice>(DEVICE_DATA)!!
|
||||||
|
|
||||||
repository.start(device, lifecycleScope)
|
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
|
package no.nordicsemi.android.uart.view
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
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.uart.R
|
||||||
import no.nordicsemi.android.utils.EMPTY
|
import no.nordicsemi.android.utils.EMPTY
|
||||||
|
|
||||||
@@ -24,47 +15,28 @@ internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDism
|
|||||||
val name = rememberSaveable { mutableStateOf(String.EMPTY) }
|
val name = rememberSaveable { mutableStateOf(String.EMPTY) }
|
||||||
val isError = rememberSaveable { mutableStateOf(false) }
|
val isError = rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
Dialog(onDismissRequest = { onDismiss() }) {
|
AlertDialog(
|
||||||
Surface(
|
onDismissRequest = { onDismiss() },
|
||||||
color = MaterialTheme.colorScheme.background,
|
title = { Text(stringResource(id = R.string.uart_configuration_dialog_title)) },
|
||||||
shape = RoundedCornerShape(10.dp),
|
text = { NameInput(name, isError) },
|
||||||
shadowElevation = 2.dp,
|
confirmButton = {
|
||||||
) {
|
TextButton(onClick = {
|
||||||
Column(verticalArrangement = Arrangement.SpaceBetween) {
|
if (isNameValid(name.value)) {
|
||||||
Text(
|
onDismiss()
|
||||||
text = stringResource(id = R.string.uart_configuration_dialog_title),
|
onEvent(OnAddConfiguration(name.value))
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
} else {
|
||||||
modifier = Modifier
|
isError.value = true
|
||||||
.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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { onDismiss() }) {
|
||||||
|
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -72,14 +44,17 @@ private fun NameInput(
|
|||||||
name: MutableState<String>,
|
name: MutableState<String>,
|
||||||
isError: MutableState<Boolean>
|
isError: MutableState<Boolean>
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
Column {
|
||||||
TextField(
|
|
||||||
text = name.value,
|
OutlinedTextField(
|
||||||
hint = stringResource(id = R.string.uart_configuration_hint)
|
value = name.value,
|
||||||
) {
|
label = { Text(stringResource(id = R.string.uart_configuration_hint)) },
|
||||||
isError.value = false
|
singleLine = true,
|
||||||
name.value = it
|
onValueChange = {
|
||||||
}
|
isError.value = false
|
||||||
|
name.value = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val errorText = if (isError.value) {
|
val errorText = if (isError.value) {
|
||||||
stringResource(id = R.string.uart_name_empty)
|
stringResource(id = R.string.uart_name_empty)
|
||||||
|
|||||||
@@ -1,18 +1,12 @@
|
|||||||
package no.nordicsemi.android.uart.view
|
package no.nordicsemi.android.uart.view
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
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.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
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.RadioButtonGroup
|
||||||
import no.nordicsemi.android.material.you.RadioButtonItem
|
import no.nordicsemi.android.material.you.RadioButtonItem
|
||||||
import no.nordicsemi.android.material.you.RadioGroupViewEntity
|
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.R
|
||||||
import no.nordicsemi.android.uart.data.MacroEol
|
import no.nordicsemi.android.uart.data.MacroEol
|
||||||
import no.nordicsemi.android.uart.data.MacroIcon
|
import no.nordicsemi.android.uart.data.MacroIcon
|
||||||
@@ -38,130 +30,85 @@ import no.nordicsemi.android.utils.EMPTY
|
|||||||
|
|
||||||
private const val GRID_SIZE = 5
|
private const val GRID_SIZE = 5
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun UARTAddMacroDialog(macro: UARTMacro?, onEvent: (UARTViewEvent) -> Unit) {
|
internal fun UARTAddMacroDialog(macro: UARTMacro?, onEvent: (UARTViewEvent) -> Unit) {
|
||||||
val newLineChar = rememberSaveable { mutableStateOf(macro?.newLineChar ?: MacroEol.LF) }
|
val newLineChar = rememberSaveable { mutableStateOf(macro?.newLineChar ?: MacroEol.LF) }
|
||||||
val command = rememberSaveable { mutableStateOf(macro?.command ?: String.EMPTY) }
|
val command = rememberSaveable { mutableStateOf(macro?.command ?: String.EMPTY) }
|
||||||
val isError = rememberSaveable { mutableStateOf(false) }
|
|
||||||
val selectedIcon = rememberSaveable { mutableStateOf(macro?.icon ?: MacroIcon.values()[0]) }
|
val selectedIcon = rememberSaveable { mutableStateOf(macro?.icon ?: MacroIcon.values()[0]) }
|
||||||
|
|
||||||
Dialog(onDismissRequest = { onEvent(OnEditFinish) }) {
|
AlertDialog(
|
||||||
Surface(
|
onDismissRequest = { onEvent(OnEditFinish) },
|
||||||
color = MaterialTheme.colorScheme.background,
|
dismissButton = {
|
||||||
shape = RoundedCornerShape(10.dp),
|
TextButton(onClick = { onEvent(OnDeleteMacro) }) {
|
||||||
shadowElevation = 0.dp,
|
Text(stringResource(id = R.string.uart_macro_dialog_delete))
|
||||||
) {
|
}
|
||||||
Column {
|
},
|
||||||
Text(
|
confirmButton = {
|
||||||
text = stringResource(id = R.string.uart_macro_dialog_title),
|
TextButton(onClick = {
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
onEvent(OnCreateMacro(UARTMacro(selectedIcon.value, command.value, newLineChar.value)))
|
||||||
modifier = Modifier
|
}) {
|
||||||
.fillMaxWidth()
|
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||||
.padding(16.dp)
|
}
|
||||||
)
|
},
|
||||||
|
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(
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
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))
|
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) }) {
|
Image(
|
||||||
CommandInput(command, isError)
|
painter = painterResource(id = icon.toResId()),
|
||||||
}
|
contentDescription = stringResource(id = R.string.uart_macro_icon),
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer),
|
||||||
items(20) { item ->
|
modifier = Modifier
|
||||||
val icon = MacroIcon.create(item)
|
.size(40.dp)
|
||||||
val background = if (selectedIcon.value == icon) {
|
.clip(RoundedCornerShape(10.dp))
|
||||||
MaterialTheme.colorScheme.primaryContainer
|
.clickable { selectedIcon.value = icon }
|
||||||
} else {
|
.background(background)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CommandInput(
|
private fun CommandInput(command: MutableState<String>) {
|
||||||
command: MutableState<String>,
|
|
||||||
isError: MutableState<Boolean>
|
|
||||||
) {
|
|
||||||
Column {
|
Column {
|
||||||
TextField(
|
OutlinedTextField(
|
||||||
text = command.value,
|
modifier = Modifier
|
||||||
hint = stringResource(id = R.string.uart_macro_dialog_command)
|
.fillMaxWidth(),
|
||||||
) {
|
value = command.value,
|
||||||
isError.value = false
|
label = { Text(stringResource(id = R.string.uart_macro_dialog_command)) },
|
||||||
command.value = it
|
onValueChange = {
|
||||||
}
|
command.value = it
|
||||||
|
}
|
||||||
if (isError.value) {
|
)
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.uart_macro_error),
|
|
||||||
style = MaterialTheme.typography.labelMedium,
|
|
||||||
color = MaterialTheme.colorScheme.error
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.size(16.dp))
|
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
|
package no.nordicsemi.android.uart.view
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
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.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
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.material.you.*
|
import no.nordicsemi.android.material.you.Card
|
||||||
import no.nordicsemi.android.theme.view.SectionTitle
|
|
||||||
import no.nordicsemi.android.uart.R
|
|
||||||
import no.nordicsemi.android.uart.data.MacroEol
|
|
||||||
import no.nordicsemi.android.uart.data.UARTData
|
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
|
@Composable
|
||||||
internal fun UARTContentView(
|
internal fun UARTContentView(
|
||||||
state: UARTData,
|
state: UARTData,
|
||||||
viewState: UARTViewState,
|
|
||||||
onEvent: (UARTViewEvent) -> Unit
|
onEvent: (UARTViewEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
InputSection(onEvent = onEvent)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.size(16.dp))
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
MacroSection(viewState, onEvent)
|
.weight(1f)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.size(16.dp))
|
|
||||||
|
|
||||||
OutputSection(state.displayMessages, onEvent)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.size(16.dp))
|
|
||||||
|
|
||||||
Button(
|
|
||||||
onClick = { onEvent(DisconnectEvent) }
|
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(id = R.string.disconnect))
|
Column(
|
||||||
}
|
modifier = Modifier
|
||||||
}
|
.fillMaxWidth()
|
||||||
}
|
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||||
|
|
||||||
@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
|
|
||||||
) {
|
) {
|
||||||
SectionTitle(
|
OutputSection(state.displayMessages, onEvent)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.graphics.ColorFilter
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import no.nordicsemi.android.uart.R
|
import no.nordicsemi.android.uart.R
|
||||||
import no.nordicsemi.android.uart.data.UARTConfiguration
|
import no.nordicsemi.android.uart.data.UARTConfiguration
|
||||||
import no.nordicsemi.android.uart.data.UARTMacro
|
import no.nordicsemi.android.uart.data.UARTMacro
|
||||||
|
|
||||||
private val divider = 4.dp
|
private val divider = 4.dp
|
||||||
private val buttonSize = 80.dp
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun UARTMacroView(
|
internal fun UARTMacroView(
|
||||||
@@ -27,34 +27,42 @@ internal fun UARTMacroView(
|
|||||||
isEdited: Boolean,
|
isEdited: Boolean,
|
||||||
onEvent: (UARTViewEvent) -> Unit
|
onEvent: (UARTViewEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
BoxWithConstraints {
|
||||||
|
val buttonSize = if (maxWidth < 260.dp) {
|
||||||
Row {
|
48.dp //Minimum touch area
|
||||||
Item(configuration, isEdited, 0, onEvent)
|
} else {
|
||||||
Spacer(modifier = Modifier.size(divider))
|
80.dp
|
||||||
Item(configuration, isEdited, 1, onEvent)
|
|
||||||
Spacer(modifier = Modifier.size(divider))
|
|
||||||
Item(configuration, isEdited, 2, onEvent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.size(divider))
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
Item(configuration, isEdited, 3, onEvent)
|
Item(configuration, isEdited, 0, buttonSize, onEvent)
|
||||||
Spacer(modifier = Modifier.size(divider))
|
Spacer(modifier = Modifier.size(divider))
|
||||||
Item(configuration, isEdited, 4, onEvent)
|
Item(configuration, isEdited, 1, buttonSize, onEvent)
|
||||||
Spacer(modifier = Modifier.size(divider))
|
Spacer(modifier = Modifier.size(divider))
|
||||||
Item(configuration, isEdited, 5, onEvent)
|
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))
|
Spacer(modifier = Modifier.size(divider))
|
||||||
Item(configuration, isEdited, 7, onEvent)
|
|
||||||
Spacer(modifier = Modifier.size(divider))
|
Row {
|
||||||
Item(configuration, isEdited, 8, onEvent)
|
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,
|
configuration: UARTConfiguration,
|
||||||
isEdited: Boolean,
|
isEdited: Boolean,
|
||||||
position: Int,
|
position: Int,
|
||||||
|
buttonSize: Dp,
|
||||||
onEvent: (UARTViewEvent) -> Unit
|
onEvent: (UARTViewEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
val macro = configuration.macros.getOrNull(position)
|
val macro = configuration.macros.getOrNull(position)
|
||||||
|
|
||||||
if (macro == null) {
|
if (macro == null) {
|
||||||
EmptyButton(isEdited, position, onEvent)
|
EmptyButton(isEdited, position, buttonSize, onEvent)
|
||||||
} else {
|
} else {
|
||||||
MacroButton(macro, position, isEdited, onEvent)
|
MacroButton(macro, position, isEdited, buttonSize, onEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +89,7 @@ private fun MacroButton(
|
|||||||
macro: UARTMacro,
|
macro: UARTMacro,
|
||||||
position: Int,
|
position: Int,
|
||||||
isEdited: Boolean,
|
isEdited: Boolean,
|
||||||
|
buttonSize: Dp,
|
||||||
onEvent: (UARTViewEvent) -> Unit
|
onEvent: (UARTViewEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
@@ -104,6 +114,7 @@ private fun MacroButton(
|
|||||||
private fun EmptyButton(
|
private fun EmptyButton(
|
||||||
isEdited: Boolean,
|
isEdited: Boolean,
|
||||||
position: Int,
|
position: Int,
|
||||||
|
buttonSize: Dp,
|
||||||
onEvent: (UARTViewEvent) -> Unit
|
onEvent: (UARTViewEvent) -> Unit
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
|||||||
@@ -8,15 +8,19 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import no.nordicsemi.android.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.service.*
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
import no.nordicsemi.android.theme.view.LoggerIconAppBar
|
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.R
|
||||||
|
import no.nordicsemi.android.uart.data.UARTData
|
||||||
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
|
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
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.DeviceDisconnectedView
|
||||||
|
import no.nordicsemi.ui.scanner.ui.NoDeviceView
|
||||||
import no.nordicsemi.ui.scanner.ui.Reason
|
import no.nordicsemi.ui.scanner.ui.Reason
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -24,29 +28,59 @@ fun UARTScreen() {
|
|||||||
val viewModel: UARTViewModel = hiltViewModel()
|
val viewModel: UARTViewModel = hiltViewModel()
|
||||||
val state = viewModel.state.collectAsState().value
|
val state = viewModel.state.collectAsState().value
|
||||||
|
|
||||||
if (state.showEditDialog) {
|
|
||||||
UARTAddMacroDialog(state.selectedMacro) { viewModel.onEvent(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
val navigateUp = { viewModel.onEvent(NavigateUp) }
|
||||||
|
|
||||||
LoggerIconAppBar(stringResource(id = R.string.uart_title), navigateUp) {
|
AppBar(state = state, navigateUp = navigateUp) { viewModel.onEvent(it) }
|
||||||
viewModel.onEvent(OpenLogger)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
when (state.uartManagerState) {
|
||||||
when (state.uartManagerState) {
|
NoDeviceState -> NoDeviceView()
|
||||||
NoDeviceState -> NoDeviceView()
|
is WorkingState -> when (state.uartManagerState.result) {
|
||||||
is WorkingState -> when (state.uartManagerState.result) {
|
is IdleResult,
|
||||||
is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) }
|
is ConnectingResult -> Scroll { DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } }
|
||||||
is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp)
|
is DisconnectedResult -> Scroll { DeviceDisconnectedView(Reason.USER, navigateUp) }
|
||||||
is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp)
|
is LinkLossResult -> Scroll { DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) }
|
||||||
is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp)
|
is MissingServiceResult -> Scroll { DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) }
|
||||||
is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp)
|
is UnknownErrorResult -> Scroll { DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) }
|
||||||
is SuccessResult -> UARTContentView(state.uartManagerState.result.data, state) { viewModel.onEvent(it) }
|
is SuccessResult -> SuccessScreen(state.uartManagerState.result.data, state, viewModel)
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.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 selectedConfigurationName: String? = null,
|
||||||
val isConfigurationEdited: Boolean = false,
|
val isConfigurationEdited: Boolean = false,
|
||||||
val configurations: List<UARTConfiguration> = emptyList(),
|
val configurations: List<UARTConfiguration> = emptyList(),
|
||||||
val uartManagerState: HTSManagerState = NoDeviceState
|
val uartManagerState: HTSManagerState = NoDeviceState,
|
||||||
|
val isInputVisible: Boolean = true
|
||||||
) {
|
) {
|
||||||
val showEditDialog: Boolean = editedPosition != null
|
val showEditDialog: Boolean = editedPosition != null
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ internal data class UARTViewState(
|
|||||||
|
|
||||||
internal sealed class HTSManagerState
|
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()
|
internal object NoDeviceState : HTSManagerState()
|
||||||
|
|||||||
@@ -23,3 +23,5 @@ internal object DisconnectEvent : UARTViewEvent()
|
|||||||
|
|
||||||
internal object NavigateUp : UARTViewEvent()
|
internal object NavigateUp : UARTViewEvent()
|
||||||
internal object OpenLogger : UARTViewEvent()
|
internal object OpenLogger : UARTViewEvent()
|
||||||
|
|
||||||
|
internal object MacroInputSwitchClick : UARTViewEvent()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import no.nordicsemi.android.navigation.*
|
import no.nordicsemi.android.navigation.*
|
||||||
|
import no.nordicsemi.android.service.IdleResult
|
||||||
import no.nordicsemi.android.uart.data.UARTConfiguration
|
import no.nordicsemi.android.uart.data.UARTConfiguration
|
||||||
import no.nordicsemi.android.uart.data.UARTMacro
|
import no.nordicsemi.android.uart.data.UARTMacro
|
||||||
import no.nordicsemi.android.uart.data.UARTPersistentDataSource
|
import no.nordicsemi.android.uart.data.UARTPersistentDataSource
|
||||||
@@ -36,6 +37,9 @@ internal class UARTViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
repository.data.onEach {
|
repository.data.onEach {
|
||||||
|
if (it is IdleResult) {
|
||||||
|
return@onEach
|
||||||
|
}
|
||||||
_state.value = _state.value.copy(uartManagerState = WorkingState(it))
|
_state.value = _state.value.copy(uartManagerState = WorkingState(it))
|
||||||
}.launchIn(viewModelScope)
|
}.launchIn(viewModelScope)
|
||||||
|
|
||||||
@@ -63,7 +67,7 @@ internal class UARTViewModel @Inject constructor(
|
|||||||
private fun handleArgs(args: DestinationResult) {
|
private fun handleArgs(args: DestinationResult) {
|
||||||
when (args) {
|
when (args) {
|
||||||
is CancelDestinationResult -> navigationManager.navigateUp()
|
is CancelDestinationResult -> navigationManager.navigateUp()
|
||||||
is SuccessDestinationResult -> repository.launch(args.getDevice().device)
|
is SuccessDestinationResult -> repository.launch(args.getDevice())
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,9 +87,14 @@ internal class UARTViewModel @Inject constructor(
|
|||||||
ClearOutputItems -> repository.clearItems()
|
ClearOutputItems -> repository.clearItems()
|
||||||
OpenLogger -> repository.openLogger()
|
OpenLogger -> repository.openLogger()
|
||||||
is OnRunInput -> repository.sendText(event.text, event.newLineChar)
|
is OnRunInput -> repository.sendText(event.text, event.newLineChar)
|
||||||
|
MacroInputSwitchClick -> onMacroInputSwitch()
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onMacroInputSwitch() {
|
||||||
|
_state.value = _state.value.copy(isInputVisible = !state.value.isInputVisible)
|
||||||
|
}
|
||||||
|
|
||||||
private fun onEditConfiguration() {
|
private fun onEditConfiguration() {
|
||||||
val isEdited = _state.value.isConfigurationEdited
|
val isEdited = _state.value.isConfigurationEdited
|
||||||
_state.value = _state.value.copy(isConfigurationEdited = !isEdited)
|
_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"?>
|
<?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_title">UART</string>
|
||||||
|
|
||||||
<string name="uart_no_macros_info">Please define a macro to send command to the device.</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_delete">Delete selected configuration.</string>
|
||||||
<string name="uart_configuration_edit">Edit 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_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_confirm">Confirm</string>
|
||||||
<string name="uart_delete_dialog_cancel">Cancel</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>
|
</resources>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
versionCatalogs {
|
versionCatalogs {
|
||||||
libs {
|
libs {
|
||||||
library('nordic-ble-common', 'no.nordicsemi.android:ble-common: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.0')
|
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-scanner', 'no.nordicsemi.android.support.v18:scanner:1.6.0')
|
||||||
|
|
||||||
library('nordic-log', 'no.nordicsemi.android:log:2.3.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('nordic-theme', 'no.nordicsemi.android.common', 'theme').versionRef('commonlibraries')
|
||||||
|
|
||||||
library('localbroadcastmanager', 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0')
|
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')
|
version('lifecycle', '2.4.1')
|
||||||
library('lifecycle-activity', 'androidx.lifecycle', 'lifecycle-runtime-ktx').versionRef('lifecycle')
|
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')
|
library('datastore-protobuf', 'com.google.protobuf:protobuf-javalite:3.18.0')
|
||||||
bundle('datastore', ['datastore-core', 'datastore-prefs', 'datastore-protobuf'])
|
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-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-tooling-preview', 'androidx.compose.ui', 'ui-tooling-preview').versionRef('compose')
|
||||||
library('compose-navigation', 'androidx.navigation:navigation-compose:2.4.1')
|
library('compose-navigation', 'androidx.navigation:navigation-compose:2.4.1')
|
||||||
bundle('compose', ['compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])
|
bundle('compose', ['compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])
|
||||||
|
|||||||
Reference in New Issue
Block a user