mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-20 07:54:20 +01:00
Make UART working
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppTheme.SplashScreen">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
@@ -2,13 +2,7 @@ package no.nordicsemi.android.theme.view.dialog
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -22,7 +16,6 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import no.nordicsemi.android.material.you.Card
|
||||
import no.nordicsemi.android.theme.R
|
||||
@@ -51,10 +44,12 @@ fun StringListView(config: StringListDialogConfig) {
|
||||
) {
|
||||
Text(
|
||||
text = config.title ?: stringResource(id = R.string.dialog).toAnnotatedString(),
|
||||
fontSize = 20.sp
|
||||
style = MaterialTheme.typography.headlineMedium
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight(0.8f)
|
||||
@@ -62,29 +57,26 @@ fun StringListView(config: StringListDialogConfig) {
|
||||
) {
|
||||
|
||||
config.items.forEachIndexed { i, entry ->
|
||||
Column(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.clickable { config.onResult(ItemSelectedResult(i)) }
|
||||
.padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row {
|
||||
config.leftIcon?.let {
|
||||
Image(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
painter = painterResource(it),
|
||||
contentDescription = "Content image",
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = entry,
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
config.leftIcon?.let {
|
||||
Image(
|
||||
modifier = Modifier.padding(horizontal = 4.dp),
|
||||
painter = painterResource(it),
|
||||
contentDescription = "Content image",
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = entry,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
enum class MacroEol(val eolIndex: Int) {
|
||||
enum class MacroEol(val index: Int) {
|
||||
LF(0),
|
||||
CR(1),
|
||||
CR_LF(2);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
enum class MacroIcon(val index: Int) {
|
||||
enum class MacroIcon(public val index: Int) {
|
||||
LEFT(0),
|
||||
UP(1),
|
||||
RIGHT(2),
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
import no.nordicsemi.android.uart.db.XmlCommand
|
||||
|
||||
private const val MACROS_SIZES = 9
|
||||
|
||||
data class UARTConfiguration(
|
||||
val id: Int?,
|
||||
val name: String,
|
||||
val macros: List<UARTMacro?> = List<UARTMacro?>(9) { null }
|
||||
) {
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
import no.nordicsemi.android.utils.EMPTY
|
||||
|
||||
internal data class UARTData(
|
||||
val text: String = String.EMPTY,
|
||||
val messages: List<UARTOutputRecord> = emptyList(),
|
||||
val batteryLevel: Int? = null,
|
||||
) {
|
||||
|
||||
val displayMessages = messages.reversed().take(10)
|
||||
}
|
||||
|
||||
internal data class UARTOutputRecord(
|
||||
val text: String,
|
||||
val timestamp: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
data class UARTMacro(val icon: MacroIcon, val command: String, val newLineChar: MacroEol)
|
||||
data class UARTMacro(val icon: MacroIcon, val command: String?, val newLineChar: MacroEol)
|
||||
|
||||
@@ -75,10 +75,10 @@ internal class UARTManager(
|
||||
override fun initialize() {
|
||||
setNotificationCallback(txCharacteristic).asFlow().onEach {
|
||||
val text: String = it.getStringValue(0) ?: String.EMPTY
|
||||
data.value = data.value.copy(text = text)
|
||||
}
|
||||
data.value = data.value.copy(messages = data.value.messages + UARTOutputRecord(text))
|
||||
}.launchIn(scope)
|
||||
|
||||
requestMtu(260).enqueue()
|
||||
requestMtu(517).enqueue()
|
||||
enableNotifications(txCharacteristic).enqueue()
|
||||
|
||||
setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow<BatteryLevelResponse>().onEach {
|
||||
@@ -88,7 +88,7 @@ internal class UARTManager(
|
||||
}
|
||||
|
||||
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
|
||||
val service: BluetoothGattService = gatt.getService(UART_SERVICE_UUID)
|
||||
val service: BluetoothGattService? = gatt.getService(UART_SERVICE_UUID)
|
||||
if (service != null) {
|
||||
rxCharacteristic = service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID)
|
||||
txCharacteristic = service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID)
|
||||
@@ -99,16 +99,13 @@ 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).
|
||||
// In case there is no WRITE REQUEST property, this manager will divide texts
|
||||
// longer then MTU-3 bytes into up to MTU-3 bytes chunks.
|
||||
if (writeRequest) {
|
||||
it.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
|
||||
} else {
|
||||
if (!writeRequest) {
|
||||
useLongWrite = false
|
||||
}
|
||||
}
|
||||
@@ -130,7 +127,12 @@ internal class UARTManager(
|
||||
if (rxCharacteristic == null) return
|
||||
if (!TextUtils.isEmpty(text)) {
|
||||
scope.launchWithCatch {
|
||||
val request: WriteRequest = writeCharacteristic(rxCharacteristic, text.toByteArray(), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
|
||||
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()
|
||||
}
|
||||
@@ -139,6 +141,10 @@ internal class UARTManager(
|
||||
}
|
||||
}
|
||||
|
||||
fun clearItems() {
|
||||
data.value = data.value.copy(messages = emptyList())
|
||||
}
|
||||
|
||||
override fun getGattCallback(): BleManagerGattCallback {
|
||||
return UARTManagerGattCallback()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package no.nordicsemi.android.uart.data
|
||||
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import no.nordicsemi.android.uart.db.*
|
||||
@@ -26,17 +25,17 @@ internal class UARTPersistentDataSource @Inject constructor(
|
||||
val serializer: Serializer = Persister(format)
|
||||
val configuration = serializer.read(XmlConfiguration::class.java, xml)
|
||||
|
||||
UARTConfiguration(configuration.name ?: "Unknown", createMacro(configuration.commands))
|
||||
UARTConfiguration(it._id, configuration.name ?: "Unknown", createMacro(configuration.commands))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMacro(macros: Array<XmlCommand?>): List<UARTMacro?> {
|
||||
private fun createMacro(macros: Array<XmlMacro?>): List<UARTMacro?> {
|
||||
return macros.map {
|
||||
if (it == null) {
|
||||
null
|
||||
} else {
|
||||
val icon = MacroIcon.create(it.iconIndex)
|
||||
it.command?.let { c -> UARTMacro(icon, c, it.eol) }
|
||||
UARTMacro(icon, it.command, it.eol)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,13 +45,29 @@ internal class UARTPersistentDataSource @Inject constructor(
|
||||
val strategy: Strategy = VisitorStrategy(CommentVisitor())
|
||||
val serializer: Serializer = Persister(strategy, format)
|
||||
val writer = StringWriter()
|
||||
serializer.write(configuration, writer)
|
||||
serializer.write(configuration.toXmlConfiguration(), writer)
|
||||
val xml = writer.toString()
|
||||
|
||||
configurationsDao.insert(Configuration(0, configuration.name, xml, 0))
|
||||
configurationsDao.insert(Configuration(configuration.id, configuration.name, xml, 0))
|
||||
}
|
||||
|
||||
suspend fun deleteConfiguration(configuration: UARTConfiguration) {
|
||||
configurationsDao.delete(configuration.name)
|
||||
}
|
||||
|
||||
private fun UARTConfiguration.toXmlConfiguration(): XmlConfiguration {
|
||||
val xmlConfiguration = XmlConfiguration()
|
||||
xmlConfiguration.name = name
|
||||
val commands = macros.map { macro ->
|
||||
macro?.let {
|
||||
XmlMacro().apply {
|
||||
setEol(it.newLineChar.index)
|
||||
command = it.command
|
||||
iconIndex = it.icon.index
|
||||
}
|
||||
}
|
||||
}.toTypedArray()
|
||||
xmlConfiguration.commands = commands
|
||||
return xmlConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ internal class CommentVisitor : Visitor {
|
||||
}
|
||||
|
||||
override fun write(type: Type, node: NodeMap<OutputNode>) {
|
||||
if (type.type == Array<XmlCommand>::class.java) {
|
||||
if (type.type == Array<XmlMacro>::class.java) {
|
||||
val element = node.node
|
||||
val builder =
|
||||
StringBuilder("A configuration must have 9 commands, one for each button.\n Possible icons are:")
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Nordic Semiconductor
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.uart.db
|
||||
|
||||
import no.nordicsemi.android.uart.data.MacroEol
|
||||
import no.nordicsemi.android.uart.data.MacroIcon
|
||||
import org.simpleframework.xml.Attribute
|
||||
import org.simpleframework.xml.Root
|
||||
import org.simpleframework.xml.Text
|
||||
|
||||
@Root
|
||||
internal class XmlCommand {
|
||||
|
||||
/**
|
||||
* Returns the command that will be sent to UART device.
|
||||
* @return the command
|
||||
*/
|
||||
/**
|
||||
* Sets the command.
|
||||
* @param command the command that will be sent to UART device
|
||||
*/
|
||||
@Text(required = false)
|
||||
var command: String? = null
|
||||
/**
|
||||
* Returns whether the icon is active.
|
||||
* @return true if it's active
|
||||
*/
|
||||
/**
|
||||
* Sets whether the command is active.
|
||||
* @param active true to make it active
|
||||
*/
|
||||
@Attribute(required = false)
|
||||
var isActive = false
|
||||
|
||||
/**
|
||||
* Returns the new line type.
|
||||
* @return end of line terminator
|
||||
*/
|
||||
@Attribute(required = false)
|
||||
var eol = MacroEol.LF
|
||||
private set
|
||||
|
||||
@Attribute(required = false)
|
||||
private var icon = MacroIcon.LEFT
|
||||
|
||||
/**
|
||||
* Sets the new line type.
|
||||
* @param eol end of line terminator
|
||||
*/
|
||||
fun setEol(eol: Int) {
|
||||
this.eol = MacroEol.values()[eol]
|
||||
}
|
||||
/**
|
||||
* Returns the icon index.
|
||||
* @return the icon index
|
||||
*/
|
||||
/**
|
||||
* Sets the icon index.
|
||||
* @param index index of the icon.
|
||||
*/
|
||||
var iconIndex: Int
|
||||
get() = icon.index
|
||||
set(index) {
|
||||
icon = MacroIcon.values()[index]
|
||||
}
|
||||
}
|
||||
@@ -19,30 +19,57 @@
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package no.nordicsemi.android.uart.db
|
||||
|
||||
import org.simpleframework.xml.Attribute
|
||||
import org.simpleframework.xml.ElementArray
|
||||
import org.simpleframework.xml.Root
|
||||
import org.simpleframework.xml.core.PersistenceException
|
||||
import org.simpleframework.xml.core.Validate
|
||||
package no.nordicsemi.android.uart.db;
|
||||
|
||||
import org.simpleframework.xml.Attribute;
|
||||
import org.simpleframework.xml.ElementArray;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.simpleframework.xml.core.PersistenceException;
|
||||
import org.simpleframework.xml.core.Validate;
|
||||
|
||||
@Root
|
||||
internal class XmlConfiguration @JvmOverloads constructor(
|
||||
@field:Attribute(required = false, empty = "Unnamed")
|
||||
var name: String? = "",
|
||||
public class XmlConfiguration {
|
||||
public static final int COMMANDS_COUNT = 9;
|
||||
|
||||
@field:ElementArray
|
||||
var commands: Array<XmlCommand?> = arrayOfNulls(COMMANDS_COUNT)
|
||||
) {
|
||||
@Attribute(required = false, empty = "Unnamed")
|
||||
private String name;
|
||||
|
||||
@Validate
|
||||
@Throws(PersistenceException::class)
|
||||
private fun validate() {
|
||||
if (commands.size != COMMANDS_COUNT) throw PersistenceException("There must be always $COMMANDS_COUNT commands in a configuration.")
|
||||
}
|
||||
@ElementArray
|
||||
private XmlMacro[] commands = new XmlMacro[COMMANDS_COUNT];
|
||||
|
||||
companion object {
|
||||
const val COMMANDS_COUNT = 9
|
||||
}
|
||||
/**
|
||||
* Returns the field name
|
||||
*
|
||||
* @return optional name
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name to specified value
|
||||
* @param name the new name
|
||||
*/
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of commands. There is always 9 of them.
|
||||
* @return the commands array
|
||||
*/
|
||||
public XmlMacro[] getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public void setCommands(XmlMacro[] commands) {
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
@Validate
|
||||
private void validate() throws PersistenceException{
|
||||
if (commands == null || commands.length != COMMANDS_COUNT)
|
||||
throw new PersistenceException("There must be always " + COMMANDS_COUNT + " commands in a configuration.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Nordic Semiconductor
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package no.nordicsemi.android.uart.db;
|
||||
|
||||
import org.simpleframework.xml.Attribute;
|
||||
import org.simpleframework.xml.Root;
|
||||
import org.simpleframework.xml.Text;
|
||||
|
||||
import no.nordicsemi.android.uart.data.MacroEol;
|
||||
import no.nordicsemi.android.uart.data.MacroIcon;
|
||||
|
||||
@Root
|
||||
public class XmlMacro {
|
||||
|
||||
@Text(required = false)
|
||||
private String command;
|
||||
|
||||
@Attribute(required = false)
|
||||
private boolean active = false;
|
||||
|
||||
@Attribute(required = false)
|
||||
private MacroEol eol = MacroEol.LF;
|
||||
|
||||
@Attribute(required = false)
|
||||
private MacroIcon icon = MacroIcon.LEFT;
|
||||
|
||||
/**
|
||||
* Sets the command.
|
||||
* @param command the command that will be sent to UART device
|
||||
*/
|
||||
public void setCommand(final String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the command is active.
|
||||
* @param active true to make it active
|
||||
*/
|
||||
public void setActive(final boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new line type.
|
||||
* @param eol end of line terminator
|
||||
*/
|
||||
public void setEol(final int eol) {
|
||||
this.eol = MacroEol.values()[eol];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the icon index.
|
||||
* @param index index of the icon.
|
||||
*/
|
||||
public void setIconIndex(final int index) {
|
||||
this.icon = MacroIcon.values()[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the command that will be sent to UART device.
|
||||
* @return the command
|
||||
*/
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the icon is active.
|
||||
* @return true if it's active
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the new line type.
|
||||
* @return end of line terminator
|
||||
*/
|
||||
public MacroEol getEol() {
|
||||
return eol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the icon index.
|
||||
* @return the icon index
|
||||
*/
|
||||
public int getIconIndex() {
|
||||
return icon.getIndex();
|
||||
}
|
||||
/**
|
||||
* Returns the EOL index.
|
||||
* @return the EOL index
|
||||
*/
|
||||
public int getEolIndex() {
|
||||
return eol.getIndex();
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,13 @@ class UARTRepository @Inject constructor(
|
||||
}
|
||||
|
||||
fun runMacro(macro: UARTMacro) {
|
||||
manager?.send(macro.command.parseWithNewLineChar(macro.newLineChar))
|
||||
macro.command?.parseWithNewLineChar(macro.newLineChar)?.let {
|
||||
manager?.send(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearItems() {
|
||||
manager?.clearItems()
|
||||
}
|
||||
|
||||
private suspend fun UARTManager.start(device: BluetoothDevice) {
|
||||
|
||||
@@ -1,53 +1,74 @@
|
||||
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.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
|
||||
|
||||
@Composable
|
||||
internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit) {
|
||||
val name = remember { mutableStateOf(String.EMPTY) }
|
||||
val isError = remember { mutableStateOf(false) }
|
||||
internal fun UARTAddConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDismiss: () -> Unit) {
|
||||
val name = rememberSaveable { mutableStateOf(String.EMPTY) }
|
||||
val isError = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Column {
|
||||
NameInput(name, isError)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
Dialog(onDismissRequest = { onDismiss() }) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
shadowElevation = 2.dp,
|
||||
) {
|
||||
TextButton(onClick = { onEvent(OnEditFinish) }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
|
||||
}
|
||||
Column(verticalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_configuration_dialog_title),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
// Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
TextButton(onClick = {
|
||||
if (isNameValid(name.value)) {
|
||||
onEvent(OnEditFinish)
|
||||
onEvent(OnAddConfiguration(name.value))
|
||||
} else {
|
||||
isError.value = true
|
||||
NameInput(name, isError)
|
||||
|
||||
// Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = { onDismiss() }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
TextButton(onClick = {
|
||||
if (isNameValid(name.value)) {
|
||||
onDismiss()
|
||||
onEvent(OnAddConfiguration(name.value))
|
||||
} else {
|
||||
isError.value = true
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -55,23 +76,27 @@ private fun NameInput(
|
||||
name: MutableState<String>,
|
||||
isError: MutableState<Boolean>
|
||||
) {
|
||||
Column {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
TextField(
|
||||
text = name.value,
|
||||
hint = stringResource(id = R.string.uart_macro_dialog_command)
|
||||
hint = stringResource(id = R.string.uart_configuration_hint)
|
||||
) {
|
||||
isError.value = false
|
||||
name.value = it
|
||||
}
|
||||
|
||||
if (isError.value) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_name_empty),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
val errorText = if (isError.value) {
|
||||
stringResource(id = R.string.uart_name_empty)
|
||||
} else {
|
||||
String.EMPTY
|
||||
}
|
||||
|
||||
Text(
|
||||
text = errorText,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ 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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -38,11 +39,11 @@ private const val GRID_SIZE = 5
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun UARTAddMacroDialog(onEvent: (UARTViewEvent) -> Unit) {
|
||||
val newLineChar = remember { mutableStateOf(MacroEol.LF) }
|
||||
val command = remember { mutableStateOf(String.EMPTY) }
|
||||
val isError = remember { mutableStateOf(false) }
|
||||
val selectedIcon = remember { mutableStateOf(MacroIcon.values()[0]) }
|
||||
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(
|
||||
@@ -95,36 +96,43 @@ internal fun UARTAddMacroDialog(onEvent: (UARTViewEvent) -> Unit) {
|
||||
.background(background)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = { onEvent(OnEditFinish) }) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_dismiss))
|
||||
}
|
||||
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))
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
TextButton(onClick = {
|
||||
if (isCommandValid(command.value)) {
|
||||
onEvent(
|
||||
OnCreateMacro(
|
||||
UARTMacro(
|
||||
selectedIcon.value,
|
||||
command.value,
|
||||
newLineChar.value
|
||||
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
|
||||
} else {
|
||||
isError.value = true
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(id = R.string.uart_macro_dialog_confirm))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ private fun createConfig(entries: List<String>, onResult: (StringListDialogResul
|
||||
return StringListDialogConfig(
|
||||
title = stringResource(id = R.string.uart_configuration_picker_dialog).toAnnotatedString(),
|
||||
items = entries,
|
||||
onResult = onResult
|
||||
onResult = onResult,
|
||||
leftIcon = R.drawable.ic_uart_settings
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
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.Add
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.theme.view.ScreenSection
|
||||
import no.nordicsemi.android.theme.view.SectionTitle
|
||||
import no.nordicsemi.android.uart.R
|
||||
import no.nordicsemi.android.uart.data.UARTData
|
||||
import no.nordicsemi.android.uart.data.UARTOutputRecord
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Composable
|
||||
internal fun UARTContentView(state: UARTData, viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
|
||||
internal fun UARTContentView(
|
||||
state: UARTData,
|
||||
viewState: UARTViewState,
|
||||
onEvent: (UARTViewEvent) -> Unit
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
OutputSection(state.text)
|
||||
InputSection(viewState, onEvent)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
InputSection(viewState, onEvent)
|
||||
OutputSection(state.displayMessages, onEvent)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
@@ -45,10 +52,15 @@ internal fun UARTContentView(state: UARTData, viewState: UARTViewState, onEvent:
|
||||
|
||||
@Composable
|
||||
private fun InputSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> Unit) {
|
||||
val showDialog = remember { mutableStateOf(false) }
|
||||
val showAddDialog = rememberSaveable { mutableStateOf(false) }
|
||||
val showDeleteDialog = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (showDialog.value) {
|
||||
UARTAddConfigurationDialog(onEvent)
|
||||
if (showAddDialog.value) {
|
||||
UARTAddConfigurationDialog(onEvent) { showAddDialog.value = false }
|
||||
}
|
||||
|
||||
if (showDeleteDialog.value) {
|
||||
DeleteConfigurationDialog(onEvent) { showDeleteDialog.value = false }
|
||||
}
|
||||
|
||||
ScreenSection {
|
||||
@@ -64,18 +76,33 @@ private fun InputSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> U
|
||||
UARTConfigurationPicker(viewState, onEvent)
|
||||
}
|
||||
|
||||
IconButton(onClick = { showDialog.value = true }) {
|
||||
IconButton(onClick = { showAddDialog.value = true }) {
|
||||
Icon(Icons.Default.Add, stringResource(id = R.string.uart_configuration_add))
|
||||
}
|
||||
|
||||
viewState.selectedConfiguration?.let {
|
||||
|
||||
IconButton(onClick = { onEvent(OnEditConfiguration) }) {
|
||||
Icon(Icons.Default.Edit, stringResource(id = R.string.uart_configuration_edit))
|
||||
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 = { onEvent(OnDeleteConfiguration) }) {
|
||||
Icon(Icons.Default.Delete, stringResource(id = R.string.uart_configuration_delete))
|
||||
IconButton(onClick = { showDeleteDialog.value = true }) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
stringResource(id = R.string.uart_configuration_delete)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,16 +117,85 @@ private fun InputSection(viewState: UARTViewState, onEvent: (UARTViewEvent) -> U
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OutputSection(text: String) {
|
||||
private fun DeleteConfigurationDialog(onEvent: (UARTViewEvent) -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_delete_dialog_title),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_info))
|
||||
},
|
||||
confirmButton = {
|
||||
Button(onClick = {
|
||||
onDismiss()
|
||||
onEvent(OnDeleteConfiguration)
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
Button(onClick = onDismiss) {
|
||||
Text(text = stringResource(id = R.string.uart_delete_dialog_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OutputSection(records: List<UARTOutputRecord>, onEvent: (UARTViewEvent) -> Unit) {
|
||||
ScreenSection {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
SectionTitle(resId = R.drawable.ic_output, title = stringResource(R.string.uart_output))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
SectionTitle(resId = R.drawable.ic_output, title = stringResource(R.string.uart_output), modifier = Modifier)
|
||||
|
||||
Icon(Icons.Default.Delete, contentDescription = "Clear items.", modifier = Modifier.clickable { onEvent(ClearOutputItems) })
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(text = text.ifBlank { stringResource(id = R.string.uart_output_placeholder) })
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
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
|
||||
@@ -84,6 +85,7 @@ private fun MacroButton(
|
||||
Image(
|
||||
painter = painterResource(id = macro.icon.toResId()),
|
||||
contentDescription = stringResource(id = R.string.uart_macro_icon),
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary),
|
||||
modifier = Modifier
|
||||
.size(buttonSize)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
@@ -104,15 +106,17 @@ private fun EmptyButton(
|
||||
position: Int,
|
||||
onEvent: (UARTViewEvent) -> Unit
|
||||
) {
|
||||
Box(modifier = Modifier
|
||||
.size(buttonSize)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.clickable {
|
||||
if (isEdited) {
|
||||
onEvent(OnEditMacro(position))
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(buttonSize)
|
||||
.clip(RoundedCornerShape(10.dp))
|
||||
.clickable {
|
||||
if (isEdited) {
|
||||
onEvent(OnEditMacro(position))
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(getBackground(isEdited)))
|
||||
.background(getBackground(isEdited))
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -120,6 +124,6 @@ private fun getBackground(isEdited: Boolean): Color {
|
||||
return if (!isEdited) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.secondary
|
||||
MaterialTheme.colorScheme.tertiary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ fun UARTScreen() {
|
||||
val state = viewModel.state.collectAsState().value
|
||||
|
||||
if (state.showEditDialog) {
|
||||
UARTAddMacroDialog { viewModel.onEvent(it) }
|
||||
UARTAddMacroDialog(state.selectedMacro) { viewModel.onEvent(it) }
|
||||
}
|
||||
|
||||
Column {
|
||||
|
||||
@@ -3,17 +3,24 @@ package no.nordicsemi.android.uart.view
|
||||
import no.nordicsemi.android.service.BleManagerResult
|
||||
import no.nordicsemi.android.uart.data.UARTConfiguration
|
||||
import no.nordicsemi.android.uart.data.UARTData
|
||||
import no.nordicsemi.android.uart.data.UARTMacro
|
||||
|
||||
internal data class UARTViewState(
|
||||
val editedPosition: Int? = null,
|
||||
val selectedConfigurationIndex: Int? = null,
|
||||
val selectedConfigurationName: String? = null,
|
||||
val isConfigurationEdited: Boolean = false,
|
||||
val configurations: List<UARTConfiguration> = emptyList(),
|
||||
val uartManagerState: HTSManagerState = NoDeviceState
|
||||
) {
|
||||
val showEditDialog: Boolean = editedPosition != null
|
||||
|
||||
val selectedConfiguration: UARTConfiguration? = selectedConfigurationIndex?.let { configurations[it] }
|
||||
val selectedConfiguration: UARTConfiguration? = configurations.find { selectedConfigurationName == it.name }
|
||||
|
||||
val selectedMacro: UARTMacro? = selectedConfiguration?.let { configuration ->
|
||||
editedPosition?.let {
|
||||
configuration.macros.getOrNull(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HTSManagerState
|
||||
|
||||
@@ -7,7 +7,7 @@ internal sealed class UARTViewEvent
|
||||
|
||||
internal data class OnEditMacro(val position: Int) : UARTViewEvent()
|
||||
internal data class OnCreateMacro(val macro: UARTMacro) : UARTViewEvent()
|
||||
internal data class OnDeleteMacro(val macro: UARTMacro) : UARTViewEvent()
|
||||
internal object OnDeleteMacro : UARTViewEvent()
|
||||
internal object OnEditFinish : UARTViewEvent()
|
||||
|
||||
internal data class OnConfigurationSelected(val configuration: UARTConfiguration) : UARTViewEvent()
|
||||
@@ -16,6 +16,7 @@ internal object OnEditConfiguration : UARTViewEvent()
|
||||
internal object OnDeleteConfiguration : UARTViewEvent()
|
||||
internal data class OnRunMacro(val macro: UARTMacro) : UARTViewEvent()
|
||||
|
||||
internal object ClearOutputItems : UARTViewEvent()
|
||||
internal object DisconnectEvent : UARTViewEvent()
|
||||
|
||||
internal object NavigateUp : UARTViewEvent()
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package no.nordicsemi.android.uart.view
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.draw.clip
|
||||
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,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.PlayArrow,
|
||||
contentDescription = stringResource(id = R.string.uart_run_macro_description),
|
||||
modifier = Modifier
|
||||
.size(70.dp)
|
||||
.padding(8.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable { onEvent(OnRunMacro(macro)) }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f).padding(vertical = 8.dp)) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.uart_macro_dialog_selected_eol, macro.newLineChar.toDisplayString()),
|
||||
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.size(16.dp))
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(id = R.string.uart_delete_macro_description),
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.padding(end = 8.dp)
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable { onEvent(OnDeleteMacro(macro)) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ internal class UARTViewModel @Inject constructor(
|
||||
fun onEvent(event: UARTViewEvent) {
|
||||
when (event) {
|
||||
is OnCreateMacro -> addNewMacro(event.macro)
|
||||
is OnDeleteMacro -> deleteMacro(event.macro)
|
||||
OnDeleteMacro -> deleteMacro()
|
||||
DisconnectEvent -> disconnect()
|
||||
is OnRunMacro -> repository.runMacro(event.macro)
|
||||
NavigateUp -> navigationManager.navigateUp()
|
||||
@@ -74,16 +74,19 @@ internal class UARTViewModel @Inject constructor(
|
||||
is OnAddConfiguration -> onAddConfiguration(event)
|
||||
OnDeleteConfiguration -> deleteConfiguration()
|
||||
OnEditConfiguration -> onEditConfiguration()
|
||||
ClearOutputItems -> repository.clearItems()
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun onEditConfiguration() {
|
||||
_state.value = _state.value.copy(isConfigurationEdited = true)
|
||||
val isEdited = _state.value.isConfigurationEdited
|
||||
_state.value = _state.value.copy(isConfigurationEdited = !isEdited)
|
||||
}
|
||||
|
||||
private fun onAddConfiguration(event: OnAddConfiguration) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
dataSource.saveConfiguration(UARTConfiguration(event.name))
|
||||
dataSource.saveConfiguration(UARTConfiguration(null, event.name))
|
||||
_state.value = _state.value.copy(selectedConfigurationName = event.name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +99,7 @@ internal class UARTViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun onConfigurationSelected(event: OnConfigurationSelected) {
|
||||
_state.value = _state.value.copy(selectedConfigurationIndex = _state.value.configurations.indexOf(event.configuration))
|
||||
_state.value = _state.value.copy(selectedConfigurationName = event.configuration.name)
|
||||
}
|
||||
|
||||
private fun addNewMacro(macro: UARTMacro) {
|
||||
@@ -106,13 +109,6 @@ internal class UARTViewModel @Inject constructor(
|
||||
set(_state.value.editedPosition!!, macro)
|
||||
}
|
||||
val newConf = it.copy(macros = macros)
|
||||
val newConfs = _state.value.configurations.map {
|
||||
if (it.name == newConf.name) {
|
||||
newConf
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
dataSource.saveConfiguration(newConf)
|
||||
_state.value = _state.value.copy(editedPosition = null)
|
||||
}
|
||||
@@ -127,9 +123,16 @@ internal class UARTViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteMacro(macro: UARTMacro) {
|
||||
private fun deleteMacro() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// dataSource.deleteMacro(macro)
|
||||
_state.value.selectedConfiguration?.let {
|
||||
val macros = it.macros.toMutableList().apply {
|
||||
set(_state.value.editedPosition!!, null)
|
||||
}
|
||||
val newConf = it.copy(macros = macros)
|
||||
dataSource.saveConfiguration(newConf)
|
||||
_state.value = _state.value.copy(editedPosition = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
9
profile_uart/src/main/res/drawable/ic_pencil_off.xml
Normal file
9
profile_uart/src/main/res/drawable/ic_pencil_off.xml
Normal 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="M18.66,2C18.4,2 18.16,2.09 17.97,2.28L16.13,4.13L19.88,7.88L21.72,6.03C22.11,5.64 22.11,5 21.72,4.63L19.38,2.28C19.18,2.09 18.91,2 18.66,2M3.28,4L2,5.28L8.5,11.75L4,16.25V20H7.75L12.25,15.5L18.72,22L20,20.72L13.5,14.25L9.75,10.5L3.28,4M15.06,5.19L11.03,9.22L14.78,12.97L18.81,8.94L15.06,5.19Z"/>
|
||||
</vector>
|
||||
@@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,7V13H13V15H9V17H13A2,2 0,0 0,15 15V13A2,2 0,0 0,13 11H11V9H15V7H9Z"/>
|
||||
android:pathData="M11,7A2,2 0,0 0,9 9V15A2,2 0,0 0,11 17H13A2,2 0,0 0,15 15V13A2,2 0,0 0,13 11H11V9H15V7H11M11,13H13V15H11V13Z"/>
|
||||
</vector>
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,7V13H13V15H9V17H13A2,2 0,0 0,15 15V13A2,2 0,0 0,13 11H11V9H15V7H9Z"/>
|
||||
android:pathData="M11,17L15,9V7H9V9H13L9,17"/>
|
||||
</vector>
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,7V13H13V15H9V17H13A2,2 0,0 0,15 15V13A2,2 0,0 0,13 11H11V9H15V7H9Z"/>
|
||||
android:pathData="M11,13H13V15H11M11,9H13V11H11M11,17H13A2,2 0,0 0,15 15V13.5A1.5,1.5 0,0 0,13.5 12A1.5,1.5 0,0 0,15 10.5V9C15,7.89 14.1,7 13,7H11A2,2 0,0 0,9 9V10.5A1.5,1.5 0,0 0,10.5 12A1.5,1.5 0,0 0,9 13.5V15C9,16.11 9.9,17 11,17"/>
|
||||
</vector>
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,7V13H13V15H9V17H13A2,2 0,0 0,15 15V13A2,2 0,0 0,13 11H11V9H15V7H9Z"/>
|
||||
android:pathData="M13,17A2,2 0,0 0,15 15V9A2,2 0,0 0,13 7H11A2,2 0,0 0,9 9V11A2,2 0,0 0,11 13H13V15H9V17H13M13,11H11V9H13V11Z"/>
|
||||
</vector>
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
<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_configuration_dialog_title">Add configuration</string>
|
||||
<string name="uart_configuration_hint">Configuration</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>
|
||||
<string name="uart_macro_dialog_delete">Delete</string>
|
||||
|
||||
<string name="uart_macro_dialog_eol">EOL:</string>
|
||||
<string name="uart_macro_dialog_selected_eol">EOL: %s</string>
|
||||
@@ -41,4 +44,9 @@
|
||||
<string name="uart_name_empty">Provided name cannot be empty.</string>
|
||||
|
||||
<string name="uart_macro_icon">Icon representing defined command.</string>
|
||||
|
||||
<string name="uart_delete_dialog_title">Delete configuration?</string>
|
||||
<string name="uart_delete_dialog_info">Are you sure that you want to delete this configuration? Your data will be irretrievably lost.</string>
|
||||
<string name="uart_delete_dialog_confirm">Confirm</string>
|
||||
<string name="uart_delete_dialog_cancel">Cancel</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user