From bdd7f1d919348dd2300fb7206ade69f8551b54e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=20Hu=CC=88sers?= Date: Wed, 20 Mar 2024 10:54:00 +0100 Subject: [PATCH 1/7] chore: Update AGP + Compose versions --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dc73e24b..1dd68354 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] activityKtx = "1.8.2" -androidGradlePlugin = "8.3.0" # Check with detekt table first: https://detekt.dev/docs/introduction/compatibility/ +androidGradlePlugin = "8.3.1" # Check with detekt table first: https://detekt.dev/docs/introduction/compatibility/ detekt = "1.23.5" datastore = "1.0.0" constraintlayoutCompose = "1.0.1" @@ -11,7 +11,7 @@ gson = "2.10.1" kotlin = "1.9.22" kotlinpoet = "1.16.0" kotlinxSerializationJson = "1.6.1" -runtimeLivedata = "1.6.2" +runtimeLivedata = "1.6.3" symbolProcessingApi = "1.9.22-1.0.17" tomcatAnnotations = "6.0.53" ktlint = "0.0" # Maintained inside ktlint.gradle.kts @@ -23,8 +23,8 @@ kotest = "5.7.2" mockk = "1.13.7" androidxLifecycle = "2.7.0" kotlinxCoroutines = "1.7.3" -kotlinCompilerExtension = "1.5.9" -composeBom = "2024.02.01" +kotlinCompilerExtension = "1.5.10" +composeBom = "2024.02.02" jvmTarget = "17" [libraries] From 3a677073f388a08c43d8cb640e8b72bdac9c2901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=20Hu=CC=88sers?= Date: Wed, 20 Mar 2024 10:57:15 +0100 Subject: [PATCH 2/7] chore: Extract VssNodesView + VssPathsView from DataBrokerView --- .../databroker/JavaDataBrokerEngine.java | 2 +- .../kuksa/testapp/KuksaDataBrokerActivity.kt | 2 +- .../databroker/KotlinDataBrokerEngine.kt | 2 +- .../DataBrokerConnectorFactory.kt | 2 +- .../testapp/databroker/view/DataBrokerView.kt | 214 +---------------- .../connection/DataBrokerConnectionView.kt | 2 - .../vssnodes/view/DataBrokerVssNodesView.kt | 118 ++++++++++ .../viewmodel/VssNodesViewModel.kt | 4 +- .../vsspaths/view/DataBrokerVssPathsView.kt | 215 ++++++++++++++++++ 9 files changed, 344 insertions(+), 217 deletions(-) rename app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/{connection => factory}/DataBrokerConnectorFactory.kt (98%) create mode 100644 app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt rename app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/{ => vssnodes}/viewmodel/VssNodesViewModel.kt (93%) create mode 100644 app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vsspaths/view/DataBrokerVssPathsView.kt diff --git a/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java b/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java index 0c17f17b..0ff2cfc6 100644 --- a/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java +++ b/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java @@ -36,7 +36,7 @@ import org.eclipse.kuksa.coroutine.CoroutineCallback; import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse; import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse; -import org.eclipse.kuksa.testapp.databroker.connection.DataBrokerConnectorFactory; +import org.eclipse.kuksa.testapp.databroker.factory.DataBrokerConnectorFactory; import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo; import org.eclipse.kuksa.vsscore.model.VssNode; diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt index 83040f81..cec13026 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt @@ -57,7 +57,7 @@ import org.eclipse.kuksa.testapp.databroker.viewmodel.OutputEntry import org.eclipse.kuksa.testapp.databroker.viewmodel.OutputViewModel import org.eclipse.kuksa.testapp.databroker.viewmodel.TopAppBarViewModel import org.eclipse.kuksa.testapp.databroker.viewmodel.VSSPathsViewModel -import org.eclipse.kuksa.testapp.databroker.viewmodel.VssNodesViewModel +import org.eclipse.kuksa.testapp.databroker.vssnodes.viewmodel.VssNodesViewModel import org.eclipse.kuksa.testapp.extension.TAG import org.eclipse.kuksa.testapp.preferences.ConnectionInfoRepository import org.eclipse.kuksa.testapp.ui.theme.KuksaAppAndroidTheme diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt index 6791eef2..abb5a042 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt @@ -36,7 +36,7 @@ import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest import org.eclipse.kuksa.coroutine.CoroutineCallback import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse -import org.eclipse.kuksa.testapp.databroker.connection.DataBrokerConnectorFactory +import org.eclipse.kuksa.testapp.databroker.factory.DataBrokerConnectorFactory import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo import org.eclipse.kuksa.vsscore.model.VssNode diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/connection/DataBrokerConnectorFactory.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/factory/DataBrokerConnectorFactory.kt similarity index 98% rename from app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/connection/DataBrokerConnectorFactory.kt rename to app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/factory/DataBrokerConnectorFactory.kt index e30239bb..a1c49644 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/connection/DataBrokerConnectorFactory.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/factory/DataBrokerConnectorFactory.kt @@ -17,7 +17,7 @@ * */ -package org.eclipse.kuksa.testapp.databroker.connection +package org.eclipse.kuksa.testapp.databroker.factory import android.content.Context import android.net.Uri diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt index 11bc75e4..a9cd5630 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerView.kt @@ -76,21 +76,20 @@ import kotlinx.coroutines.launch import org.eclipse.kuksa.proto.v1.Types.Datapoint.ValueCase import org.eclipse.kuksa.testapp.R import org.eclipse.kuksa.testapp.databroker.view.connection.DataBrokerConnectionView -import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionAdapter import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionTextView import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel import org.eclipse.kuksa.testapp.databroker.viewmodel.OutputViewModel import org.eclipse.kuksa.testapp.databroker.viewmodel.TopAppBarViewModel import org.eclipse.kuksa.testapp.databroker.viewmodel.TopAppBarViewModel.DataBrokerMode import org.eclipse.kuksa.testapp.databroker.viewmodel.VSSPathsViewModel -import org.eclipse.kuksa.testapp.databroker.viewmodel.VssNodesViewModel +import org.eclipse.kuksa.testapp.databroker.vssnodes.view.DataBrokerVssNodesView +import org.eclipse.kuksa.testapp.databroker.vssnodes.viewmodel.VssNodesViewModel +import org.eclipse.kuksa.testapp.databroker.vsspaths.view.DataBrokerVssPathsView import org.eclipse.kuksa.testapp.extension.compose.Headline import org.eclipse.kuksa.testapp.extension.compose.OverflowMenu import org.eclipse.kuksa.testapp.extension.compose.SimpleExposedDropdownMenuBox import org.eclipse.kuksa.testapp.preferences.ConnectionInfoRepository import org.eclipse.kuksa.testapp.ui.theme.KuksaAppAndroidTheme -import org.eclipse.kuksa.vss.VssVehicle -import org.eclipse.kuksa.vsscore.model.VssNode import java.time.format.DateTimeFormatter val SettingsMenuPadding = 16.dp @@ -125,8 +124,8 @@ fun DataBrokerView( val dataBrokerMode = topAppBarViewModel.dataBrokerMode if (connectionViewModel.isConnected) { when (dataBrokerMode) { - DataBrokerMode.VSS_PATH -> DataBrokerProperties(vssPathsViewModel) - DataBrokerMode.VSS_FILE -> DataBrokerVssNodes(vssNodesViewModel) + DataBrokerMode.VSS_PATH -> DataBrokerVssPathsView(vssPathsViewModel) + DataBrokerMode.VSS_FILE -> DataBrokerVssNodesView(vssNodesViewModel) } } Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) @@ -232,209 +231,6 @@ private fun ConnectionStatusIcon( } } -@Composable -fun DataBrokerVssNodes(viewModel: VssNodesViewModel) { - Column { - Headline(name = "Generated VSS Nodes") - - val adapter = object : SuggestionAdapter { - override fun toString(item: VssNode): String { - return item.vssPath - } - } - - SuggestionTextView( - value = "Vehicle", - suggestions = viewModel.vssNodes, - adapter = adapter, - onItemSelected = { - val vssNode = it ?: VssVehicle() - viewModel.updateNode(vssNode) - }, - modifier = Modifier - .fillMaxWidth() - .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), - ) - Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - ) { - Button( - onClick = { - viewModel.onGetNode(viewModel.node) - }, - modifier = Modifier.requiredWidth(80.dp), - ) { - Text(text = "Get") - } - if (viewModel.isSubscribed) { - Button(onClick = { - viewModel.subscribedNodes.remove(viewModel.node) - viewModel.onUnsubscribeNode(viewModel.node) - }) { - Text(text = "Unsubscribe") - } - } else { - Button(onClick = { - viewModel.subscribedNodes.add(viewModel.node) - viewModel.onSubscribeNode(viewModel.node) - }) { - Text(text = "Subscribe") - } - } - } - } -} - -@Composable -fun DataBrokerProperties(viewModel: VSSPathsViewModel) { - val dataBrokerProperty = viewModel.dataBrokerProperty - var expanded by remember { mutableStateOf(false) } - - Column { - Headline(name = "VSS Paths") - SuggestionTextView( - suggestions = viewModel.suggestions, - value = dataBrokerProperty.vssPath, - onValueChanged = { - val newVssProperties = dataBrokerProperty.copy( - vssPath = it, - valueType = ValueCase.VALUE_NOT_SET, - ) - viewModel.updateDataBrokerProperty(newVssProperties) - }, - label = { - Text(text = "VSS Path") - }, - singleLine = true, - modifier = Modifier - .fillMaxWidth() - .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), - ) - Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) - Row { - TextField( - value = "${dataBrokerProperty.valueType}", - onValueChange = {}, - label = { - Text("Value Type") - }, - readOnly = true, - enabled = false, - trailingIcon = { - IconButton(onClick = { expanded = !expanded }) { - Icon( - modifier = Modifier.size(23.dp), - imageVector = Icons.Default.ArrowDropDown, - contentDescription = "More", - ) - } - }, - colors = TextFieldDefaults.colors( - disabledTextColor = Color.Black, - disabledLabelColor = Color.Black, - disabledTrailingIconColor = Color.Black, - ), - modifier = Modifier - .fillMaxWidth() - .padding(start = DefaultEdgePadding, end = DefaultEdgePadding) - .clickable { - expanded = true - }, - ) - Box(modifier = Modifier.requiredHeight(23.dp)) { - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - modifier = Modifier.height(400.dp), - ) { - viewModel.valueTypes.forEach { - DropdownMenuItem( - text = { - Text(it.toString()) - }, - onClick = { - expanded = false - - val newVssProperties = dataBrokerProperty.copy(valueType = it) - viewModel.updateDataBrokerProperty(newVssProperties) - }, - ) - } - } - } - } - Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) - Row { - TextField( - value = dataBrokerProperty.value, - onValueChange = { - val newVssProperties = dataBrokerProperty.copy(value = it) - viewModel.updateDataBrokerProperty(newVssProperties) - }, - modifier = Modifier - .padding(start = DefaultEdgePadding) - .weight(1f), - singleLine = true, - label = { - Text(text = "Value") - }, - ) - Spacer(modifier = Modifier.padding(start = DefaultElementPadding, end = DefaultElementPadding)) - SimpleExposedDropdownMenuBox( - Modifier - .weight(2f) - .padding(end = DefaultEdgePadding), - label = "Field Type", - list = viewModel.fieldTypes, - onValueChange = { - val newVssProperties = dataBrokerProperty.copy(fieldTypes = setOf(it)) - viewModel.updateDataBrokerProperty(newVssProperties) - }, - ) - } - Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly, - ) { - Button( - onClick = { - viewModel.onGetProperty(dataBrokerProperty) - }, - modifier = Modifier.requiredWidth(80.dp), - ) { - Text(text = "Get") - } - Button( - enabled = dataBrokerProperty.valueType != ValueCase.VALUE_NOT_SET, - onClick = { - viewModel.onSetProperty(dataBrokerProperty, viewModel.datapoint) - }, - modifier = Modifier.requiredWidth(80.dp), - ) { - Text(text = "Set") - } - if (viewModel.isSubscribed) { - Button(onClick = { - viewModel.subscribedProperties.remove(dataBrokerProperty) - viewModel.onUnsubscribeProperty(dataBrokerProperty) - }) { - Text(text = "Unsubscribe") - } - } else { - Button(onClick = { - viewModel.subscribedProperties.add(dataBrokerProperty) - viewModel.onSubscribeProperty(dataBrokerProperty) - }) { - Text(text = "Subscribe") - } - } - } - } -} - @Composable fun DataBrokerOutput(viewModel: OutputViewModel, modifier: Modifier = Modifier) { val scope = rememberCoroutineScope() diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/DataBrokerConnectionView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/DataBrokerConnectionView.kt index fcdc428d..ccd8356e 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/DataBrokerConnectionView.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/connection/DataBrokerConnectionView.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController @@ -59,7 +58,6 @@ import org.eclipse.kuksa.testapp.extension.compose.Headline import org.eclipse.kuksa.testapp.extension.compose.RememberCountdown import org.eclipse.kuksa.testapp.preferences.ConnectionInfoRepository -@OptIn(ExperimentalComposeUiApi::class) @Composable fun DataBrokerConnectionView(viewModel: ConnectionViewModel) { val keyboardController = LocalSoftwareKeyboardController.current diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt new file mode 100644 index 00000000..eee277f6 --- /dev/null +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.kuksa.testapp.databroker.vssnodes.view + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.eclipse.kuksa.testapp.databroker.view.DefaultEdgePadding +import org.eclipse.kuksa.testapp.databroker.view.DefaultElementPadding +import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionAdapter +import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionTextView +import org.eclipse.kuksa.testapp.databroker.vssnodes.viewmodel.VssNodesViewModel +import org.eclipse.kuksa.testapp.extension.compose.Headline +import org.eclipse.kuksa.testapp.ui.theme.KuksaAppAndroidTheme +import org.eclipse.kuksa.vss.VssVehicle +import org.eclipse.kuksa.vsscore.model.VssNode +import org.eclipse.kuksa.vsscore.model.VssSignal + +@Composable +fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { + Column { + Headline(name = "Generated VSS Nodes") + + val isUpdatePossible = viewModel.node is VssSignal<*> + val adapter = object : SuggestionAdapter { + override fun toString(item: VssNode): String { + return item.vssPath + } + } + + SuggestionTextView( + value = "Vehicle", + suggestions = viewModel.vssNodes, + adapter = adapter, + onItemSelected = { + val vssNode = it ?: VssVehicle() + viewModel.updateNode(vssNode) + }, + modifier = Modifier + .fillMaxWidth() + .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), + ) + Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Button( + onClick = { + viewModel.onGetNode(viewModel.node) + }, + modifier = Modifier.requiredWidth(80.dp), + ) { + Text(text = "Get") + } + Button( + onClick = { + viewModel.onUpdateNode(viewModel.node) + }, + enabled = isUpdatePossible, + modifier = Modifier.requiredWidth(100.dp), + ) { + Text(text = "Update") + } + if (viewModel.isSubscribed) { + Button(onClick = { + viewModel.subscribedNodes.remove(viewModel.node) + viewModel.onUnsubscribeNode(viewModel.node) + }) { + Text(text = "Unsubscribe") + } + } else { + Button(onClick = { + viewModel.subscribedNodes.add(viewModel.node) + viewModel.onSubscribeNode(viewModel.node) + }) { + Text(text = "Subscribe") + } + } + } + } +} + diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/viewmodel/VssNodesViewModel.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/viewmodel/VssNodesViewModel.kt similarity index 93% rename from app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/viewmodel/VssNodesViewModel.kt rename to app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/viewmodel/VssNodesViewModel.kt index 1f1cb3da..0a71195a 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/viewmodel/VssNodesViewModel.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/viewmodel/VssNodesViewModel.kt @@ -14,10 +14,9 @@ * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.eclipse.kuksa.testapp.databroker.viewmodel +package org.eclipse.kuksa.testapp.databroker.vssnodes.viewmodel import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -31,6 +30,7 @@ import org.eclipse.kuksa.vsscore.model.heritage class VssNodesViewModel : ViewModel() { var onGetNode: (node: VssNode) -> Unit = { } + var onUpdateNode: (node: VssNode) -> Unit = { } var onSubscribeNode: (node: VssNode) -> Unit = { } var onUnsubscribeNode: (node: VssNode) -> Unit = { } diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vsspaths/view/DataBrokerVssPathsView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vsspaths/view/DataBrokerVssPathsView.kt new file mode 100644 index 00000000..3f89ec3c --- /dev/null +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vsspaths/view/DataBrokerVssPathsView.kt @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.kuksa.testapp.databroker.vsspaths.view + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.eclipse.kuksa.proto.v1.Types +import org.eclipse.kuksa.testapp.databroker.view.DefaultEdgePadding +import org.eclipse.kuksa.testapp.databroker.view.DefaultElementPadding +import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionTextView +import org.eclipse.kuksa.testapp.databroker.viewmodel.VSSPathsViewModel +import org.eclipse.kuksa.testapp.extension.compose.Headline +import org.eclipse.kuksa.testapp.extension.compose.SimpleExposedDropdownMenuBox +import org.eclipse.kuksa.testapp.ui.theme.KuksaAppAndroidTheme + +@Composable +fun DataBrokerVssPathsView(viewModel: VSSPathsViewModel) { + val dataBrokerProperty = viewModel.dataBrokerProperty + var expanded by remember { mutableStateOf(false) } + + Column { + Headline(name = "VSS Paths") + SuggestionTextView( + suggestions = viewModel.suggestions, + value = dataBrokerProperty.vssPath, + onValueChanged = { + val newVssProperties = dataBrokerProperty.copy( + vssPath = it, + valueType = Types.Datapoint.ValueCase.VALUE_NOT_SET, + ) + viewModel.updateDataBrokerProperty(newVssProperties) + }, + label = { + Text(text = "VSS Path") + }, + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), + ) + Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + Row { + TextField( + value = "${dataBrokerProperty.valueType}", + onValueChange = {}, + label = { + Text("Value Type") + }, + readOnly = true, + enabled = false, + trailingIcon = { + IconButton(onClick = { expanded = !expanded }) { + Icon( + modifier = Modifier.size(23.dp), + imageVector = Icons.Default.ArrowDropDown, + contentDescription = "More", + ) + } + }, + colors = TextFieldDefaults.colors( + disabledTextColor = Color.Black, + disabledLabelColor = Color.Black, + disabledTrailingIconColor = Color.Black, + ), + modifier = Modifier + .fillMaxWidth() + .padding(start = DefaultEdgePadding, end = DefaultEdgePadding) + .clickable { + expanded = true + }, + ) + Box(modifier = Modifier.requiredHeight(23.dp)) { + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier.height(400.dp), + ) { + viewModel.valueTypes.forEach { + DropdownMenuItem( + text = { + Text(it.toString()) + }, + onClick = { + expanded = false + + val newVssProperties = dataBrokerProperty.copy(valueType = it) + viewModel.updateDataBrokerProperty(newVssProperties) + }, + ) + } + } + } + } + Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + Row { + TextField( + value = dataBrokerProperty.value, + onValueChange = { + val newVssProperties = dataBrokerProperty.copy(value = it) + viewModel.updateDataBrokerProperty(newVssProperties) + }, + modifier = Modifier + .padding(start = DefaultEdgePadding) + .weight(1f), + singleLine = true, + label = { + Text(text = "Value") + }, + ) + Spacer(modifier = Modifier.padding(start = DefaultElementPadding, end = DefaultElementPadding)) + SimpleExposedDropdownMenuBox( + Modifier + .weight(2f) + .padding(end = DefaultEdgePadding), + label = "Field Type", + list = viewModel.fieldTypes, + onValueChange = { + val newVssProperties = dataBrokerProperty.copy(fieldTypes = setOf(it)) + viewModel.updateDataBrokerProperty(newVssProperties) + }, + ) + } + Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + Button( + onClick = { + viewModel.onGetProperty(dataBrokerProperty) + }, + modifier = Modifier.requiredWidth(80.dp), + ) { + Text(text = "Get") + } + Button( + enabled = dataBrokerProperty.valueType != Types.Datapoint.ValueCase.VALUE_NOT_SET, + onClick = { + viewModel.onSetProperty(dataBrokerProperty, viewModel.datapoint) + }, + modifier = Modifier.requiredWidth(80.dp), + ) { + Text(text = "Set") + } + if (viewModel.isSubscribed) { + Button(onClick = { + viewModel.subscribedProperties.remove(dataBrokerProperty) + viewModel.onUnsubscribeProperty(dataBrokerProperty) + }) { + Text(text = "Unsubscribe") + } + } else { + Button(onClick = { + viewModel.subscribedProperties.add(dataBrokerProperty) + viewModel.onSubscribeProperty(dataBrokerProperty) + }) { + Text(text = "Subscribe") + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun Preview() { + KuksaAppAndroidTheme { + DataBrokerVssPathsView(VSSPathsViewModel()) + } +} From 44bb3a5cad9dd7e6fabeb96b424acb0d739f29a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=20Hu=CC=88sers?= Date: Thu, 21 Mar 2024 14:32:47 +0100 Subject: [PATCH 3/7] feature: Add possibility to test the update of generated VSS Models - The value is now updated in the test view when a GET request is successful. - A new collapsable surface is now available inside the "Generated VSS Nodes" view. It shows most property values for the VssNode / VssSignal. The "value" property can also be changed manually and afterwards updated to the DataBroker close #105 --- .../databroker/JavaDataBrokerEngine.java | 14 ++ .../kuksa/testapp/KuksaDataBrokerActivity.kt | 42 +++- .../testapp/databroker/DataBrokerEngine.kt | 11 +- .../databroker/KotlinDataBrokerEngine.kt | 16 ++ .../vssnodes/view/DataBrokerVssNodesView.kt | 196 +++++++++++++++++- .../vssnodes/viewmodel/VssNodesViewModel.kt | 5 +- .../databroker/DataBrokerConnection.kt | 9 +- .../response/VssNodeUpdateResponse.kt | 31 +++ .../kuksa/extension/DataPointExtension.kt | 54 ++++- samples/src/main/res/values/colors.xml | 8 +- samples/src/main/res/values/themes.xml | 1 - 11 files changed, 353 insertions(+), 34 deletions(-) create mode 100644 kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/response/VssNodeUpdateResponse.kt diff --git a/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java b/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java index 0ff2cfc6..47031721 100644 --- a/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java +++ b/app/src/main/java/org/eclipse/kuksa/testapp/databroker/JavaDataBrokerEngine.java @@ -33,6 +33,8 @@ import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest; import org.eclipse.kuksa.connectivity.databroker.request.VssNodeFetchRequest; import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest; +import org.eclipse.kuksa.connectivity.databroker.request.VssNodeUpdateRequest; +import org.eclipse.kuksa.connectivity.databroker.response.VssNodeUpdateResponse; import org.eclipse.kuksa.coroutine.CoroutineCallback; import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse; import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse; @@ -124,6 +126,18 @@ public void update( dataBrokerConnection.update(request, callback); } + @Override + public void update( + @NonNull VssNodeUpdateRequest request, + @NonNull CoroutineCallback callback + ) { + if (dataBrokerConnection == null) { + return; + } + + dataBrokerConnection.update(request, callback ); + } + @Override public void subscribe(@NonNull SubscribeRequest request, @NonNull VssPathListener listener) { if (dataBrokerConnection == null) { diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt index cec13026..2acb1b90 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt @@ -38,8 +38,12 @@ import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeFetchRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest +import org.eclipse.kuksa.connectivity.databroker.request.VssNodeUpdateRequest +import org.eclipse.kuksa.connectivity.databroker.response.VssNodeUpdateResponse import org.eclipse.kuksa.coroutine.CoroutineCallback import org.eclipse.kuksa.extension.entriesMetadata +import org.eclipse.kuksa.extension.firstValue +import org.eclipse.kuksa.extension.stringValue import org.eclipse.kuksa.extension.valueType import org.eclipse.kuksa.proto.v1.KuksaValV1 import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse @@ -167,6 +171,28 @@ class KuksaDataBrokerActivity : ComponentActivity() { dataBrokerEngine.unsubscribe(request, vssNodeListener) } + vssNodesViewModel.onUpdateSignal = { signal -> + val request = VssNodeUpdateRequest(signal) + dataBrokerEngine.update( + request, + object : CoroutineCallback() { + override fun onSuccess(result: VssNodeUpdateResponse?) { + val errorsList = result?.flatMap { it.errorsList } + errorsList?.forEach { + outputViewModel.addOutputEntry(it.toString()) + return + } + + outputViewModel.addOutputEntry(result.toString()) + } + + override fun onError(error: Throwable) { + outputViewModel.addOutputEntry("Connection to data broker failed: ${error.message}") + } + }, + ) + } + vssNodesViewModel.onGetNode = { vssNode -> fetchVssNode(vssNode) } @@ -277,7 +303,9 @@ class KuksaDataBrokerActivity : ComponentActivity() { request, object : CoroutineCallback() { override fun onSuccess(result: GetResponse?) { - val errorsList = result?.errorsList + if (result == null) return + + val errorsList = result.errorsList errorsList?.forEach { outputViewModel.addOutputEntry(it.toString()) @@ -285,13 +313,19 @@ class KuksaDataBrokerActivity : ComponentActivity() { } val outputEntry = OutputEntry() - result?.entriesList?.withIndex()?.forEach { + result.entriesList?.withIndex()?.forEach { val dataEntry = it.value val text = dataEntry.toString().substringAfter("\n") outputEntry.addMessage(text) } + outputViewModel.addOutputEntry(outputEntry) + + val updatedValue = result.firstValue?.stringValue ?: "" + val dataBrokerProperty = vssPathsViewModel.dataBrokerProperty + .copy(value = updatedValue) + vssPathsViewModel.updateDataBrokerProperty(dataBrokerProperty) } override fun onError(error: Throwable) { @@ -333,6 +367,10 @@ class KuksaDataBrokerActivity : ComponentActivity() { object : CoroutineCallback() { override fun onSuccess(result: VssNode?) { Log.d(TAG, "Fetched node: $result") + + if (result == null) return + + vssNodesViewModel.updateNode(result) outputViewModel.addOutputEntry("Fetched node: $result") } diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/DataBrokerEngine.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/DataBrokerEngine.kt index 7a4547cf..4aa4bcdf 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/DataBrokerEngine.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/DataBrokerEngine.kt @@ -29,6 +29,8 @@ import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeFetchRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest +import org.eclipse.kuksa.connectivity.databroker.request.VssNodeUpdateRequest +import org.eclipse.kuksa.connectivity.databroker.response.VssNodeUpdateResponse import org.eclipse.kuksa.coroutine.CoroutineCallback import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse @@ -57,15 +59,20 @@ interface DataBrokerEngine { callback: CoroutineCallback, ) - fun subscribe(request: SubscribeRequest, listener: VssPathListener) + fun update( + request: VssNodeUpdateRequest, + callback: CoroutineCallback, + ) - fun unsubscribe(request: SubscribeRequest, listener: VssPathListener) + fun subscribe(request: SubscribeRequest, listener: VssPathListener) fun subscribe( request: VssNodeSubscribeRequest, vssNodeListener: VssNodeListener, ) + fun unsubscribe(request: SubscribeRequest, listener: VssPathListener) + fun unsubscribe( request: VssNodeSubscribeRequest, vssNodeListener: VssNodeListener, diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt index abb5a042..82187caa 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/KotlinDataBrokerEngine.kt @@ -33,6 +33,8 @@ import org.eclipse.kuksa.connectivity.databroker.request.SubscribeRequest import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeFetchRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest +import org.eclipse.kuksa.connectivity.databroker.request.VssNodeUpdateRequest +import org.eclipse.kuksa.connectivity.databroker.response.VssNodeUpdateResponse import org.eclipse.kuksa.coroutine.CoroutineCallback import org.eclipse.kuksa.proto.v1.KuksaValV1.GetResponse import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse @@ -110,6 +112,20 @@ class KotlinDataBrokerEngine( } } + override fun update( + request: VssNodeUpdateRequest, + callback: CoroutineCallback, + ) { + lifecycleScope.launch { + try { + val response = dataBrokerConnection?.update(request) ?: return@launch + callback.onSuccess(response) + } catch (e: DataBrokerException) { + callback.onError(e) + } + } + } + override fun subscribe(request: SubscribeRequest, listener: VssPathListener) { dataBrokerConnection?.subscribe(request, listener) } diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt index eee277f6..1bb669f3 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/view/DataBrokerVssNodesView.kt @@ -18,6 +18,7 @@ package org.eclipse.kuksa.testapp.databroker.vssnodes.view +import android.util.Log import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -26,7 +27,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -37,8 +40,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import org.eclipse.kuksa.extension.createDatapoint +import org.eclipse.kuksa.extension.valueCase +import org.eclipse.kuksa.extension.vss.copy import org.eclipse.kuksa.testapp.databroker.view.DefaultEdgePadding import org.eclipse.kuksa.testapp.databroker.view.DefaultElementPadding import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionAdapter @@ -46,16 +57,22 @@ import org.eclipse.kuksa.testapp.databroker.view.suggestions.SuggestionTextView import org.eclipse.kuksa.testapp.databroker.vssnodes.viewmodel.VssNodesViewModel import org.eclipse.kuksa.testapp.extension.compose.Headline import org.eclipse.kuksa.testapp.ui.theme.KuksaAppAndroidTheme +import org.eclipse.kuksa.vss.VssHeartRate import org.eclipse.kuksa.vss.VssVehicle import org.eclipse.kuksa.vsscore.model.VssNode import org.eclipse.kuksa.vsscore.model.VssSignal +private const val tag = "DataBrokerVssNodesView" + @Composable fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { Column { Headline(name = "Generated VSS Nodes") - val isUpdatePossible = viewModel.node is VssSignal<*> + var currentVssSignal: VssSignal<*>? = null + + val currentNode = viewModel.node + val isUpdatePossible = currentNode is VssSignal<*> val adapter = object : SuggestionAdapter { override fun toString(item: VssNode): String { return item.vssPath @@ -64,7 +81,7 @@ fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { SuggestionTextView( value = "Vehicle", - suggestions = viewModel.vssNodes, + suggestions = viewModel.nodes, adapter = adapter, onItemSelected = { val vssNode = it ?: VssVehicle() @@ -75,6 +92,15 @@ fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), ) Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + VssNodeInformation( + currentNode, + modifier = Modifier + .fillMaxWidth() + .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), + onSignalChanged = { updatedSignal -> + currentVssSignal = updatedSignal + }, + ) Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) Row( Modifier.fillMaxWidth(), @@ -82,7 +108,7 @@ fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { ) { Button( onClick = { - viewModel.onGetNode(viewModel.node) + viewModel.onGetNode(currentNode) }, modifier = Modifier.requiredWidth(80.dp), ) { @@ -90,7 +116,9 @@ fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { } Button( onClick = { - viewModel.onUpdateNode(viewModel.node) + currentVssSignal?.let { + viewModel.onUpdateSignal(it) + } }, enabled = isUpdatePossible, modifier = Modifier.requiredWidth(100.dp), @@ -99,15 +127,15 @@ fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { } if (viewModel.isSubscribed) { Button(onClick = { - viewModel.subscribedNodes.remove(viewModel.node) - viewModel.onUnsubscribeNode(viewModel.node) + viewModel.subscribedNodes.remove(currentNode) + viewModel.onUnsubscribeNode(currentNode) }) { Text(text = "Unsubscribe") } } else { Button(onClick = { - viewModel.subscribedNodes.add(viewModel.node) - viewModel.onSubscribeNode(viewModel.node) + viewModel.subscribedNodes.add(currentNode) + viewModel.onSubscribeNode(currentNode) }) { Text(text = "Subscribe") } @@ -116,3 +144,155 @@ fun DataBrokerVssNodesView(viewModel: VssNodesViewModel) { } } +@Composable +private fun VssNodeInformation( + vssNode: VssNode, + modifier: Modifier = Modifier, + isShowingInformation: Boolean = false, + onSignalChanged: (VssSignal<*>) -> Unit = {}, +) { + val isVssSignal = vssNode is VssSignal<*> + var isShowingNodeInformation by remember { mutableStateOf(isShowingInformation) } + + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + OutlinedButton(onClick = { isShowingNodeInformation = !isShowingNodeInformation }) { + Text(text = "Node Information") + } + } + + AnimatedContent( + targetState = isShowingNodeInformation, + label = "ShowingNodeInformationAnimation", + modifier = modifier, + ) { + when (it) { + true -> { + Surface(color = MaterialTheme.colorScheme.tertiary, shape = RoundedCornerShape(8.dp)) { + val boldSpanStyle = SpanStyle(fontWeight = FontWeight.Bold) + + Spacer(modifier = Modifier.padding(top = DefaultElementPadding, bottom = DefaultElementPadding)) + Column(modifier = Modifier.padding(all = DefaultElementPadding)) { + Text( + text = buildAnnotatedString { + withStyle(style = boldSpanStyle) { + append("UUID: ") + } + append(vssNode.uuid) + }, + maxLines = 2, + ) + Text( + text = buildAnnotatedString { + withStyle(style = boldSpanStyle) { + append("VSS Path: ") + } + append(vssNode.vssPath) + }, + maxLines = 1, + ) + Text( + text = buildAnnotatedString { + withStyle(style = boldSpanStyle) { + append("Type: ") + } + append(vssNode.type) + }, + maxLines = 1, + ) + Text( + text = buildAnnotatedString { + withStyle(style = boldSpanStyle) { + append("Description: ") + } + append(vssNode.description) + }, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + ) + if (isVssSignal) { + val vssSignal = vssNode as VssSignal<*> + VssSignalInformation(vssSignal, onSignalChanged = onSignalChanged) + } + } + } + } + + false -> { + Surface { + } + } + } + } +} + +@Composable +private fun VssSignalInformation( + vssSignal: VssSignal<*>, + modifier: Modifier = Modifier, + onSignalChanged: (VssSignal<*>) -> Unit = {}, +) { + val boldSpanStyle = SpanStyle(fontWeight = FontWeight.Bold) + + var inputValue: String by remember(vssSignal) { + mutableStateOf(vssSignal.value.toString()) + } + + Text( + text = buildAnnotatedString { + withStyle(style = boldSpanStyle) { + append("Data Type: ") + } + append(vssSignal.dataType.simpleName) + }, + maxLines = 1, + ) + TextField( + value = inputValue, + onValueChange = { newValue -> + inputValue = newValue + + @Suppress("TooGenericExceptionCaught") + try { + val datapoint = vssSignal.valueCase.createDatapoint(newValue.trim()) + val updatedVssSignal = vssSignal.copy(datapoint) + onSignalChanged(updatedVssSignal) + } catch (e: Exception) { + // Do nothing, wrong input + Log.v(tag, "Wrong input for VssSignal:value: ", e) + } + }, + modifier = modifier + .fillMaxWidth(), + singleLine = true, + label = { + Text(text = "Value") + }, + ) +} + +@Preview(showBackground = true) +@Composable +private fun Preview() { + KuksaAppAndroidTheme { + DataBrokerVssNodesView(VssNodesViewModel()) + } +} + +@Preview +@Composable +private fun NodeInformationPreview() { + KuksaAppAndroidTheme { + VssNodeInformation(VssVehicle(), isShowingInformation = true) + } +} + +@Preview(showBackground = true) +@Composable +private fun SignalInformationPreview() { + KuksaAppAndroidTheme { + VssNodeInformation(VssHeartRate(), isShowingInformation = true) + } +} diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/viewmodel/VssNodesViewModel.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/viewmodel/VssNodesViewModel.kt index 0a71195a..13afff7d 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/viewmodel/VssNodesViewModel.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/vssnodes/viewmodel/VssNodesViewModel.kt @@ -26,11 +26,12 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import org.eclipse.kuksa.vss.VssVehicle import org.eclipse.kuksa.vsscore.model.VssNode +import org.eclipse.kuksa.vsscore.model.VssSignal import org.eclipse.kuksa.vsscore.model.heritage class VssNodesViewModel : ViewModel() { var onGetNode: (node: VssNode) -> Unit = { } - var onUpdateNode: (node: VssNode) -> Unit = { } + var onUpdateSignal: (signal: VssSignal<*>) -> Unit = { } var onSubscribeNode: (node: VssNode) -> Unit = { } var onUnsubscribeNode: (node: VssNode) -> Unit = { } @@ -41,7 +42,7 @@ class VssNodesViewModel : ViewModel() { } private val vssVehicle = VssVehicle() - val vssNodes = listOf(vssVehicle) + vssVehicle.heritage + val nodes = listOf(vssVehicle) + vssVehicle.heritage var node: VssNode by mutableStateOf(vssVehicle) private set diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnection.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnection.kt index 3134f3ee..96c2d0f8 100644 --- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnection.kt +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/DataBrokerConnection.kt @@ -34,6 +34,7 @@ import org.eclipse.kuksa.connectivity.databroker.request.UpdateRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeFetchRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeSubscribeRequest import org.eclipse.kuksa.connectivity.databroker.request.VssNodeUpdateRequest +import org.eclipse.kuksa.connectivity.databroker.response.VssNodeUpdateResponse import org.eclipse.kuksa.connectivity.databroker.subscription.DataBrokerSubscriber import org.eclipse.kuksa.extension.TAG import org.eclipse.kuksa.extension.datapoint @@ -217,14 +218,14 @@ class DataBrokerConnection internal constructor( * Only a [VssSignal] can be updated because they have an actual value. When provided with any parent * [VssNode] then this [update] method will find all [VssSignal] children and updates their corresponding * [Types.Field] instead. - * Compared to [update] with only one [UpdateRequest], here multiple [SetResponse] will be returned - * because a [VssNode] may consists of multiple values which may need to be updated. + * Compared to [update] with only one [UpdateRequest], here multiple [SetResponse] via [VssNodeUpdateResponse] will + * be returned because a [VssNode] may consists of multiple values which may need to be updated. * * @throws DataBrokerException in case the connection to the DataBroker is no longer active * @throws IllegalArgumentException if the [VssSignal] could not be converted to a [Datapoint]. */ @Suppress("performance:SpreadOperator") // Neglectable: Field types are 1-2 elements mostly - suspend fun update(request: VssNodeUpdateRequest): Collection { + suspend fun update(request: VssNodeUpdateRequest): VssNodeUpdateResponse { val responses = mutableListOf() val vssNode = request.vssNode @@ -235,7 +236,7 @@ class DataBrokerConnection internal constructor( responses.add(response) } - return responses + return VssNodeUpdateResponse(responses) } /** diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/response/VssNodeUpdateResponse.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/response/VssNodeUpdateResponse.kt new file mode 100644 index 00000000..05993739 --- /dev/null +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/connectivity/databroker/response/VssNodeUpdateResponse.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.eclipse.kuksa.connectivity.databroker.response + +import org.eclipse.kuksa.proto.v1.KuksaValV1.SetResponse + +/** + * Represents a collection of [SetResponse]s. + */ +// Necessary to ensure Java compatibility with generics + suspend functions. +class VssNodeUpdateResponse internal constructor( + responses: Collection, +) : ArrayList(responses) { + internal constructor(vararg setResponse: SetResponse) : this(setResponse.toList()) +} diff --git a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/extension/DataPointExtension.kt b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/extension/DataPointExtension.kt index 45c003b3..8b51d34d 100644 --- a/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/extension/DataPointExtension.kt +++ b/kuksa-sdk/src/main/kotlin/org/eclipse/kuksa/extension/DataPointExtension.kt @@ -40,10 +40,23 @@ val Types.Metadata.valueType: ValueCase * * @throws IllegalArgumentException if the [VssSignal] could not be converted to a [Datapoint]. */ -@OptIn(ExperimentalUnsignedTypes::class) val VssSignal.datapoint: Datapoint get() { - val valueCase = when (dataType) { + val stringValue = value.toString() + + return valueCase.createDatapoint(stringValue) + } + +/** + * Converts the [VssSignal.value] into a [ValueCase] enum. The [VssSignal.dataType] is used to derive the correct + * [ValueCase]. + * + * @throws IllegalArgumentException if the [VssSignal] could not be converted to a [ValueCase]. + */ +@OptIn(ExperimentalUnsignedTypes::class) +val VssSignal.valueCase: ValueCase + get() { + return when (dataType) { String::class -> ValueCase.STRING Boolean::class -> ValueCase.BOOL Int::class -> ValueCase.INT32 @@ -61,12 +74,8 @@ val VssSignal.datapoint: Datapoint UIntArray::class -> ValueCase.UINT32_ARRAY ULongArray::class -> ValueCase.UINT64_ARRAY - else -> throw IllegalArgumentException("Could not create datapoint for value class: ${dataType::class}!") + else -> throw IllegalArgumentException("Could not create value case for value class: ${dataType::class}!") } - - val stringValue = value.toString() - - return valueCase.createDatapoint(stringValue) } /** @@ -128,13 +137,42 @@ fun ValueCase.createDatapoint(value: String): Datapoint { datapointBuilder.boolArray = createBoolArray(value) } } catch (e: NumberFormatException) { - Log.w(TAG, "Could not convert value: $value to ValueCase: $this") + Log.w(TAG, "Could not convert value: $value to ValueCase: $this", e) datapointBuilder.string = value // Fallback to string } return datapointBuilder.build() } +/** + * Returns the contained value inside the [Datapoint] as a string representation. + */ +val Datapoint.stringValue: String + get() { + val value: Any = when (valueCase) { + ValueCase.STRING -> string + ValueCase.UINT32 -> uint32 + ValueCase.INT32 -> int32 + ValueCase.UINT64 -> uint64 + ValueCase.INT64 -> int64 + ValueCase.FLOAT -> float + ValueCase.DOUBLE -> double + ValueCase.BOOL -> bool + ValueCase.STRING_ARRAY -> stringArray + ValueCase.UINT32_ARRAY -> uint32Array + ValueCase.INT32_ARRAY -> int32Array + ValueCase.UINT64_ARRAY -> uint64Array + ValueCase.INT64_ARRAY -> int64Array + ValueCase.FLOAT_ARRAY -> floatArray + ValueCase.DOUBLE_ARRAY -> doubleArray + ValueCase.BOOL_ARRAY -> boolArray + ValueCase.VALUE_NOT_SET -> "" + null -> "" + } + + return value.toString() + } + private fun createBoolArray(value: String): BoolArray { val csvValues = value.split(CSV_DELIMITER).map { it.toBoolean() } diff --git a/samples/src/main/res/values/colors.xml b/samples/src/main/res/values/colors.xml index fd65fa9f..4a83c26e 100644 --- a/samples/src/main/res/values/colors.xml +++ b/samples/src/main/res/values/colors.xml @@ -19,11 +19,5 @@ --> - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF + diff --git a/samples/src/main/res/values/themes.xml b/samples/src/main/res/values/themes.xml index 59740031..65d8355a 100644 --- a/samples/src/main/res/values/themes.xml +++ b/samples/src/main/res/values/themes.xml @@ -19,6 +19,5 @@ --> -