diff --git a/app/src/main/java/com/m57/hermescontrol/ui/common/ExposedDropdownField.kt b/app/src/main/java/com/m57/hermescontrol/ui/common/ExposedDropdownField.kt new file mode 100644 index 00000000..63291130 --- /dev/null +++ b/app/src/main/java/com/m57/hermescontrol/ui/common/ExposedDropdownField.kt @@ -0,0 +1,69 @@ +package com.m57.hermescontrol.ui.common + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuAnchorType +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +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.unit.dp + +/** + * Shared dropdown field used by [com.m57.hermescontrol.ui.config.ConfigScreen] + * and [com.m57.hermescontrol.ui.plugins.PluginsScreen]. Extracted from the + * byte-for-byte identical private copies that previously lived in each screen + * (issue #796). + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExposedDropdownField( + label: String, + options: List, + selectedValue: String, + onOptionSelected: (String) -> Unit, +) { + var expanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = it }, + ) { + OutlinedTextField( + value = selectedValue.ifEmpty { options.firstOrNull() ?: "" }, + onValueChange = {}, + readOnly = true, + modifier = + Modifier + .fillMaxWidth() + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable), + label = { Text(label) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + singleLine = true, + shape = RoundedCornerShape(8.dp), + ) + + ExposedDropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + options.forEach { option -> + DropdownMenuItem( + text = { Text(option) }, + onClick = { + onOptionSelected(option) + expanded = false + }, + ) + } + } + } +} diff --git a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt index 902b75bc..241800b7 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/config/ConfigScreen.kt @@ -23,11 +23,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuAnchorType -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -41,7 +37,6 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -57,6 +52,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.m57.hermescontrol.R import com.m57.hermescontrol.data.model.SchemaField import com.m57.hermescontrol.ui.common.ErrorState +import com.m57.hermescontrol.ui.common.ExposedDropdownField import com.m57.hermescontrol.ui.common.HermesScaffold import com.m57.hermescontrol.ui.common.NavIcon import com.m57.hermescontrol.ui.common.SearchBar @@ -591,7 +587,7 @@ private fun ConfigField( } "select" -> { - DropdownField( + ExposedDropdownField( label = label, options = field.options ?: emptyList(), selectedValue = (currentValue as? JsonPrimitive)?.content ?: "", @@ -655,51 +651,6 @@ private fun ConfigField( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun DropdownField( - label: String, - options: List, - selectedValue: String, - onOptionSelected: (String) -> Unit, -) { - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = it }, - ) { - OutlinedTextField( - value = selectedValue.ifEmpty { options.firstOrNull() ?: "" }, - onValueChange = {}, - readOnly = true, - modifier = - Modifier - .fillMaxWidth() - .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable), - label = { Text(label) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - singleLine = true, - shape = RoundedCornerShape(8.dp), - ) - - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - options.forEach { option -> - DropdownMenuItem( - text = { Text(option) }, - onClick = { - onOptionSelected(option) - expanded = false - }, - ) - } - } - } -} - /** Recursively find all config dot-paths not covered by the schema. */ private fun collectUncoveredPaths( config: Map, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt index e126bf81..4880655b 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/plugins/PluginsScreen.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.size 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.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.InstallDesktop @@ -24,11 +23,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuAnchorType -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -56,6 +51,7 @@ import com.m57.hermescontrol.theme.LocalHermesStatusColors import com.m57.hermescontrol.ui.common.DetailDialog import com.m57.hermescontrol.ui.common.EmptyState import com.m57.hermescontrol.ui.common.ErrorState +import com.m57.hermescontrol.ui.common.ExposedDropdownField import com.m57.hermescontrol.ui.common.HermesScaffold import com.m57.hermescontrol.ui.common.NavIcon import com.m57.hermescontrol.ui.common.SearchBar @@ -247,7 +243,7 @@ private fun ProviderSelectionSection( style = MaterialTheme.typography.labelMedium, ) Spacer(modifier = Modifier.height(4.dp)) - PluginProviderDropdown( + ExposedDropdownField( label = stringResource(R.string.plugins_memory_provider_label), options = listOf( @@ -276,7 +272,7 @@ private fun ProviderSelectionSection( style = MaterialTheme.typography.labelMedium, ) Spacer(modifier = Modifier.height(4.dp)) - PluginProviderDropdown( + ExposedDropdownField( label = stringResource(R.string.plugins_context_engine_label), options = listOf("compressor") + state.contextOptions.map { it.name }, selectedValue = state.contextEngine, @@ -302,51 +298,6 @@ private fun ProviderSelectionSection( } } -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun PluginProviderDropdown( - label: String, - options: List, - selectedValue: String, - onOptionSelected: (String) -> Unit, -) { - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = it }, - ) { - OutlinedTextField( - value = selectedValue.ifEmpty { options.firstOrNull() ?: "" }, - onValueChange = {}, - readOnly = true, - modifier = - Modifier - .fillMaxWidth() - .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable), - label = { Text(label) }, - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - singleLine = true, - shape = RoundedCornerShape(8.dp), - ) - - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - options.forEach { option -> - DropdownMenuItem( - text = { Text(option) }, - onClick = { - onOptionSelected(option) - expanded = false - }, - ) - } - } - } -} - @OptIn(ExperimentalMaterial3Api::class) @Composable private fun InstallSection(