Add UART views.

This commit is contained in:
Sylwester Zieliński
2022-01-03 14:58:18 +01:00
parent 8d9e2cc22e
commit 8545a50995
15 changed files with 377 additions and 4 deletions

View File

@@ -260,7 +260,6 @@ fun HomeView(callback: (NavDestination) -> Unit) {
R.string.uart_module_full
) { callback(NavDestination.UART) }
}
Spacer(modifier = Modifier.width(16.dp))
}
}
}

View File

@@ -4,5 +4,6 @@ import no.nordicsemi.android.utils.EMPTY
internal data class UARTData(
val text: String = String.EMPTY,
val macros: List<UARTMacro> = emptyList(),
val batteryLevel: Int = 0
)

View File

@@ -1,6 +1,9 @@
package no.nordicsemi.android.uart.data
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton
@@ -11,6 +14,20 @@ internal class UARTDataHolder @Inject constructor() {
private val _data = MutableStateFlow(UARTData())
val data = _data.asStateFlow()
private val _command = MutableSharedFlow<UARTServiceCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
val command = _command.asSharedFlow()
fun addNewMacro(macro: UARTMacro) {
_data.tryEmit(_data.value.copy(macros = _data.value.macros + macro))
}
fun deleteMacro(macro: UARTMacro) {
val macros = _data.value.macros.toMutableList().apply {
remove(macro)
}
_data.tryEmit(_data.value.copy(macros = macros))
}
fun emitNewMessage(message: String) {
_data.tryEmit(_data.value.copy(text = message))
}
@@ -18,4 +35,8 @@ internal class UARTDataHolder @Inject constructor() {
fun emitNewBatteryLevel(batteryLevel: Int) {
_data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
}
fun sendNewCommand(command: UARTServiceCommand) {
_command.tryEmit(command)
}
}

View File

@@ -0,0 +1,6 @@
package no.nordicsemi.android.uart.data
data class UARTMacro(
val alias: String,
val command: String,
)

View File

@@ -0,0 +1,3 @@
package no.nordicsemi.android.uart.data
data class UARTServiceCommand(val command: String)

View File

@@ -1,6 +1,9 @@
package no.nordicsemi.android.uart.repository
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.uart.data.UARTDataHolder
import javax.inject.Inject
@@ -12,4 +15,12 @@ internal class UARTService : ForegroundBleService() {
lateinit var dataHolder: UARTDataHolder
override val manager: UARTManager by lazy { UARTManager(this, dataHolder) }
override fun onCreate() {
super.onCreate()
dataHolder.command.onEach {
manager.send(it.command)
}.launchIn(lifecycleScope)
}
}

View File

@@ -0,0 +1,61 @@
package no.nordicsemi.android.uart.view
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.material.you.TextField
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTMacro
import no.nordicsemi.android.utils.EMPTY
@Composable
internal fun UARTAddMacroDialog(onDismiss: () -> Unit, onEvent: (UARTViewEvent) -> Unit) {
val alias = remember { mutableStateOf(String.EMPTY) }
val command = remember { mutableStateOf(String.EMPTY) }
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(text = stringResource(id = R.string.uart_macro_dialog_title))
},
text = {
Column {
TextField(text = alias.value, hint = stringResource(id = R.string.uart_macro_dialog_alias)) {
alias.value = it
}
Spacer(modifier = Modifier.padding(16.dp))
TextField(text = command.value, hint = stringResource(id = R.string.uart_macro_dialog_command)) {
command.value = it
}
}
},
confirmButton = {
TextButton(
onClick = {
onDismiss()
onEvent(OnCreateMacro(UARTMacro(alias.value, command.value)))
}
) {
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
}
},
dismissButton = {
TextButton(
onClick = { onDismiss() }
) {
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
}
}
)
}

View File

@@ -1,4 +1,95 @@
package no.nordicsemi.android.uart.view
class UARTContentView {
}
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.theme.view.ScreenSection
import no.nordicsemi.android.theme.view.SectionTitle
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTData
@Composable
internal fun UARTContentView(state: UARTData, onEvent: (UARTViewEvent) -> Unit) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(horizontal = 16.dp)
) {
Spacer(modifier = Modifier.height(16.dp))
InputSection(state, onEvent)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { onEvent(OnDisconnectButtonClick) }
) {
Text(text = stringResource(id = R.string.disconnect))
}
}
}
@Composable
private fun InputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) {
val showSearchDialog = remember { mutableStateOf(false) }
if (showSearchDialog.value) {
UARTAddMacroDialog(onDismiss = { showSearchDialog.value = false }, onEvent = onEvent)
}
ScreenSection {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
SectionTitle(resId = R.drawable.ic_input, title = stringResource(R.string.uart_input))
Spacer(modifier = Modifier.height(16.dp))
state.macros.forEach {
MacroItem(macro = it, onEvent = onEvent)
Spacer(modifier = Modifier.height(16.dp))
}
if (state.macros.isEmpty()) {
Text(
text = stringResource(id = R.string.uart_no_macros_info),
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(16.dp))
}
Button(
onClick = { showSearchDialog.value = true }
) {
Text(text = stringResource(id = R.string.uart_add_macro))
}
}
}
}
@Composable
private fun OutputSection(state: UARTData, onEvent: (UARTViewEvent) -> Unit) {
ScreenSection {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
SectionTitle(resId = R.drawable.ic_output, title = stringResource(R.string.uart_output))
Spacer(modifier = Modifier.height(16.dp))
}
}
}

View File

@@ -1,7 +1,54 @@
package no.nordicsemi.android.uart.view
import android.content.Intent
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import no.nordicsemi.android.theme.view.BackIconAppBar
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTData
import no.nordicsemi.android.uart.repository.UARTService
import no.nordicsemi.android.uart.viewmodel.UARTViewModel
import no.nordicsemi.android.utils.isServiceRunning
@Composable
fun UARTScreen(finishAction: () -> Unit) {
val viewModel: UARTViewModel = hiltViewModel()
val state = viewModel.state.collectAsState().value
val isScreenActive = viewModel.isActive.collectAsState().value
val context = LocalContext.current
LaunchedEffect(isScreenActive) {
if (!isScreenActive) {
finishAction()
}
if (context.isServiceRunning(UARTService::class.java.name)) {
val intent = Intent(context, UARTService::class.java)
context.stopService(intent)
}
}
LaunchedEffect("start-service") {
if (!context.isServiceRunning(UARTService::class.java.name)) {
val intent = Intent(context, UARTService::class.java)
context.startService(intent)
}
}
UARTView(state) { viewModel.onEvent(it) }
}
@Composable
private fun UARTView(state: UARTData, onEvent: (UARTViewEvent) -> Unit) {
Column {
BackIconAppBar(stringResource(id = R.string.uart_title)) {
onEvent(OnDisconnectButtonClick)
}
UARTContentView(state) { onEvent(it) }
}
}

View File

@@ -0,0 +1,12 @@
package no.nordicsemi.android.uart.view
import no.nordicsemi.android.uart.data.UARTMacro
internal sealed class UARTViewEvent
internal data class OnCreateMacro(val macro: UARTMacro) : UARTViewEvent()
internal data class OnDeleteMacro(val macro: UARTMacro) : UARTViewEvent()
internal data class OnRunMacro(val macro: UARTMacro) : UARTViewEvent()
internal object OnDisconnectButtonClick : UARTViewEvent()

View File

@@ -0,0 +1,60 @@
package no.nordicsemi.android.uart.view
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import no.nordicsemi.android.material.you.Card
import no.nordicsemi.android.uart.R
import no.nordicsemi.android.uart.data.UARTMacro
@Composable
internal fun MacroItem(macro: UARTMacro, onEvent: (UARTViewEvent) -> Unit) {
Card(backgroundColor = MaterialTheme.colorScheme.primaryContainer) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(16.dp)
) {
Icon(
imageVector = Icons.Default.PlayArrow,
contentDescription = stringResource(id = R.string.uart_run_macro_description),
modifier = Modifier
.size(40.dp)
.clickable { onEvent(OnRunMacro(macro)) }
)
Spacer(modifier = Modifier.padding(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = macro.alias,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
Text(
text = stringResource(id = R.string.uart_command_field, macro.command),
style = MaterialTheme.typography.bodyMedium
)
}
Spacer(modifier = Modifier.padding(16.dp))
Icon(
imageVector = Icons.Default.Delete,
contentDescription = stringResource(id = R.string.uart_delete_macro_description),
modifier = Modifier
.size(32.dp)
.clickable { onEvent(OnDeleteMacro(macro)) }
)
}
}
}

View File

@@ -1,6 +1,26 @@
package no.nordicsemi.android.uart.viewmodel
import dagger.hilt.android.lifecycle.HiltViewModel
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
import no.nordicsemi.android.uart.data.UARTDataHolder
import no.nordicsemi.android.uart.data.UARTServiceCommand
import no.nordicsemi.android.uart.view.*
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
class UARTViewModel : CloseableViewModel() {
@HiltViewModel
internal class UARTViewModel @Inject constructor(
private val dataHolder: UARTDataHolder
) : CloseableViewModel() {
val state = dataHolder.data
fun onEvent(event: UARTViewEvent) {
when (event) {
is OnCreateMacro -> dataHolder.addNewMacro(event.macro)
is OnDeleteMacro -> dataHolder.deleteMacro(event.macro)
OnDisconnectButtonClick -> finish()
is OnRunMacro -> dataHolder.sendNewCommand(UARTServiceCommand(event.macro.command))
}.exhaustive
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0,0 0,4 6V9H6V6H18V18H6V15H4V18A2,2 0,0 0,6 20H18A2,2 0,0 0,20 18Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0,0 1,17 6V9H15V6H3V18H15V15H17V18A2,2 0,0 1,15 20H3A2,2 0,0 1,1 18Z"/>
</vector>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="uart_title">UART</string>
<string name="uart_no_macros_info">Please define a macro to send command to the device.</string>
<string name="uart_input">Macros</string>
<string name="uart_output">Output</string>
<string name="uart_command_field">Command: %s</string>
<string name="uart_run_macro_description">Run macro</string>
<string name="uart_delete_macro_description">Delete macro</string>
<string name="uart_add_macro">Add macro</string>
<string name="uart_output_info">Here will be displayed read value from GATT characteristic.</string>
<string name="uart_macro_dialog_title">Add macro</string>
<string name="uart_macro_dialog_alias">Alias</string>
<string name="uart_macro_dialog_command">Command</string>
<string name="uart_macro_dialog_confirm">Confirm</string>
<string name="uart_macro_dialog_dismiss">Dismiss</string>
</resources>