diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 238b7db8..a9baf0a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,6 +34,7 @@ android:exported="true" android:label="@string/app_name" android:launchMode="singleTask" + android:windowSoftInputMode="stateVisible|adjustResize" android:theme="@style/AppTheme.SplashScreen"> diff --git a/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt b/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt index cbf1de78..26481f49 100644 --- a/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt +++ b/app/src/main/java/no/nordicsemi/android/nrftoolbox/MainActivity.kt @@ -2,7 +2,6 @@ package no.nordicsemi.android.nrftoolbox import android.os.Bundle import androidx.activity.compose.setContent -import androidx.activity.viewModels import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface @@ -13,7 +12,6 @@ import no.nordicsemi.android.material.you.NordicActivity import no.nordicsemi.android.material.you.NordicTheme import no.nordicsemi.android.navigation.NavigationView import no.nordicsemi.android.nrftoolbox.repository.ActivitySignals -import no.nordicsemi.android.nrftoolbox.viewmodel.HomeViewModel import no.nordicsemi.ui.scanner.ScannerDestinations import javax.inject.Inject diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt index 97dca30c..ebd7e812 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt @@ -1,5 +1,8 @@ package no.nordicsemi.android.service +import android.annotation.SuppressLint +import android.bluetooth.BluetoothDevice + sealed class BleManagerResult { fun isRunning(): Boolean { @@ -15,8 +18,13 @@ sealed class BleManagerResult { } } +class IdleResult : BleManagerResult() class ConnectingResult : BleManagerResult() -data class SuccessResult(val data: T) : BleManagerResult() +data class SuccessResult(val device: BluetoothDevice, val data: T) : BleManagerResult() { + + @SuppressLint("MissingPermission") + fun deviceName(): String = device.name ?: device.address +} class LinkLossResult(val data: T) : BleManagerResult() class DisconnectedResult : BleManagerResult() diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt index 82d1dab7..dff8cda7 100644 --- a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt +++ b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt @@ -34,7 +34,7 @@ class ConnectionObserverAdapter : ConnectionObserver { override fun onDeviceReady(device: BluetoothDevice) { Log.d(TAG, "onDeviceReady()") - _status.value = SuccessResult(lastValue!!) + _status.value = SuccessResult(device, lastValue!!) } override fun onDeviceDisconnecting(device: BluetoothDevice) { @@ -53,8 +53,8 @@ class ConnectionObserverAdapter : ConnectionObserver { fun setValue(value: T) { lastValue = value - if (_status.value.isRunning()) { - _status.value = SuccessResult(value) + (_status.value as? SuccessResult)?.let { + _status.value = SuccessResult(it.device, value) } } } diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/TopAppBar.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/TopAppBar.kt index 1c024478..1f5a48ff 100644 --- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/TopAppBar.kt +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/TopAppBar.kt @@ -4,16 +4,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Close -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SmallTopAppBar -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -56,6 +50,39 @@ fun TitleAppBar(text: String) { ) } +@Composable +fun LoggerBackIconAppBar(text: String, onClick: () -> Unit) { + SmallTopAppBar( + title = { Text(text) }, + colors = TopAppBarDefaults.smallTopAppBarColors( + scrolledContainerColor = MaterialTheme.colorScheme.primary, + containerColor = colorResource(id = R.color.appBarColor), + titleContentColor = MaterialTheme.colorScheme.onPrimary, + actionIconContentColor = MaterialTheme.colorScheme.onPrimary, + navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, + ), + navigationIcon = { + IconButton(onClick = { onClick() }) { + Icon( + Icons.Default.ArrowBack, + tint = MaterialTheme.colorScheme.onPrimary, + contentDescription = stringResource(id = R.string.back_screen), + ) + } + }, + actions = { + IconButton(onClick = { onClick() }) { + Icon( + painterResource(id = R.drawable.ic_logger), + contentDescription = stringResource(id = R.string.open_logger), + tint = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.size(24.dp) + ) + } + } + ) +} + @Composable fun BackIconAppBar(text: String, onClick: () -> Unit) { SmallTopAppBar( @@ -71,25 +98,16 @@ fun BackIconAppBar(text: String, onClick: () -> Unit) { IconButton(onClick = { onClick() }) { Icon( Icons.Default.ArrowBack, + tint = MaterialTheme.colorScheme.onPrimary, contentDescription = stringResource(id = R.string.back_screen), ) } }, - actions = { - IconButton(onClick = { onClick() }) { - Icon( - painterResource(id = R.drawable.ic_logger), - contentDescription = stringResource(id = R.string.back_screen), - tint = MaterialTheme.colorScheme.onPrimary, - modifier = Modifier.size(24.dp) - ) - } - } ) } @Composable -fun LoggerIconAppBar(text: String, onClick: () -> Unit, onLoggerClick: () -> Unit) { +fun LoggerIconAppBar(text: String, onClick: () -> Unit, onDisconnectClick: () -> Unit, onLoggerClick: () -> Unit) { SmallTopAppBar( title = { Text(text) }, colors = TopAppBarDefaults.smallTopAppBarColors( @@ -103,15 +121,25 @@ fun LoggerIconAppBar(text: String, onClick: () -> Unit, onLoggerClick: () -> Uni IconButton(onClick = { onClick() }) { Icon( Icons.Default.ArrowBack, + tint = MaterialTheme.colorScheme.onPrimary, contentDescription = stringResource(id = R.string.back_screen), ) } }, actions = { + TextButton( + onClick = { onDisconnectClick() }, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onPrimary + ) + ) { + Text(stringResource(id = R.string.disconnect)) + } IconButton(onClick = { onLoggerClick() }) { Icon( painterResource(id = R.drawable.ic_logger), - contentDescription = stringResource(id = R.string.back_screen), + contentDescription = stringResource(id = R.string.open_logger), tint = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.size(24.dp) ) diff --git a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/dialog/StringListDialog.kt b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/dialog/StringListDialog.kt index 29e31714..f930491d 100644 --- a/lib_theme/src/main/java/no/nordicsemi/android/theme/view/dialog/StringListDialog.kt +++ b/lib_theme/src/main/java/no/nordicsemi/android/theme/view/dialog/StringListDialog.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -17,43 +18,16 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import no.nordicsemi.android.material.you.Card import no.nordicsemi.android.theme.R @Composable fun StringListDialog(config: StringListDialogConfig) { - Dialog(onDismissRequest = { config.onResult(FlowCanceled) }) { - StringListView(config) - } -} - -@Composable -fun StringListView(config: StringListDialogConfig) { - Card( - modifier = Modifier.height(300.dp), - backgroundColor = MaterialTheme.colorScheme.surfaceVariant, - shape = RoundedCornerShape(10.dp), - elevation = 0.dp - ) { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.SpaceBetween - ) { - Column( - modifier = Modifier.fillMaxWidth(), - ) { - Text( - text = config.title ?: stringResource(id = R.string.dialog).toAnnotatedString(), - style = MaterialTheme.typography.headlineMedium - ) - } - - Spacer(modifier = Modifier.size(8.dp)) - + AlertDialog( + onDismissRequest = { config.onResult(FlowCanceled) }, + title = { Text(text = config.title ?: stringResource(id = R.string.dialog).toAnnotatedString()) }, + text = { Column( modifier = Modifier - .fillMaxHeight(0.8f) .verticalScroll(rememberScrollState()) ) { @@ -81,17 +55,13 @@ fun StringListView(config: StringListDialogConfig) { } } } - - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.End - ) { - TextButton(onClick = { config.onResult(FlowCanceled) }) { - Text( - text = stringResource(id = R.string.cancel), - ) - } + }, + confirmButton = { + TextButton(onClick = { config.onResult(FlowCanceled) }) { + Text( + text = stringResource(id = R.string.cancel), + ) } } - } + ) } diff --git a/lib_theme/src/main/res/values/strings.xml b/lib_theme/src/main/res/values/strings.xml index 76cb2c68..720465f7 100644 --- a/lib_theme/src/main/res/values/strings.xml +++ b/lib_theme/src/main/res/values/strings.xml @@ -3,12 +3,13 @@ nRF Toolbox Dialog - CANCEL + Cancel Back Close the application. Close the current screen. + Open logger application. Disconnect Battery diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt index 1837aaf8..0b4d02e5 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/data/BPSManager.kt @@ -61,7 +61,7 @@ internal class BPSManager( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSRepository.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSRepository.kt index 84c7486e..d4a3f043 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSRepository.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSRepository.kt @@ -1,6 +1,5 @@ package no.nordicsemi.android.bps.repository -import android.bluetooth.BluetoothDevice import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.scopes.ViewModelScoped @@ -14,6 +13,7 @@ import no.nordicsemi.android.bps.data.BPSManager import no.nordicsemi.android.logger.ToolboxLogger import no.nordicsemi.android.logger.ToolboxLoggerFactory import no.nordicsemi.android.service.BleManagerResult +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @ViewModelScoped @@ -25,9 +25,9 @@ internal class BPSRepository @Inject constructor( private var logger: ToolboxLogger? = null - fun downloadData(device: BluetoothDevice): Flow> = callbackFlow { + fun downloadData(device: DiscoveredBluetoothDevice): Flow> = callbackFlow { val scope = this - val createdLogger = toolboxLoggerFactory.create("BPS", device.address).also { + val createdLogger = toolboxLoggerFactory.create("BPS", device.address()).also { logger = it } val manager = BPSManager(context, scope, createdLogger) @@ -36,7 +36,7 @@ internal class BPSRepository @Inject constructor( trySend(it) }.launchIn(scope) - manager.connect(device) + manager.connect(device.device) .useAutoConnect(false) .retry(3, 100) .enqueue() diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSContentView.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSContentView.kt index 29b017cc..acd10f1a 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSContentView.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSContentView.kt @@ -15,7 +15,7 @@ import no.nordicsemi.android.bps.R import no.nordicsemi.android.bps.data.BPSData @Composable -internal fun BPSContentView(state: BPSData, onEvent: (BPSScreenViewEvent) -> Unit) { +internal fun BPSContentView(state: BPSData, onEvent: (BPSViewEvent) -> Unit) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(16.dp) diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt index a991dbef..ce7c0a17 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreen.kt @@ -1,5 +1,6 @@ package no.nordicsemi.android.bps.view +import android.annotation.SuppressLint import androidx.compose.foundation.layout.Column import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -9,14 +10,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.bps.R +import no.nordicsemi.android.bps.data.BPSData import no.nordicsemi.android.bps.viewmodel.BPSViewModel import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -27,16 +29,13 @@ fun BPSScreen() { Column { val navigateUp = { viewModel.onEvent(DisconnectEvent) } - LoggerIconAppBar(stringResource(id = R.string.bps_title), { - viewModel.onEvent(DisconnectEvent) - }) { - viewModel.onEvent(OpenLoggerEvent) - } + AppBar(state = state, navigateUp = navigateUp, viewModel = viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) @@ -48,3 +47,20 @@ fun BPSScreen() { } } } + +@Composable +private fun AppBar(state: BPSViewState, navigateUp: () -> Unit, viewModel: BPSViewModel) { + val toolbarName = (state as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.bps_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, { + viewModel.onEvent(DisconnectEvent) + }, { viewModel.onEvent(DisconnectEvent) }) { + viewModel.onEvent(OpenLoggerEvent) + } + } +} diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreenViewEvent.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreenViewEvent.kt deleted file mode 100644 index d057a27a..00000000 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSScreenViewEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package no.nordicsemi.android.bps.view - -internal sealed class BPSScreenViewEvent - -internal object DisconnectEvent : BPSScreenViewEvent() - -internal object OpenLoggerEvent : BPSScreenViewEvent() diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewEvent.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewEvent.kt new file mode 100644 index 00000000..acb1adf3 --- /dev/null +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewEvent.kt @@ -0,0 +1,7 @@ +package no.nordicsemi.android.bps.view + +internal sealed class BPSViewEvent + +internal object DisconnectEvent : BPSViewEvent() + +internal object OpenLoggerEvent : BPSViewEvent() diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewState.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewState.kt index 36826f5e..d5b145a2 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewState.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/view/BPSViewState.kt @@ -2,8 +2,12 @@ package no.nordicsemi.android.bps.view import no.nordicsemi.android.bps.data.BPSData import no.nordicsemi.android.service.BleManagerResult +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice internal sealed class BPSViewState -internal data class WorkingState(val result: BleManagerResult) : BPSViewState() +internal data class WorkingState( + val result: BleManagerResult +) : BPSViewState() + internal object NoDeviceState : BPSViewState() diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt index 1d031de8..8ea7928a 100644 --- a/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt +++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/viewmodel/BPSViewModel.kt @@ -43,7 +43,7 @@ internal class BPSViewModel @Inject constructor( }.exhaustive } - fun onEvent(event: BPSScreenViewEvent) { + fun onEvent(event: BPSViewEvent) { when (event) { DisconnectEvent -> navigationManager.navigateUp() OpenLoggerEvent -> repository.openLogger() @@ -51,7 +51,7 @@ internal class BPSViewModel @Inject constructor( } private fun connectDevice(device: DiscoveredBluetoothDevice) { - repository.downloadData(device.device).onEach { + repository.downloadData(device).onEach { _state.value = WorkingState(it) }.launchIn(viewModelScope) } diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMManager.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMManager.kt index 034e3c6c..cb17db14 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMManager.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/data/CGMManager.kt @@ -85,7 +85,7 @@ internal class CGMManager( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMRepository.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMRepository.kt index 10ea642d..57ed9ae3 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMRepository.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMRepository.kt @@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.ConnectingResult import no.nordicsemi.android.service.ServiceManager +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @@ -33,12 +34,12 @@ class CGMRepository @Inject constructor( val isRunning = data.map { it.isRunning() } val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } - fun launch(device: BluetoothDevice) { + fun launch(device: DiscoveredBluetoothDevice) { serviceManager.startService(CGMService::class.java, device) } - fun start(device: BluetoothDevice, scope: CoroutineScope) { - val createdLogger = toolboxLoggerFactory.create("CGMS", device.address).also { + fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { + val createdLogger = toolboxLoggerFactory.create("CGMS", device.address()).also { logger = it } val manager = CGMManager(context, scope, createdLogger) @@ -53,9 +54,9 @@ class CGMRepository @Inject constructor( } } - private suspend fun CGMManager.start(device: BluetoothDevice) { + private suspend fun CGMManager.start(device: DiscoveredBluetoothDevice) { try { - connect(device) + connect(device.device) .useAutoConnect(false) .retry(3, 100) .suspend() diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt index d97d1a63..51f81d03 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMService.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +20,7 @@ internal class CGMService : NotificationService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val device = intent!!.getParcelableExtra(DEVICE_DATA)!! + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! repository.start(device, lifecycleScope) diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt index 820c8e9b..2246958c 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMScreen.kt @@ -9,32 +9,32 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.cgms.R -import no.nordicsemi.android.cgms.viewmodel.CGMScreenViewModel +import no.nordicsemi.android.cgms.data.CGMData +import no.nordicsemi.android.cgms.viewmodel.CGMViewModel import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable fun CGMScreen() { - val viewModel: CGMScreenViewModel = hiltViewModel() + val viewModel: CGMViewModel = hiltViewModel() val state = viewModel.state.collectAsState().value Column { val navigateUp = { viewModel.onEvent(NavigateUp) } - LoggerIconAppBar(stringResource(id = R.string.cgms_title), navigateUp) { - viewModel.onEvent(OpenLoggerEvent) - } + AppBar(state, navigateUp, viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) @@ -46,3 +46,18 @@ fun CGMScreen() { } } } + +@Composable +private fun AppBar(state: CGMViewState, navigateUp: () -> Unit, viewModel: CGMViewModel) { + val toolbarName = (state as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.cgms_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) { + viewModel.onEvent(OpenLoggerEvent) + } + } +} diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMViewState.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMViewState.kt index f25acb02..35db4a28 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMViewState.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/view/CGMViewState.kt @@ -3,7 +3,7 @@ package no.nordicsemi.android.cgms.view import no.nordicsemi.android.cgms.data.CGMData import no.nordicsemi.android.service.BleManagerResult -internal sealed class BPSViewState +internal sealed class CGMViewState -internal data class WorkingState(val result: BleManagerResult) : BPSViewState() -internal object NoDeviceState : BPSViewState() +internal data class WorkingState(val result: BleManagerResult) : CGMViewState() +internal object NoDeviceState : CGMViewState() diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMViewModel.kt similarity index 94% rename from profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt rename to profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMViewModel.kt index 06a3f954..9cf47f27 100644 --- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMScreenViewModel.kt +++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/viewmodel/CGMViewModel.kt @@ -16,12 +16,12 @@ import no.nordicsemi.ui.scanner.ScannerDestinationId import javax.inject.Inject @HiltViewModel -internal class CGMScreenViewModel @Inject constructor( +internal class CGMViewModel @Inject constructor( private val repository: CGMRepository, private val navigationManager: NavigationManager ) : ViewModel() { - private val _state = MutableStateFlow(NoDeviceState) + private val _state = MutableStateFlow(NoDeviceState) val state = _state.asStateFlow() init { @@ -58,7 +58,7 @@ internal class CGMScreenViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> repository.launch(args.getDevice().device) + is SuccessDestinationResult -> repository.launch(args.getDevice()) }.exhaustive } diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCManager.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCManager.kt index 273ce2af..520fa5fa 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCManager.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCManager.kt @@ -59,7 +59,7 @@ internal class CSCManager( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCRepository.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCRepository.kt index 2b80f735..3372b2e6 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCRepository.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCRepository.kt @@ -15,6 +15,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.ConnectingResult import no.nordicsemi.android.service.ServiceManager +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @@ -34,12 +35,12 @@ class CSCRepository @Inject constructor( val isRunning = data.map { it.isRunning() } val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } - fun launch(device: BluetoothDevice) { + fun launch(device: DiscoveredBluetoothDevice) { serviceManager.startService(CSCService::class.java, device) } - fun start(device: BluetoothDevice, scope: CoroutineScope) { - val createdLogger = toolboxLoggerFactory.create("CSC", device.address).also { + fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { + val createdLogger = toolboxLoggerFactory.create("CSC", device.address()).also { logger = it } val manager = CSCManager(context, scope, createdLogger) @@ -58,9 +59,9 @@ class CSCRepository @Inject constructor( manager?.setWheelSize(wheelSize) } - private suspend fun CSCManager.start(device: BluetoothDevice) { + private suspend fun CSCManager.start(device: DiscoveredBluetoothDevice) { try { - connect(device) + connect(device.device) .useAutoConnect(false) .retry(3, 100) .suspend() diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt index 32fdf0f0..488ceb93 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCService.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +20,7 @@ internal class CSCService : NotificationService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val device = intent!!.getParcelableExtra(DEVICE_DATA)!! + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! repository.start(device, lifecycleScope) diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt index 33774a33..f9ec79d6 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/view/CSCScreen.kt @@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.csc.R +import no.nordicsemi.android.csc.data.CSCData import no.nordicsemi.android.csc.viewmodel.CSCViewModel import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -27,14 +28,13 @@ fun CSCScreen() { Column { val navigateUp = { viewModel.onEvent(NavigateUp) } - LoggerIconAppBar(stringResource(id = R.string.csc_title), navigateUp) { - viewModel.onEvent(OpenLogger) - } + AppBar(state, navigateUp, viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state.cscManagerState) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.cscManagerState.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(OnDisconnectButtonClick) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) @@ -46,3 +46,18 @@ fun CSCScreen() { } } } + +@Composable +private fun AppBar(state: CSCViewState, navigateUp: () -> Unit, viewModel: CSCViewModel) { + val toolbarName = (state.cscManagerState as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.csc_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(OnDisconnectButtonClick) }) { + viewModel.onEvent(OpenLogger) + } + } +} diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt index 0dd8b15d..ec692fbd 100644 --- a/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt +++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/viewmodel/CSCViewModel.kt @@ -48,7 +48,7 @@ internal class CSCViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> repository.launch(args.getDevice().device) + is SuccessDestinationResult -> repository.launch(args.getDevice()) }.exhaustive } diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt index 5ede2603..c6b99e46 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/data/GLSManager.kt @@ -71,7 +71,7 @@ internal class GLSManager( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/details/view/GLSDetailsScreen.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/details/view/GLSDetailsScreen.kt index 845f07f1..fd017a33 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/details/view/GLSDetailsScreen.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/details/view/GLSDetailsScreen.kt @@ -6,7 +6,7 @@ import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.gls.R import no.nordicsemi.android.gls.details.viewmodel.GLSDetailsViewModel -import no.nordicsemi.android.theme.view.BackIconAppBar +import no.nordicsemi.android.theme.view.LoggerBackIconAppBar @Composable internal fun GLSDetailsScreen() { @@ -14,7 +14,7 @@ internal fun GLSDetailsScreen() { val record = viewModel.record Column { - BackIconAppBar(stringResource(id = R.string.gls_title)) { + LoggerBackIconAppBar(stringResource(id = R.string.gls_title)) { viewModel.navigateBack() } diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt index e39c952b..6b4e90f9 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSScreen.kt @@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.gls.R +import no.nordicsemi.android.gls.data.GLSData import no.nordicsemi.android.gls.main.viewmodel.GLSViewModel import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -27,16 +28,13 @@ fun GLSScreen() { Column { val navigateUp = { viewModel.onEvent(DisconnectEvent) } - LoggerIconAppBar(stringResource(id = R.string.gls_title), { - viewModel.onEvent(DisconnectEvent) - }) { - viewModel.onEvent(OpenLoggerEvent) - } + AppBar(state, navigateUp, viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) @@ -48,3 +46,20 @@ fun GLSScreen() { } } } + +@Composable +private fun AppBar(state: GLSViewState, navigateUp: () -> Unit, viewModel: GLSViewModel) { + val toolbarName = (state as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.gls_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, { + viewModel.onEvent(DisconnectEvent) + }, { viewModel.onEvent(DisconnectEvent) }) { + viewModel.onEvent(OpenLoggerEvent) + } + } +} diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSState.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSState.kt index 63839047..f9ded715 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSState.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/view/GLSState.kt @@ -3,7 +3,7 @@ package no.nordicsemi.android.gls.main.view import no.nordicsemi.android.gls.data.GLSData import no.nordicsemi.android.service.BleManagerResult -internal sealed class BPSViewState +internal sealed class GLSViewState -internal data class WorkingState(val result: BleManagerResult) : BPSViewState() -internal object NoDeviceState : BPSViewState() +internal data class WorkingState(val result: BleManagerResult) : GLSViewState() +internal object NoDeviceState : GLSViewState() diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt index bb002791..eb62319b 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/main/viewmodel/GLSViewModel.kt @@ -21,7 +21,7 @@ internal class GLSViewModel @Inject constructor( private val navigationManager: NavigationManager ) : ViewModel() { - private val _state = MutableStateFlow(NoDeviceState) + private val _state = MutableStateFlow(NoDeviceState) val state = _state.asStateFlow() init { @@ -52,7 +52,7 @@ internal class GLSViewModel @Inject constructor( } private fun connectDevice(device: DiscoveredBluetoothDevice) { - repository.downloadData(device.device).onEach { + repository.downloadData(device).onEach { _state.value = WorkingState(it) }.launchIn(viewModelScope) } diff --git a/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRepository.kt b/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRepository.kt index ac27ebf7..daf8f711 100644 --- a/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRepository.kt +++ b/profile_gls/src/main/java/no/nordicsemi/android/gls/repository/GLSRepository.kt @@ -18,6 +18,7 @@ import no.nordicsemi.android.logger.ToolboxLogger import no.nordicsemi.android.logger.ToolboxLoggerFactory import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @ViewModelScoped @@ -30,14 +31,14 @@ internal class GLSRepository @Inject constructor( private var manager: GLSManager? = null private var logger: ToolboxLogger? = null - fun downloadData(device: BluetoothDevice): Flow> = callbackFlow { + fun downloadData(device: DiscoveredBluetoothDevice): Flow> = callbackFlow { val scope = this - val createdLogger = toolboxLoggerFactory.create("GLS", device.address).also { + val createdLogger = toolboxLoggerFactory.create("GLS", device.address()).also { logger = it } val managerInstance = manager ?: GLSManager(context, scope, createdLogger).apply { try { - connect(device) + connect(device.device) .useAutoConnect(false) .retry(3, 100) .suspend() diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSManager.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSManager.kt index 41466198..c1095d9a 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSManager.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/data/HRSManager.kt @@ -61,7 +61,7 @@ internal class HRSManager( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSRepository.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSRepository.kt index b5258e3d..312485b7 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSRepository.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSRepository.kt @@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.ConnectingResult import no.nordicsemi.android.service.ServiceManager +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @@ -33,12 +34,12 @@ class HRSRepository @Inject constructor( val isRunning = data.map { it.isRunning() } val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } - fun launch(device: BluetoothDevice) { + fun launch(device: DiscoveredBluetoothDevice) { serviceManager.startService(HRSService::class.java, device) } - fun start(device: BluetoothDevice, scope: CoroutineScope) { - val createdLogger = toolboxLoggerFactory.create("HRS", device.address).also { + fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { + val createdLogger = toolboxLoggerFactory.create("HRS", device.address()).also { logger = it } val manager = HRSManager(context, scope, createdLogger) @@ -57,9 +58,9 @@ class HRSRepository @Inject constructor( logger?.openLogger() } - private suspend fun HRSManager.start(device: BluetoothDevice) { + private suspend fun HRSManager.start(device: DiscoveredBluetoothDevice) { try { - connect(device) + connect(device.device) .useAutoConnect(false) .retry(3, 100) .suspend() diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt index 565f4cd0..ce0077fd 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/service/HRSService.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +20,7 @@ internal class HRSService : NotificationService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val device = intent!!.getParcelableExtra(DEVICE_DATA)!! + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! repository.start(device, lifecycleScope) diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt index 37ae8175..87505533 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/view/HRSScreen.kt @@ -9,8 +9,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.hrs.R +import no.nordicsemi.android.hrs.data.HRSData import no.nordicsemi.android.hrs.viewmodel.HRSViewModel import no.nordicsemi.android.service.* +import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar import no.nordicsemi.android.utils.exhaustive import no.nordicsemi.ui.scanner.ui.DeviceConnectingView @@ -26,14 +28,13 @@ fun HRSScreen() { Column { val navigateUp = { viewModel.onEvent(NavigateUpEvent) } - LoggerIconAppBar(stringResource(id = R.string.hrs_title), navigateUp) { - viewModel.onEvent(OpenLoggerEvent) - } + AppBar(state, navigateUp, viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) @@ -45,3 +46,18 @@ fun HRSScreen() { } } } + +@Composable +private fun AppBar(state: HRSViewState, navigateUp: () -> Unit, viewModel: HRSViewModel) { + val toolbarName = (state as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.hrs_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) { + viewModel.onEvent(OpenLoggerEvent) + } + } +} diff --git a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt index 504d98de..b5d8aa90 100644 --- a/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt +++ b/profile_hrs/src/main/java/no/nordicsemi/android/hrs/viewmodel/HRSViewModel.kt @@ -49,7 +49,7 @@ internal class HRSViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> repository.launch(args.getDevice().device) + is SuccessDestinationResult -> repository.launch(args.getDevice()) }.exhaustive } diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSManager.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSManager.kt index cfeb285e..7be575f3 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSManager.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/data/HTSManager.kt @@ -56,7 +56,7 @@ internal class HTSManager internal constructor( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSRepository.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSRepository.kt index 0d767b46..cd22b37d 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSRepository.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSRepository.kt @@ -14,6 +14,7 @@ import no.nordicsemi.android.logger.ToolboxLoggerFactory import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.ConnectingResult import no.nordicsemi.android.service.ServiceManager +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @@ -33,12 +34,12 @@ class HTSRepository @Inject constructor( val isRunning = data.map { it.isRunning() } val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } - fun launch(device: BluetoothDevice) { + fun launch(device: DiscoveredBluetoothDevice) { serviceManager.startService(HTSService::class.java, device) } - fun start(device: BluetoothDevice, scope: CoroutineScope) { - val createdLogger = toolboxLoggerFactory.create("HTS", device.address).also { + fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { + val createdLogger = toolboxLoggerFactory.create("HTS", device.address()).also { logger = it } val manager = HTSManager(context, scope, createdLogger) @@ -57,9 +58,9 @@ class HTSRepository @Inject constructor( logger?.openLogger() } - private suspend fun HTSManager.start(device: BluetoothDevice) { + private suspend fun HTSManager.start(device: DiscoveredBluetoothDevice) { try { - connect(device) + connect(device.device) .useAutoConnect(false) .retry(3, 100) .suspend() diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt index fbb41b6e..5d7f71f5 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/repository/HTSService.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +20,7 @@ internal class HTSService : NotificationService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val device = intent!!.getParcelableExtra(DEVICE_DATA)!! + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! repository.start(device, lifecycleScope) diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt index bf25a839..4b780caf 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/view/HTSScreen.kt @@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.hts.R +import no.nordicsemi.android.hts.data.HTSData import no.nordicsemi.android.hts.viewmodel.HTSViewModel import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -27,14 +28,13 @@ fun HTSScreen() { Column { val navigateUp = { viewModel.onEvent(NavigateUp) } - LoggerIconAppBar(stringResource(id = R.string.hts_title), navigateUp) { - viewModel.onEvent(OpenLoggerEvent) - } + AppBar(state, navigateUp, viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state.htsManagerState) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.htsManagerState.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) @@ -46,3 +46,19 @@ fun HTSScreen() { } } } + +@Composable +private fun AppBar(state: HTSViewState, navigateUp: () -> Unit, viewModel: HTSViewModel) { + val toolbarName = (state.htsManagerState as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.hts_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) { + viewModel.onEvent(OpenLoggerEvent) + } + } +} + diff --git a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt index 9a7d3d8f..3e8b63a3 100644 --- a/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt +++ b/profile_hts/src/main/java/no/nordicsemi/android/hts/viewmodel/HTSViewModel.kt @@ -48,7 +48,7 @@ internal class HTSViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> repository.launch(args.getDevice().device) + is SuccessDestinationResult -> repository.launch(args.getDevice()) }.exhaustive } diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXManager.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXManager.kt index 1e4eb51e..db10b09c 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXManager.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/data/PRXManager.kt @@ -68,7 +68,7 @@ internal class PRXManager( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXRepository.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXRepository.kt index 4ebd7b95..695680e0 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXRepository.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXRepository.kt @@ -12,6 +12,7 @@ import no.nordicsemi.android.prx.data.PRXData import no.nordicsemi.android.prx.data.PRXManager import no.nordicsemi.android.prx.data.ProximityServerManager import no.nordicsemi.android.service.* +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @@ -34,13 +35,13 @@ class PRXRepository @Inject internal constructor( val isRunning = data.map { it.isRunning() } val hasBeenDisconnectedWithoutLinkLoss = data.map { it.hasBeenDisconnectedWithoutLinkLoss() } - fun launch(device: BluetoothDevice) { + fun launch(device: DiscoveredBluetoothDevice) { serviceManager.startService(PRXService::class.java, device) proximityServerManager.open() } - fun start(device: BluetoothDevice, scope: CoroutineScope) { - val createdLogger = toolboxLoggerFactory.create("PRX", device.address).also { + fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { + val createdLogger = toolboxLoggerFactory.create("PRX", device.address()).also { logger = it } val manager = PRXManager(context, scope, createdLogger) @@ -52,7 +53,7 @@ class PRXRepository @Inject internal constructor( handleLocalAlarm(it) }.launchIn(scope) - manager.connect(device) + manager.connect(device.device) .useAutoConnect(true) .retry(3, 100) .enqueue() diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt index e2654c62..1f440d90 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/repository/PRXService.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +20,7 @@ internal class PRXService : NotificationService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val device = intent!!.getParcelableExtra(DEVICE_DATA)!! + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! repository.start(device, lifecycleScope) diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt index ce53534e..b5230b9a 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/view/PRXScreen.kt @@ -10,14 +10,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.prx.R +import no.nordicsemi.android.prx.data.PRXData import no.nordicsemi.android.prx.viewmodel.PRXViewModel import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -28,14 +29,13 @@ fun PRXScreen() { Column(horizontalAlignment = Alignment.CenterHorizontally) { val navigateUp = { viewModel.onEvent(NavigateUpEvent) } - LoggerIconAppBar(stringResource(id = R.string.prx_title), navigateUp) { - viewModel.onEvent(OpenLoggerEvent) - } + AppBar(state, navigateUp, viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceOutOfRangeView { viewModel.onEvent(DisconnectEvent) } @@ -47,3 +47,18 @@ fun PRXScreen() { } } } + +@Composable +private fun AppBar(state: PRXViewState, navigateUp: () -> Unit, viewModel: PRXViewModel) { + val toolbarName = (state as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.prx_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) { + viewModel.onEvent(OpenLoggerEvent) + } + } +} diff --git a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt index 786a9796..4aac1ea3 100644 --- a/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt +++ b/profile_prx/src/main/java/no/nordicsemi/android/prx/viewmodel/PRXViewModel.kt @@ -48,7 +48,7 @@ internal class PRXViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> repository.launch(args.getDevice().device) + is SuccessDestinationResult -> repository.launch(args.getDevice()) }.exhaustive } diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSManager.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSManager.kt index 01e4918c..cc8ed938 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSManager.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/data/RSCSManager.kt @@ -56,7 +56,7 @@ internal class RSCSManager internal constructor( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSRepository.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSRepository.kt index 15688907..4b4f2141 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSRepository.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSRepository.kt @@ -14,6 +14,7 @@ import no.nordicsemi.android.rscs.data.RSCSManager import no.nordicsemi.android.service.BleManagerResult import no.nordicsemi.android.service.ConnectingResult import no.nordicsemi.android.service.ServiceManager +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @@ -33,12 +34,12 @@ class RSCSRepository @Inject constructor( val isRunning = data.map { it.isRunning() } val hasBeenDisconnected = data.map { it.hasBeenDisconnected() } - fun launch(device: BluetoothDevice) { + fun launch(device: DiscoveredBluetoothDevice) { serviceManager.startService(RSCSService::class.java, device) } - fun start(device: BluetoothDevice, scope: CoroutineScope) { - val createdLogger = toolboxLoggerFactory.create("RSCS", device.address).also { + fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { + val createdLogger = toolboxLoggerFactory.create("RSCS", device.address()).also { logger = it } val manager = RSCSManager(context, scope, createdLogger) @@ -57,9 +58,9 @@ class RSCSRepository @Inject constructor( logger?.openLogger() } - private suspend fun RSCSManager.start(device: BluetoothDevice) { + private suspend fun RSCSManager.start(device: DiscoveredBluetoothDevice) { try { - connect(device) + connect(device.device) .useAutoConnect(false) .retry(3, 100) .suspend() diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt index 70007ded..6c05e241 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/repository/RSCSService.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +20,7 @@ internal class RSCSService : NotificationService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val device = intent!!.getParcelableExtra(DEVICE_DATA)!! + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! repository.start(device, lifecycleScope) diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt index 2f18abf2..f3c04d11 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/view/RSCSScreen.kt @@ -9,14 +9,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel import no.nordicsemi.android.rscs.R +import no.nordicsemi.android.rscs.data.RSCSData import no.nordicsemi.android.rscs.viewmodel.RSCSViewModel import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -27,14 +28,13 @@ fun RSCSScreen() { Column { val navigateUp = { viewModel.onEvent(NavigateUpEvent) } - LoggerIconAppBar(stringResource(id = R.string.rscs_title), navigateUp) { - viewModel.onEvent(OpenLoggerEvent) - } + AppBar(state, navigateUp, viewModel) Column(modifier = Modifier.verticalScroll(rememberScrollState())) { when (state) { NoDeviceState -> NoDeviceView() is WorkingState -> when (state.result) { + is IdleResult, is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) @@ -46,3 +46,18 @@ fun RSCSScreen() { } } } + +@Composable +private fun AppBar(state: RSCSViewState, navigateUp: () -> Unit, viewModel: RSCSViewModel) { + val toolbarName = (state as? WorkingState)?.let { + (it.result as? SuccessResult)?.deviceName() + } + + if (toolbarName == null) { + BackIconAppBar(stringResource(id = R.string.rscs_title), navigateUp) + } else { + LoggerIconAppBar(toolbarName, navigateUp, { viewModel.onEvent(DisconnectEvent) }) { + viewModel.onEvent(OpenLoggerEvent) + } + } +} diff --git a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt index 6977b1c8..03c00310 100644 --- a/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt +++ b/profile_rscs/src/main/java/no/nordicsemi/android/rscs/viewmodel/RSCSViewModel.kt @@ -48,7 +48,7 @@ internal class RSCSViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> repository.launch(args.getDevice().device) + is SuccessDestinationResult -> repository.launch(args.getDevice()) }.exhaustive } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt index 3980b64b..0ad98522 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTData.kt @@ -1,14 +1,19 @@ package no.nordicsemi.android.uart.data internal data class UARTData( - val messages: List = emptyList(), + val messages: List = emptyList(), val batteryLevel: Int? = null, ) { - val displayMessages = messages.reversed().take(10) + val displayMessages = messages } -internal data class UARTOutputRecord( +internal data class UARTRecord( val text: String, + val type: UARTRecordType, val timestamp: Long = System.currentTimeMillis() ) + +enum class UARTRecordType { + INPUT, OUTPUT +} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTManager.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTManager.kt index 2205529b..c3ce714f 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTManager.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/data/UARTManager.kt @@ -29,9 +29,9 @@ import android.content.Context import android.text.TextUtils import android.util.Log import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.withContext import no.nordicsemi.android.ble.BleManager import no.nordicsemi.android.ble.WriteRequest import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse @@ -49,7 +49,8 @@ private val UART_RX_CHARACTERISTIC_UUID = UUID.fromString("6E400002-B5A3-F393-E0 private val UART_TX_CHARACTERISTIC_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb") -private val BATTERY_LEVEL_CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb") +private val BATTERY_LEVEL_CHARACTERISTIC_UUID = + UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb") internal class UARTManager( context: Context, @@ -68,7 +69,7 @@ internal class UARTManager( val dataHolder = ConnectionObserverAdapter() init { - setConnectionObserver(dataHolder) + connectionObserver = dataHolder data.onEach { dataHolder.setValue(it) @@ -87,18 +88,25 @@ internal class UARTManager( @SuppressLint("WrongConstant") override fun initialize() { - setNotificationCallback(txCharacteristic).asFlow().onEach { - val text: String = it.getStringValue(0) ?: String.EMPTY - log(10, "\"$text\" received") - data.value = data.value.copy(messages = data.value.messages + UARTOutputRecord(text)) - }.launchIn(scope) + setNotificationCallback(txCharacteristic).asFlow() + .flowOn(Dispatchers.IO) + .map { + val text: String = it.getStringValue(0) ?: String.EMPTY + log(10, "\"$text\" received") + val messages = data.value.messages + UARTRecord(text, UARTRecordType.OUTPUT) + messages.takeLast(50) + } + .onEach { + data.value = data.value.copy(messages = it) + }.launchIn(scope) requestMtu(517).enqueue() enableNotifications(txCharacteristic).enqueue() - setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow().onEach { - data.value = data.value.copy(batteryLevel = it.batteryLevel) - }.launchIn(scope) + setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow() + .onEach { + data.value = data.value.copy(batteryLevel = it.batteryLevel) + }.launchIn(scope) enableNotifications(batteryLevelCharacteristic).enqueue() } @@ -114,7 +122,8 @@ internal class UARTManager( rxCharacteristic?.let { val rxProperties: Int = it.properties writeRequest = rxProperties and BluetoothGattCharacteristic.PROPERTY_WRITE > 0 - writeCommand = rxProperties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE > 0 + writeCommand = + rxProperties and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE > 0 // Set the WRITE REQUEST type when the characteristic supports it. // This will allow to send long write (also if the characteristic support it). @@ -141,20 +150,25 @@ internal class UARTManager( @SuppressLint("WrongConstant") fun send(text: String) { if (rxCharacteristic == null) return - if (!TextUtils.isEmpty(text)) { - scope.launchWithCatch { - val writeType = if (useLongWrite) { - BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT - } else { - BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE - } - val request: WriteRequest = writeCharacteristic(rxCharacteristic, text.toByteArray(), writeType) - if (!useLongWrite) { - request.split() - } - request.suspend() - log(10, "\"$text\" sent") + scope.launchWithCatch { + val writeType = if (useLongWrite) { + BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT + } else { + BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE } + val request: WriteRequest = + writeCharacteristic(rxCharacteristic, text.toByteArray(), writeType) + if (!useLongWrite) { + request.split() + } + request.suspend() + data.value = data.value.copy( + messages = data.value.messages + UARTRecord( + text, + UARTRecordType.INPUT + ) + ) + log(10, "\"$text\" sent") } } diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTRepository.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTRepository.kt index 0469820d..41d58f5b 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTRepository.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTRepository.kt @@ -10,9 +10,11 @@ import no.nordicsemi.android.ble.ktx.suspend import no.nordicsemi.android.logger.ToolboxLogger import no.nordicsemi.android.logger.ToolboxLoggerFactory import no.nordicsemi.android.service.BleManagerResult -import no.nordicsemi.android.service.ConnectingResult +import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.service.ServiceManager import no.nordicsemi.android.uart.data.* +import no.nordicsemi.android.utils.EMPTY +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject import javax.inject.Singleton @@ -22,12 +24,12 @@ class UARTRepository @Inject internal constructor( private val context: Context, private val serviceManager: ServiceManager, private val configurationDataSource: ConfigurationDataSource, - private val toolboxLoggerFactory: ToolboxLoggerFactory + private val toolboxLoggerFactory: ToolboxLoggerFactory, ) { private var manager: UARTManager? = null private var logger: ToolboxLogger? = null - private val _data = MutableStateFlow>(ConnectingResult()) + private val _data = MutableStateFlow>(IdleResult()) internal val data = _data.asStateFlow() val isRunning = data.map { it.isRunning() } @@ -35,12 +37,12 @@ class UARTRepository @Inject internal constructor( val lastConfigurationName = configurationDataSource.lastConfigurationName - fun launch(device: BluetoothDevice) { + fun launch(device: DiscoveredBluetoothDevice) { serviceManager.startService(UARTService::class.java, device) } - fun start(device: BluetoothDevice, scope: CoroutineScope) { - val createdLogger = toolboxLoggerFactory.create("UART", device.address).also { + fun start(device: DiscoveredBluetoothDevice, scope: CoroutineScope) { + val createdLogger = toolboxLoggerFactory.create("UART", device.address()).also { logger = it } val manager = UARTManager(context, scope, createdLogger) @@ -60,9 +62,8 @@ class UARTRepository @Inject internal constructor( } fun runMacro(macro: UARTMacro) { - macro.command?.parseWithNewLineChar(macro.newLineChar)?.let { - manager?.send(it) - } + val command = macro.command?.parseWithNewLineChar(macro.newLineChar) + manager?.send(command ?: String.EMPTY) } fun clearItems() { @@ -77,9 +78,9 @@ class UARTRepository @Inject internal constructor( configurationDataSource.saveConfigurationName(name) } - private suspend fun UARTManager.start(device: BluetoothDevice) { + private suspend fun UARTManager.start(device: DiscoveredBluetoothDevice) { try { - connect(device) + connect(device.device) .useAutoConnect(false) .retry(3, 100) .suspend() diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt index d01a5660..3f241958 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/repository/UARTService.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import no.nordicsemi.android.service.DEVICE_DATA import no.nordicsemi.android.service.NotificationService +import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice import javax.inject.Inject @AndroidEntryPoint @@ -19,7 +20,7 @@ internal class UARTService : NotificationService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { super.onStartCommand(intent, flags, startId) - val device = intent!!.getParcelableExtra(DEVICE_DATA)!! + val device = intent!!.getParcelableExtra(DEVICE_DATA)!! repository.start(device, lifecycleScope) diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/InputSection.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/InputSection.kt new file mode 100644 index 00000000..2a2e0021 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/InputSection.kt @@ -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)) + } + } +} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/MacroSection.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/MacroSection.kt new file mode 100644 index 00000000..edd534f6 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/MacroSection.kt @@ -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)) + } + } + ) +} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/OutputSection.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/OutputSection.kt new file mode 100644 index 00000000..06ff4911 --- /dev/null +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/OutputSection.kt @@ -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, 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) +} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt index 5e922bd1..2aaaecc2 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddConfigurationDialog.kt @@ -1,21 +1,12 @@ package no.nordicsemi.android.uart.view -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import no.nordicsemi.android.material.you.TextField import no.nordicsemi.android.uart.R import no.nordicsemi.android.utils.EMPTY @@ -24,47 +15,28 @@ internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDism val name = rememberSaveable { mutableStateOf(String.EMPTY) } val isError = rememberSaveable { mutableStateOf(false) } - Dialog(onDismissRequest = { onDismiss() }) { - Surface( - color = MaterialTheme.colorScheme.background, - shape = RoundedCornerShape(10.dp), - shadowElevation = 2.dp, - ) { - Column(verticalArrangement = Arrangement.SpaceBetween) { - Text( - text = stringResource(id = R.string.uart_configuration_dialog_title), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) - - NameInput(name, isError) - - Row( - modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.End - ) { - TextButton(onClick = { onDismiss() }) { - Text(stringResource(id = R.string.uart_macro_dialog_dismiss)) - } - - Spacer(modifier = Modifier.size(16.dp)) - - TextButton(onClick = { - if (isNameValid(name.value)) { - onDismiss() - onEvent(OnAddConfiguration(name.value)) - } else { - isError.value = true - } - }) { - Text(stringResource(id = R.string.uart_macro_dialog_confirm)) - } + AlertDialog( + onDismissRequest = { onDismiss() }, + title = { Text(stringResource(id = R.string.uart_configuration_dialog_title)) }, + text = { NameInput(name, isError) }, + confirmButton = { + TextButton(onClick = { + if (isNameValid(name.value)) { + onDismiss() + onEvent(OnAddConfiguration(name.value)) + } else { + isError.value = true } + }) { + Text(stringResource(id = R.string.uart_macro_dialog_confirm)) + } + }, + dismissButton = { + TextButton(onClick = { onDismiss() }) { + Text(stringResource(id = R.string.uart_macro_dialog_dismiss)) } } - } + ) } @Composable @@ -72,14 +44,17 @@ private fun NameInput( name: MutableState, isError: MutableState ) { - Column(modifier = Modifier.padding(16.dp)) { - TextField( - text = name.value, - hint = stringResource(id = R.string.uart_configuration_hint) - ) { - isError.value = false - name.value = it - } + Column { + + OutlinedTextField( + value = name.value, + label = { Text(stringResource(id = R.string.uart_configuration_hint)) }, + singleLine = true, + onValueChange = { + isError.value = false + name.value = it + } + ) val errorText = if (isError.value) { stringResource(id = R.string.uart_name_empty) diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddMacroDialog.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddMacroDialog.kt index 3fdc6e0d..11ce0563 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddMacroDialog.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTAddMacroDialog.kt @@ -1,18 +1,12 @@ package no.nordicsemi.android.uart.view -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.GridCells -import androidx.compose.foundation.lazy.GridItemSpan -import androidx.compose.foundation.lazy.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf @@ -25,11 +19,9 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog import no.nordicsemi.android.material.you.RadioButtonGroup import no.nordicsemi.android.material.you.RadioButtonItem import no.nordicsemi.android.material.you.RadioGroupViewEntity -import no.nordicsemi.android.material.you.TextField import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.data.MacroEol import no.nordicsemi.android.uart.data.MacroIcon @@ -38,130 +30,85 @@ import no.nordicsemi.android.utils.EMPTY private const val GRID_SIZE = 5 -@OptIn(ExperimentalFoundationApi::class) @Composable internal fun UARTAddMacroDialog(macro: UARTMacro?, onEvent: (UARTViewEvent) -> Unit) { val newLineChar = rememberSaveable { mutableStateOf(macro?.newLineChar ?: MacroEol.LF) } val command = rememberSaveable { mutableStateOf(macro?.command ?: String.EMPTY) } - val isError = rememberSaveable { mutableStateOf(false) } val selectedIcon = rememberSaveable { mutableStateOf(macro?.icon ?: MacroIcon.values()[0]) } - Dialog(onDismissRequest = { onEvent(OnEditFinish) }) { - Surface( - color = MaterialTheme.colorScheme.background, - shape = RoundedCornerShape(10.dp), - shadowElevation = 0.dp, - ) { - Column { - Text( - text = stringResource(id = R.string.uart_macro_dialog_title), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - ) + AlertDialog( + onDismissRequest = { onEvent(OnEditFinish) }, + dismissButton = { + TextButton(onClick = { onEvent(OnDeleteMacro) }) { + Text(stringResource(id = R.string.uart_macro_dialog_delete)) + } + }, + confirmButton = { + TextButton(onClick = { + onEvent(OnCreateMacro(UARTMacro(selectedIcon.value, command.value, newLineChar.value))) + }) { + Text(stringResource(id = R.string.uart_macro_dialog_confirm)) + } + }, + title = { + Text( + text = stringResource(id = R.string.uart_macro_dialog_title), + style = MaterialTheme.typography.headlineSmall + ) + }, + text = { + LazyVerticalGrid( + columns = GridCells.Fixed(GRID_SIZE), + modifier = Modifier.wrapContentHeight() + ) { + item(span = { GridItemSpan(GRID_SIZE) }) { + Column { + NewLineCharSection(newLineChar.value) { newLineChar.value = it } - LazyVerticalGrid( - cells = GridCells.Fixed(GRID_SIZE), - modifier = Modifier - .padding(horizontal = 16.dp) - .wrapContentHeight() - ) { - item(span = { GridItemSpan(GRID_SIZE) }) { - Column { - NewLineCharSection(newLineChar.value) { newLineChar.value = it } + Spacer(modifier = Modifier.size(16.dp)) + } + } - Spacer(modifier = Modifier.size(16.dp)) - } + item(span = { GridItemSpan(GRID_SIZE) }) { + CommandInput(command) + } + + items(20) { item -> + val icon = MacroIcon.create(item) + val background = if (selectedIcon.value == icon) { + MaterialTheme.colorScheme.primaryContainer + } else { + Color.Transparent } - item(span = { GridItemSpan(GRID_SIZE) }) { - CommandInput(command, isError) - } - - items(20) { item -> - val icon = MacroIcon.create(item) - val background = if (selectedIcon.value == icon) { - MaterialTheme.colorScheme.primaryContainer - } else { - Color.Transparent - } - - Image( - painter = painterResource(id = icon.toResId()), - contentDescription = stringResource(id = R.string.uart_macro_icon), - colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer), - modifier = Modifier - .size(40.dp) - .clip(RoundedCornerShape(10.dp)) - .clickable { selectedIcon.value = icon } - .background(background) - ) - } - - item(span = { GridItemSpan(GRID_SIZE) }) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - TextButton(onClick = { onEvent(OnEditFinish) }) { - Text(stringResource(id = R.string.uart_macro_dialog_dismiss)) - } - - Spacer(modifier = Modifier.size(16.dp)) - - TextButton(onClick = { onEvent(OnDeleteMacro) }) { - Text(stringResource(id = R.string.uart_macro_dialog_delete)) - } - - Spacer(modifier = Modifier.size(16.dp)) - - TextButton(onClick = { - if (isCommandValid(command.value)) { - onEvent( - OnCreateMacro( - UARTMacro( - selectedIcon.value, - command.value, - newLineChar.value - ) - ) - ) - } else { - isError.value = true - } - }) { - Text(stringResource(id = R.string.uart_macro_dialog_confirm)) - } - } - } + Image( + painter = painterResource(id = icon.toResId()), + contentDescription = stringResource(id = R.string.uart_macro_icon), + colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimaryContainer), + modifier = Modifier + .size(40.dp) + .clip(RoundedCornerShape(10.dp)) + .clickable { selectedIcon.value = icon } + .background(background) + ) } } } - } + ) } @Composable -private fun CommandInput( - command: MutableState, - isError: MutableState -) { +private fun CommandInput(command: MutableState) { Column { - TextField( - text = command.value, - hint = stringResource(id = R.string.uart_macro_dialog_command) - ) { - isError.value = false - command.value = it - } - - if (isError.value) { - Text( - text = stringResource(id = R.string.uart_macro_error), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.error - ) - } + OutlinedTextField( + modifier = Modifier + .fillMaxWidth(), + value = command.value, + label = { Text(stringResource(id = R.string.uart_macro_dialog_command)) }, + onValueChange = { + command.value = it + } + ) Spacer(modifier = Modifier.size(16.dp)) } @@ -186,7 +133,3 @@ private fun NewLineCharSection(checkedItem: MacroEol, onItemClick: (MacroEol) -> } } } - -private fun isCommandValid(command: String): Boolean { - return command.isNotBlank() -} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt index a4b9259d..a42272c2 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTContentView.kt @@ -1,265 +1,40 @@ package no.nordicsemi.android.uart.view import androidx.compose.foundation.layout.* -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Delete -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material3.* import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import no.nordicsemi.android.material.you.* -import no.nordicsemi.android.theme.view.SectionTitle -import no.nordicsemi.android.uart.R -import no.nordicsemi.android.uart.data.MacroEol +import no.nordicsemi.android.material.you.Card import no.nordicsemi.android.uart.data.UARTData -import no.nordicsemi.android.uart.data.UARTOutputRecord -import no.nordicsemi.android.utils.EMPTY -import java.text.SimpleDateFormat -import java.util.* @Composable internal fun UARTContentView( state: UARTData, - viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit ) { Column( horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(16.dp) + modifier = Modifier + .padding(16.dp) + .fillMaxSize() ) { - InputSection(onEvent = onEvent) - Spacer(modifier = Modifier.size(16.dp)) - - MacroSection(viewState, onEvent) - - Spacer(modifier = Modifier.size(16.dp)) - - OutputSection(state.displayMessages, onEvent) - - Spacer(modifier = Modifier.size(16.dp)) - - Button( - onClick = { onEvent(DisconnectEvent) } + Card( + modifier = Modifier + .weight(1f) ) { - Text(text = stringResource(id = R.string.disconnect)) - } - } -} - -@Composable -private fun InputSection(onEvent: (UARTViewEvent) -> Unit) { - val text = rememberSaveable { mutableStateOf(String.EMPTY) } - val hint = stringResource(id = R.string.uart_input_hint) - val checkedItem = rememberSaveable { mutableStateOf(MacroEol.values()[0]) } - - val items = MacroEol.values().map { - RadioButtonItem(it.toDisplayString(), it == checkedItem.value) - } - val viewEntity = RadioGroupViewEntity(items) - - ScreenSection { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_input)) - - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = stringResource(id = R.string.uart_macro_dialog_eol), - style = MaterialTheme.typography.labelLarge - ) - - RadioButtonGroup(viewEntity) { - val i = items.indexOf(it) - checkedItem.value = MacroEol.values()[i] - } - } - - Spacer(modifier = Modifier.size(16.dp)) - - Row(verticalAlignment = Alignment.CenterVertically) { - Box(modifier = Modifier.weight(1f)) { - TextField(text = text.value, hint = hint) { - text.value = it - } - } - - Spacer(modifier = Modifier.size(16.dp)) - - Button( - onClick = { onEvent(OnRunInput(text.value, checkedItem.value)) }, - modifier = Modifier.padding(top = 6.dp) - ) { - Text(text = stringResource(id = R.string.uart_send)) - } - } - } - } -} - -@Composable -private fun MacroSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) { - val showAddDialog = rememberSaveable { mutableStateOf(false) } - val showDeleteDialog = rememberSaveable { mutableStateOf(false) } - - if (showAddDialog.value) { - UARTAddConfigurationDialog(onEvent) { showAddDialog.value = false } - } - - if (showDeleteDialog.value) { - DeleteConfigurationDialog(onEvent) { showDeleteDialog.value = false } - } - - ScreenSection { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_macros)) - - Spacer(modifier = Modifier.height(16.dp)) - - Row { - Box(modifier = Modifier.weight(1f)) { - UARTConfigurationPicker(viewState, onEvent) - } - - IconButton(onClick = { showAddDialog.value = true }) { - Icon(Icons.Default.Add, stringResource(id = R.string.uart_configuration_add)) - } - - viewState.selectedConfiguration?.let { - - if (!viewState.isConfigurationEdited) { - IconButton(onClick = { onEvent(OnEditConfiguration) }) { - Icon( - Icons.Default.Edit, - stringResource(id = R.string.uart_configuration_edit) - ) - } - } else { - IconButton(onClick = { onEvent(OnEditConfiguration) }) { - Icon( - painterResource(id = R.drawable.ic_pencil_off), - stringResource(id = R.string.uart_configuration_edit) - ) - } - } - - IconButton(onClick = { showDeleteDialog.value = true }) { - Icon( - Icons.Default.Delete, - stringResource(id = R.string.uart_configuration_delete) - ) - } - } - } - - viewState.selectedConfiguration?.let { - Spacer(modifier = Modifier.height(16.dp)) - - UARTMacroView(it, viewState.isConfigurationEdited, onEvent) - } - } - } -} - -@Composable -private fun DeleteConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDismiss: () -> Unit) { - AlertDialog( - onDismissRequest = onDismiss, - title = { - Text( - text = stringResource(id = R.string.uart_delete_dialog_title), - style = MaterialTheme.typography.headlineSmall - ) - }, - text = { - Text(text = stringResource(id = R.string.uart_delete_dialog_info)) - }, - confirmButton = { - Button(onClick = { - onDismiss() - onEvent(OnDeleteConfiguration) - }) { - Text(text = stringResource(id = R.string.uart_delete_dialog_confirm)) - } - }, - dismissButton = { - Button(onClick = onDismiss) { - Text(text = stringResource(id = R.string.uart_delete_dialog_cancel)) - } - } - ) -} - -@Composable -private fun OutputSection(records: List, onEvent: (UARTViewEvent) -> Unit) { - ScreenSection { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically + Column( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 16.dp, end = 16.dp) ) { - SectionTitle( - resId = R.drawable.ic_output, - title = stringResource(R.string.uart_output), - modifier = Modifier - ) - - IconButton(onClick = { onEvent(ClearOutputItems) }) { - Icon( - Icons.Default.Delete, - contentDescription = "Clear items.", - ) - } - } - - Spacer(modifier = Modifier.size(16.dp)) - - Column(modifier = Modifier.fillMaxWidth()) { - if (records.isEmpty()) { - Text(text = stringResource(id = R.string.uart_output_placeholder)) - } else { - records.forEach { - MessageItem(record = it) - - Spacer(modifier = Modifier.height(16.dp)) - } - } + OutputSection(state.displayMessages, onEvent) } } + + Spacer(modifier = Modifier.size(16.dp)) + + InputSection(onEvent = onEvent) } } - -@Composable -private fun MessageItem(record: UARTOutputRecord) { - Column { - Text( - text = record.timeToString(), - style = MaterialTheme.typography.labelMedium, - color = MaterialTheme.colorScheme.outline - ) - Spacer(modifier = Modifier.height(4.dp)) - Text( - text = record.text, - style = MaterialTheme.typography.bodyMedium - ) - } -} - -private val datFormatter = SimpleDateFormat("dd MMMM yyyy, HH:mm:ss", Locale.ENGLISH) - -private fun UARTOutputRecord.timeToString(): String { - return datFormatter.format(timestamp) -} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt index 44cd24cf..e9c3496e 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTMacroView.kt @@ -13,13 +13,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import no.nordicsemi.android.uart.R import no.nordicsemi.android.uart.data.UARTConfiguration import no.nordicsemi.android.uart.data.UARTMacro private val divider = 4.dp -private val buttonSize = 80.dp @Composable internal fun UARTMacroView( @@ -27,34 +27,42 @@ internal fun UARTMacroView( isEdited: Boolean, onEvent: (UARTViewEvent) -> Unit ) { - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - - Row { - Item(configuration, isEdited, 0, onEvent) - Spacer(modifier = Modifier.size(divider)) - Item(configuration, isEdited, 1, onEvent) - Spacer(modifier = Modifier.size(divider)) - Item(configuration, isEdited, 2, onEvent) + BoxWithConstraints { + val buttonSize = if (maxWidth < 260.dp) { + 48.dp //Minimum touch area + } else { + 80.dp } - Spacer(modifier = Modifier.size(divider)) + Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Row { - Item(configuration, isEdited, 3, onEvent) - Spacer(modifier = Modifier.size(divider)) - Item(configuration, isEdited, 4, onEvent) - Spacer(modifier = Modifier.size(divider)) - Item(configuration, isEdited, 5, onEvent) - } + Row { + Item(configuration, isEdited, 0, buttonSize, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 1, buttonSize, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 2, buttonSize, onEvent) + } - Spacer(modifier = Modifier.size(divider)) + Spacer(modifier = Modifier.size(divider)) + + Row { + Item(configuration, isEdited, 3, buttonSize, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 4, buttonSize, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 5, buttonSize, onEvent) + } - Row { - Item(configuration, isEdited, 6, onEvent) Spacer(modifier = Modifier.size(divider)) - Item(configuration, isEdited, 7, onEvent) - Spacer(modifier = Modifier.size(divider)) - Item(configuration, isEdited, 8, onEvent) + + Row { + Item(configuration, isEdited, 6, buttonSize, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 7, buttonSize, onEvent) + Spacer(modifier = Modifier.size(divider)) + Item(configuration, isEdited, 8, buttonSize, onEvent) + } } } } @@ -64,14 +72,15 @@ private fun Item( configuration: UARTConfiguration, isEdited: Boolean, position: Int, + buttonSize: Dp, onEvent: (UARTViewEvent) -> Unit ) { val macro = configuration.macros.getOrNull(position) if (macro == null) { - EmptyButton(isEdited, position, onEvent) + EmptyButton(isEdited, position, buttonSize, onEvent) } else { - MacroButton(macro, position, isEdited, onEvent) + MacroButton(macro, position, isEdited, buttonSize, onEvent) } } @@ -80,6 +89,7 @@ private fun MacroButton( macro: UARTMacro, position: Int, isEdited: Boolean, + buttonSize: Dp, onEvent: (UARTViewEvent) -> Unit ) { Image( @@ -104,6 +114,7 @@ private fun MacroButton( private fun EmptyButton( isEdited: Boolean, position: Int, + buttonSize: Dp, onEvent: (UARTViewEvent) -> Unit ) { Box( diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt index 43ef2738..d9079ea5 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTScreen.kt @@ -8,15 +8,19 @@ import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.hilt.navigation.compose.hiltViewModel +import no.nordicsemi.android.material.you.PagerView +import no.nordicsemi.android.material.you.PagerViewEntity +import no.nordicsemi.android.material.you.PagerViewItem import no.nordicsemi.android.service.* import no.nordicsemi.android.theme.view.BackIconAppBar import no.nordicsemi.android.theme.view.LoggerIconAppBar -import no.nordicsemi.ui.scanner.ui.DeviceConnectingView -import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.android.uart.R +import no.nordicsemi.android.uart.data.UARTData import no.nordicsemi.android.uart.viewmodel.UARTViewModel import no.nordicsemi.android.utils.exhaustive +import no.nordicsemi.ui.scanner.ui.DeviceConnectingView import no.nordicsemi.ui.scanner.ui.DeviceDisconnectedView +import no.nordicsemi.ui.scanner.ui.NoDeviceView import no.nordicsemi.ui.scanner.ui.Reason @Composable @@ -24,29 +28,59 @@ fun UARTScreen() { val viewModel: UARTViewModel = hiltViewModel() val state = viewModel.state.collectAsState().value - if (state.showEditDialog) { - UARTAddMacroDialog(state.selectedMacro) { viewModel.onEvent(it) } - } - Column { val navigateUp = { viewModel.onEvent(NavigateUp) } - LoggerIconAppBar(stringResource(id = R.string.uart_title), navigateUp) { - viewModel.onEvent(OpenLogger) - } + AppBar(state = state, navigateUp = navigateUp) { viewModel.onEvent(it) } - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - when (state.uartManagerState) { - NoDeviceState -> NoDeviceView() - is WorkingState -> when (state.uartManagerState.result) { - is ConnectingResult -> DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } - is DisconnectedResult -> DeviceDisconnectedView(Reason.USER, navigateUp) - is LinkLossResult -> DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) - is MissingServiceResult -> DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) - is UnknownErrorResult -> DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) - is SuccessResult -> UARTContentView(state.uartManagerState.result.data, state) { viewModel.onEvent(it) } - } - }.exhaustive + when (state.uartManagerState) { + NoDeviceState -> NoDeviceView() + is WorkingState -> when (state.uartManagerState.result) { + is IdleResult, + is ConnectingResult -> Scroll { DeviceConnectingView { viewModel.onEvent(DisconnectEvent) } } + is DisconnectedResult -> Scroll { DeviceDisconnectedView(Reason.USER, navigateUp) } + is LinkLossResult -> Scroll { DeviceDisconnectedView(Reason.LINK_LOSS, navigateUp) } + is MissingServiceResult -> Scroll { DeviceDisconnectedView(Reason.MISSING_SERVICE, navigateUp) } + is UnknownErrorResult -> Scroll { DeviceDisconnectedView(Reason.UNKNOWN, navigateUp) } + is SuccessResult -> SuccessScreen(state.uartManagerState.result.data, state, viewModel) + } + }.exhaustive + } +} + +@Composable +private fun AppBar(state: UARTViewState, navigateUp: () -> Unit, onEvent: (UARTViewEvent) -> Unit) { + val toolbarName = (state.uartManagerState as? WorkingState)?.let { + (it.result as? SuccessResult)?.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() + } +} diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt index 9e20b79d..a41e6821 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTState.kt @@ -10,7 +10,8 @@ internal data class UARTViewState( val selectedConfigurationName: String? = null, val isConfigurationEdited: Boolean = false, val configurations: List = emptyList(), - val uartManagerState: HTSManagerState = NoDeviceState + val uartManagerState: HTSManagerState = NoDeviceState, + val isInputVisible: Boolean = true ) { val showEditDialog: Boolean = editedPosition != null @@ -25,6 +26,8 @@ internal data class UARTViewState( internal sealed class HTSManagerState -internal data class WorkingState(val result: BleManagerResult) : HTSManagerState() +internal data class WorkingState( + val result: BleManagerResult +) : HTSManagerState() internal object NoDeviceState : HTSManagerState() diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt index 0fabc6e2..1c1a2ff0 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/view/UARTViewEvent.kt @@ -23,3 +23,5 @@ internal object DisconnectEvent : UARTViewEvent() internal object NavigateUp : UARTViewEvent() internal object OpenLogger : UARTViewEvent() + +internal object MacroInputSwitchClick : UARTViewEvent() diff --git a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt index 3bfae508..690dd5a6 100644 --- a/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt +++ b/profile_uart/src/main/java/no/nordicsemi/android/uart/viewmodel/UARTViewModel.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import no.nordicsemi.android.navigation.* +import no.nordicsemi.android.service.IdleResult import no.nordicsemi.android.uart.data.UARTConfiguration import no.nordicsemi.android.uart.data.UARTMacro import no.nordicsemi.android.uart.data.UARTPersistentDataSource @@ -36,6 +37,9 @@ internal class UARTViewModel @Inject constructor( } repository.data.onEach { + if (it is IdleResult) { + return@onEach + } _state.value = _state.value.copy(uartManagerState = WorkingState(it)) }.launchIn(viewModelScope) @@ -63,7 +67,7 @@ internal class UARTViewModel @Inject constructor( private fun handleArgs(args: DestinationResult) { when (args) { is CancelDestinationResult -> navigationManager.navigateUp() - is SuccessDestinationResult -> repository.launch(args.getDevice().device) + is SuccessDestinationResult -> repository.launch(args.getDevice()) }.exhaustive } @@ -83,9 +87,14 @@ internal class UARTViewModel @Inject constructor( ClearOutputItems -> repository.clearItems() OpenLogger -> repository.openLogger() is OnRunInput -> repository.sendText(event.text, event.newLineChar) + MacroInputSwitchClick -> onMacroInputSwitch() }.exhaustive } + private fun onMacroInputSwitch() { + _state.value = _state.value.copy(isInputVisible = !state.value.isInputVisible) + } + private fun onEditConfiguration() { val isEdited = _state.value.isConfigurationEdited _state.value = _state.value.copy(isConfigurationEdited = !isEdited) diff --git a/profile_uart/src/main/res/drawable/ic_macro.xml b/profile_uart/src/main/res/drawable/ic_macro.xml new file mode 100644 index 00000000..b51a7b3e --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_macro.xml @@ -0,0 +1,10 @@ + + + diff --git a/profile_uart/src/main/res/drawable/ic_sync_down.xml b/profile_uart/src/main/res/drawable/ic_sync_down.xml new file mode 100644 index 00000000..43315763 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_sync_down.xml @@ -0,0 +1,11 @@ + + + + diff --git a/profile_uart/src/main/res/drawable/ic_sync_down_off.xml b/profile_uart/src/main/res/drawable/ic_sync_down_off.xml new file mode 100644 index 00000000..2b431aa6 --- /dev/null +++ b/profile_uart/src/main/res/drawable/ic_sync_down_off.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/profile_uart/src/main/res/values/strings.xml b/profile_uart/src/main/res/values/strings.xml index 00bcf127..227945d9 100644 --- a/profile_uart/src/main/res/values/strings.xml +++ b/profile_uart/src/main/res/values/strings.xml @@ -1,10 +1,10 @@ - + UART Please define a macro to send command to the device. - Add selected configuration. + Add Delete selected configuration. Edit selected configuration. @@ -52,4 +52,13 @@ Are you sure that you want to delete this configuration? Your data will be irretrievably lost. Confirm Cancel + + Click to switch between text input and macro input. + Clear items. + Click to constantly scroll view to the latest available log. + --> %s + <-- %s + + Settings + Go to settings screen. diff --git a/settings.gradle b/settings.gradle index 68009a0f..0ac38731 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,8 +9,8 @@ dependencyResolutionManagement { versionCatalogs { libs { - library('nordic-ble-common', 'no.nordicsemi.android:ble-common:2.4.0') - library('nordic-ble-ktx', 'no.nordicsemi.android:ble-ktx:2.4.0') + library('nordic-ble-common', 'no.nordicsemi.android:ble-common:2.4.1') + library('nordic-ble-ktx', 'no.nordicsemi.android:ble-ktx:2.4.1') library('nordic-scanner', 'no.nordicsemi.android.support.v18:scanner:1.6.0') library('nordic-log', 'no.nordicsemi.android:log:2.3.0') @@ -21,7 +21,7 @@ dependencyResolutionManagement { library('nordic-theme', 'no.nordicsemi.android.common', 'theme').versionRef('commonlibraries') library('localbroadcastmanager', 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0') - library('material', 'com.google.android.material:material:1.6.0-alpha02') + library('material', 'com.google.android.material:material:1.6.0-rc01') version('lifecycle', '2.4.1') library('lifecycle-activity', 'androidx.lifecycle', 'lifecycle-runtime-ktx').versionRef('lifecycle') @@ -37,9 +37,9 @@ dependencyResolutionManagement { library('datastore-protobuf', 'com.google.protobuf:protobuf-javalite:3.18.0') bundle('datastore', ['datastore-core', 'datastore-prefs', 'datastore-protobuf']) - version('compose', '1.1.0') + version('compose', '1.2.0-alpha07') library('compose-ui', 'androidx.compose.ui', 'ui').versionRef('compose') - library('compose-material', 'androidx.compose.material3:material3:1.0.0-alpha05') + library('compose-material', 'androidx.compose.material3:material3:1.0.0-alpha09') library('compose-tooling-preview', 'androidx.compose.ui', 'ui-tooling-preview').versionRef('compose') library('compose-navigation', 'androidx.navigation:navigation-compose:2.4.1') bundle('compose', ['compose-ui', 'compose-material', 'compose-tooling-preview', 'compose-navigation'])