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 e660dac2..5f72b44b 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/KuksaDataBrokerActivity.kt @@ -77,6 +77,8 @@ class KuksaDataBrokerActivity : ComponentActivity() { override fun onSuccess(result: DataBrokerConnection?) { outputViewModel.addOutputEntry("Connection to DataBroker successful established") connectionViewModel.updateConnectionState(ConnectionViewState.CONNECTED) + + loadVssPathSuggestions() } override fun onError(error: Throwable) { @@ -313,4 +315,24 @@ class KuksaDataBrokerActivity : ComponentActivity() { }, ) } + + private fun loadVssPathSuggestions() { + val property = Property("Vehicle", listOf(Field.FIELD_VALUE)) + + dataBrokerEngine.fetch( + property, + object : CoroutineCallback() { + override fun onSuccess(result: GetResponse?) { + val entriesList = result?.entriesList + val vssPaths = entriesList?.map { it.path } ?: emptyList() + + vssPropertiesViewModel.updateSuggestions(vssPaths) + } + + override fun onError(error: Throwable) { + outputViewModel.addOutputEntry(error.toString()) + } + }, + ) + } } diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerConnectionView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerConnectionView.kt index 68ba2e0c..04955eb6 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerConnectionView.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/DataBrokerConnectionView.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.Checkbox +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -52,7 +53,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import org.eclipse.kuksa.testapp.databroker.model.ConnectionInfo import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel -import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel.* +import org.eclipse.kuksa.testapp.databroker.viewmodel.ConnectionViewModel.ConnectionViewState import org.eclipse.kuksa.testapp.extension.compose.Headline import org.eclipse.kuksa.testapp.extension.compose.RememberCountdown import org.eclipse.kuksa.testapp.extension.fetchFileName @@ -71,8 +72,9 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) { mutableStateOf(connectionInfoState.value) } - Headline("Connection") Column { + Headline("Connection") + AnimatedVisibility(visible = viewModel.isDisconnected) { Column { Row( @@ -223,13 +225,9 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) { Text(text = "Connecting... ($timeoutSeconds)", textAlign = TextAlign.Center) } - ConnectionViewState.CONNECTED -> - Button( - onClick = { viewModel.onDisconnect() }, - modifier = Modifier.requiredWidth(MinimumButtonWidth), - ) { - Text(text = "Disconnect") - } + ConnectionViewState.CONNECTED -> { + // intentionally left empty + } } } } @@ -238,7 +236,21 @@ fun DataBrokerConnection(viewModel: ConnectionViewModel) { @Preview @Composable -fun DataBrokerConnectionPreview() { +private fun ConnectedPreview() { val connectionInfoRepository = ConnectionInfoRepository(LocalContext.current) - DataBrokerConnection(viewModel = ConnectionViewModel(connectionInfoRepository)) + val viewModel = ConnectionViewModel(connectionInfoRepository) + Surface { + DataBrokerConnection(viewModel = viewModel) + } +} + +@Preview +@Composable +private fun DisconnectedPreview() { + val connectionInfoRepository = ConnectionInfoRepository(LocalContext.current) + val viewModel = ConnectionViewModel(connectionInfoRepository) + viewModel.updateConnectionState(ConnectionViewState.CONNECTING) + Surface { + DataBrokerConnection(viewModel = viewModel) + } } 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 823c53cd..6cc4f379 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 @@ -19,7 +19,6 @@ package org.eclipse.kuksa.testapp.databroker.view -import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -32,14 +31,12 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset 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.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.ClickableText import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown @@ -55,6 +52,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -66,16 +64,20 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextLayoutResult -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first 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.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 @@ -83,13 +85,15 @@ import org.eclipse.kuksa.testapp.databroker.viewmodel.TopAppBarViewModel.DataBro import org.eclipse.kuksa.testapp.databroker.viewmodel.VSSPropertiesViewModel import org.eclipse.kuksa.testapp.databroker.viewmodel.VssSpecificationsViewModel import org.eclipse.kuksa.testapp.extension.compose.Headline -import org.eclipse.kuksa.testapp.extension.compose.LazyDropdownMenu 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.VssSpecification import java.time.format.DateTimeFormatter +val SettingsMenuPadding = 16.dp val DefaultEdgePadding = 25.dp val DefaultElementPadding = 10.dp val MinimumButtonWidth = 150.dp @@ -116,17 +120,14 @@ fun DataBrokerView( verticalArrangement = Arrangement.SpaceBetween, ) { Column { + if (!connectionViewModel.isConnected) { + DataBrokerConnection(connectionViewModel) + } val dataBrokerMode = topAppBarViewModel.dataBrokerMode - DataBrokerConnection(connectionViewModel) if (connectionViewModel.isConnected) { - AnimatedContent( - targetState = dataBrokerMode, - label = "DataBrokerModeAnimation", - ) { mode -> - when (mode) { - DataBrokerMode.VSS_PATH -> DataBrokerProperties(vssPropertiesViewModel) - DataBrokerMode.SPECIFICATION -> DataBrokerSpecifications(vssSpecificationsViewModel) - } + when (dataBrokerMode) { + DataBrokerMode.VSS_PATH -> DataBrokerProperties(vssPropertiesViewModel) + DataBrokerMode.SPECIFICATION -> DataBrokerSpecifications(vssSpecificationsViewModel) } } Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) @@ -150,46 +151,86 @@ private fun TopBar( containerColor = MaterialTheme.colorScheme.primaryContainer, ), actions = { - OverflowMenu { - Row( - modifier = Modifier - .clickable( - enabled = connectionViewModel.isDisconnected, - ) { - val newValue = !topAppBarViewModel.isCompatibilityModeEnabled - topAppBarViewModel.isCompatibilityModeEnabled = newValue - } - .padding(horizontal = 16.dp), + ConnectionStatusIcon(connectionViewModel) + SettingsMenu(connectionViewModel, topAppBarViewModel) + }, + ) +} + +@Composable +private fun SettingsMenu( + connectionViewModel: ConnectionViewModel, + topAppBarViewModel: TopAppBarViewModel, +) { + OverflowMenu { + Row( + modifier = Modifier + .clickable( + enabled = connectionViewModel.isDisconnected, ) { - Checkbox( - checked = topAppBarViewModel.isCompatibilityModeEnabled, - onCheckedChange = null, - enabled = connectionViewModel.isDisconnected, - ) - Text(text = "Java Compatibility Mode", modifier = Modifier.padding(start = 16.dp)) + val newValue = !topAppBarViewModel.isCompatibilityModeEnabled + topAppBarViewModel.isCompatibilityModeEnabled = newValue } - Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) - Row( - modifier = Modifier - .clickable { - val newMode = if (!topAppBarViewModel.isSpecificationModeEnabled) { - DataBrokerMode.SPECIFICATION - } else { - DataBrokerMode.VSS_PATH - } - topAppBarViewModel.updateDataBrokerMode(newMode) - } - .padding(horizontal = 16.dp), - ) { - Checkbox( - checked = topAppBarViewModel.isSpecificationModeEnabled, - onCheckedChange = null, - ) - Text(text = "Specification Mode", modifier = Modifier.padding(start = 16.dp)) + .padding(horizontal = SettingsMenuPadding), + ) { + Checkbox( + checked = topAppBarViewModel.isCompatibilityModeEnabled, + onCheckedChange = null, + enabled = connectionViewModel.isDisconnected, + ) + Text( + text = "Java Compatibility Mode", + modifier = Modifier.padding(start = SettingsMenuPadding), + ) + } + Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + Row( + modifier = Modifier + .clickable { + val newMode = if (!topAppBarViewModel.isSpecificationModeEnabled) { + DataBrokerMode.SPECIFICATION + } else { + DataBrokerMode.VSS_PATH + } + topAppBarViewModel.updateDataBrokerMode(newMode) } - } - }, - ) + .padding(horizontal = SettingsMenuPadding), + ) { + Checkbox( + checked = topAppBarViewModel.isSpecificationModeEnabled, + onCheckedChange = null, + ) + Text(text = "Specification Mode", modifier = Modifier.padding(start = SettingsMenuPadding)) + } + } +} + +@Composable +private fun ConnectionStatusIcon( + connectionViewModel: ConnectionViewModel, +) { + val modifier: Modifier = Modifier + if (connectionViewModel.isConnected) { + Icon( + painter = painterResource(id = R.drawable.round_power_24), + contentDescription = "Disconnect", + modifier = modifier.clickable(enabled = connectionViewModel.isConnected) { + connectionViewModel.onDisconnect() + }, + ) + } else { + Icon( + painter = painterResource(id = R.drawable.round_power_off_24), + contentDescription = "Connect", + modifier = modifier.clickable(enabled = connectionViewModel.isDisconnected) { + val coroutineScope = CoroutineScope(Dispatchers.Default) + coroutineScope.launch { + val connectionInfo = connectionViewModel.connectionInfoFlow.first() + connectionViewModel.onConnect(connectionInfo) + } + }, + ) + } } @Composable @@ -197,17 +238,23 @@ fun DataBrokerSpecifications(viewModel: VssSpecificationsViewModel) { Column { Headline(name = "Specifications") - var selectedIndex by remember { mutableStateOf(0) } - LazyDropdownMenu( + val adapter = object : SuggestionAdapter { + override fun toString(item: VssSpecification): String { + return item.vssPath + } + } + + SuggestionTextView( + value = "Vehicle", + suggestions = viewModel.specifications, + adapter = adapter, + onItemSelected = { + val specification = it ?: VssVehicle() + viewModel.updateSpecification(specification) + }, modifier = Modifier + .fillMaxWidth() .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), - items = viewModel.specifications, - selectedIndex = selectedIndex, - itemToString = { it.vssPath.substringAfter(".") }, - onItemSelected = { index, item -> - selectedIndex = index - viewModel.updateSpecification(item) - }, ) Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) Row( @@ -248,60 +295,77 @@ fun DataBrokerProperties(viewModel: VSSPropertiesViewModel) { Column { Headline(name = "Properties") - TextField( + SuggestionTextView( + suggestions = viewModel.suggestions, value = viewModel.vssProperties.vssPath, - onValueChange = { + onValueChanged = { val newVssProperties = viewModel.vssProperties.copy( vssPath = it, valueType = ValueCase.VALUE_NOT_SET, ) viewModel.updateVssProperties(newVssProperties) }, - modifier = Modifier - .fillMaxWidth() - .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), - singleLine = true, label = { Text(text = "VSS Path") }, - suffix = { - Row(modifier = Modifier.offset(x = 15.dp)) { - ClickableText( - text = AnnotatedString(": ${viewModel.vssProperties.valueType}"), - onClick = { expanded = !expanded }, - style = TextStyle(fontStyle = FontStyle.Italic), - ) - Box(modifier = Modifier.requiredHeight(23.dp)) { - IconButton(onClick = { expanded = !expanded }) { - Icon( - modifier = Modifier.size(23.dp), - imageVector = Icons.Default.ArrowDropDown, - contentDescription = "More", - ) - } - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - modifier = Modifier.height(400.dp), - ) { - viewModel.valueTypes.forEach { - DropdownMenuItem( - text = { - Text(it.toString()) - }, - onClick = { - expanded = false + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .padding(start = DefaultEdgePadding, end = DefaultEdgePadding), + ) + Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) + Row { + TextField( + value = "${viewModel.vssProperties.valueType}", + onValueChange = {}, + label = { + Text("Field 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 = viewModel.vssProperties.copy(valueType = it) - viewModel.updateVssProperties(newVssProperties) - }, - ) - } - } + val newVssProperties = viewModel.vssProperties.copy(valueType = it) + viewModel.updateVssProperties(newVssProperties) + }, + ) } } - }, - ) + } + } Spacer(modifier = Modifier.padding(top = DefaultElementPadding)) Row { TextField( diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/suggestions/SuggestionAdapter.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/suggestions/SuggestionAdapter.kt new file mode 100644 index 00000000..72387096 --- /dev/null +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/suggestions/SuggestionAdapter.kt @@ -0,0 +1,30 @@ +/* + * 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.view.suggestions + +interface SuggestionAdapter { + fun toString(item: T): String +} + +class DefaultSuggestionAdapter : SuggestionAdapter { + override fun toString(item: T): String { + return item.toString() + } +} diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/suggestions/SuggestionTextView.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/suggestions/SuggestionTextView.kt new file mode 100644 index 00000000..0d559bf8 --- /dev/null +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/view/suggestions/SuggestionTextView.kt @@ -0,0 +1,231 @@ +/* + * 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.view.suggestions + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Clear +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +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.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.toSize +import org.eclipse.kuksa.testapp.R + +@Composable +fun SuggestionTextView( + suggestions: Collection, + adapter: SuggestionAdapter = DefaultSuggestionAdapter(), + value: String = "", + onItemSelected: ((T?) -> Unit)? = null, + onValueChanged: ((String) -> Unit)? = null, + label: @Composable (() -> Unit)? = null, + singleLine: Boolean = false, + modifier: Modifier, +) { + var text by remember { + mutableStateOf(value) + } + + val heightTextFields by remember { + mutableStateOf(55.dp) + } + + var textFieldSize by remember { + mutableStateOf(Size.Zero) + } + + var expanded by remember { + mutableStateOf(false) + } + val interactionSource = remember { + MutableInteractionSource() + } + + val focusManager = LocalFocusManager.current + + Column( + modifier = modifier + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { + expanded = false + }, + ), + ) { + Column(modifier = Modifier.fillMaxWidth()) { + Row(modifier = Modifier.fillMaxWidth()) { + TextField( + label = label, + modifier = Modifier + .fillMaxWidth() + .height(heightTextFields) + .onGloballyPositioned { coordinates -> + textFieldSize = coordinates.size.toSize() + } + .background(Color.Transparent), + value = text, + onValueChange = { + text = it + expanded = true + onValueChanged?.invoke(it) + }, + colors = TextFieldDefaults.colors( + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + cursorColor = Color.Black, + ), + textStyle = TextStyle( + color = Color.Black, + fontSize = 16.sp, + ), + keyboardActions = KeyboardActions( + onDone = { + expanded = false + focusManager.clearFocus() + }, + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done, + ), + singleLine = singleLine, + trailingIcon = { + Row { + if (text.isNotEmpty()) { + IconButton(onClick = { + text = "" + expanded = false + onValueChanged?.invoke(text) + onItemSelected?.invoke(null) + }) { + Icon( + imageVector = Icons.Rounded.Clear, + contentDescription = "clear", + tint = Color.Black, + ) + } + } + if (suggestions.isNotEmpty()) { + IconButton(onClick = { expanded = !expanded }) { + val drawableRes = if (expanded) { + R.drawable.baseline_arrow_drop_up_24 + } else { + R.drawable.baseline_arrow_drop_down_24 + } + + Icon( + painter = painterResource(id = drawableRes), + contentDescription = "suggestions", + tint = Color.Black, + ) + } + } + } + }, + ) + } + + AnimatedVisibility(visible = expanded) { + Card( + modifier = Modifier + .padding(top = 5.dp) + .width(textFieldSize.width.dp), + elevation = CardDefaults.elevatedCardElevation(), + shape = RoundedCornerShape(topStart = 5.dp, topEnd = 5.dp), + ) { + LazyColumn( + modifier = Modifier.heightIn(max = 150.dp), + ) { + items( + suggestions.filter { + adapter.toString(it).lowercase().contains(text.lowercase()) + }, + ) { + SuggestionItem( + item = it, + itemText = adapter.toString(it), + ) { item -> + text = adapter.toString(item) + expanded = false + focusManager.clearFocus() + onValueChanged?.invoke(text) + onItemSelected?.invoke(it) + } + } + } + } + } + } + } +} + +@Composable +private fun SuggestionItem( + item: T, + itemText: String, + onSelect: (T) -> Unit, +) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + onSelect(item) + } + .padding(10.dp), + ) { + Text(text = itemText, fontSize = 16.sp) + } +} diff --git a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/viewmodel/VssPropertiesViewModel.kt b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/viewmodel/VssPropertiesViewModel.kt index 486d8000..0ba78623 100644 --- a/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/viewmodel/VssPropertiesViewModel.kt +++ b/app/src/main/kotlin/org/eclipse/kuksa/testapp/databroker/viewmodel/VssPropertiesViewModel.kt @@ -31,6 +31,7 @@ import org.eclipse.kuksa.model.Property import org.eclipse.kuksa.proto.v1.Types.Datapoint import org.eclipse.kuksa.proto.v1.Types.Datapoint.ValueCase import org.eclipse.kuksa.proto.v1.Types.Field +import java.util.TreeSet class VSSPropertiesViewModel : ViewModel() { var onGetProperty: (property: Property) -> Unit = { } @@ -47,29 +48,51 @@ class VSSPropertiesViewModel : ViewModel() { var vssProperties: VSSProperties by mutableStateOf(VSSProperties()) private set - val valueTypes: List = ValueCase.values().toList() + val valueTypes: List = ValueCase.entries val fieldTypes: List = listOf( Field.FIELD_VALUE, Field.FIELD_ACTUATOR_TARGET, Field.FIELD_METADATA, ) + var suggestions: Set by mutableStateOf(setOf()) + private set + val datapoint: Datapoint get() = vssProperties.valueType.createDatapoint(vssProperties.value) - // Meta data are always part of the properties val property: Property get() = Property(vssProperties.vssPath, listOf(vssProperties.fieldType)) fun updateVssProperties(vssProperties: VSSProperties = VSSProperties()) { this.vssProperties = vssProperties } + + fun updateSuggestions(vssPaths: Collection) { + suggestions = generateVssPathHierarchy(vssPaths) + } + + private fun generateVssPathHierarchy(paths: Collection): TreeSet { + val pathSet = TreeSet() + + paths.forEach { + pathSet.add(it) + + var value = it + while (value.indexOf(".") > -1) { + value = value.substringBeforeLast(".") + pathSet.add(value) + } + } + + return pathSet + } } @Immutable data class VSSProperties( - val vssPath: String = "Vehicle.Speed", + val vssPath: String = "Vehicle", val valueType: ValueCase = ValueCase.VALUE_NOT_SET, - val value: String = "130", + val value: String = "", val fieldType: Field = Field.FIELD_VALUE, ) diff --git a/app/src/main/res/drawable/baseline_arrow_drop_down_24.xml b/app/src/main/res/drawable/baseline_arrow_drop_down_24.xml new file mode 100644 index 00000000..c1c897af --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_drop_down_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_arrow_drop_up_24.xml b/app/src/main/res/drawable/baseline_arrow_drop_up_24.xml new file mode 100644 index 00000000..10c5932c --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_drop_up_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/round_power_24.xml b/app/src/main/res/drawable/round_power_24.xml new file mode 100644 index 00000000..59ed4e0c --- /dev/null +++ b/app/src/main/res/drawable/round_power_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/round_power_off_24.xml b/app/src/main/res/drawable/round_power_off_24.xml new file mode 100644 index 00000000..ec5d47ae --- /dev/null +++ b/app/src/main/res/drawable/round_power_off_24.xml @@ -0,0 +1,24 @@ + + + + +