refactor(ui): extract shared ExposedDropdownField (issue #796) (#815)

DropdownField (ConfigScreen) and PluginProviderDropdown (PluginsScreen)
were byte-for-byte identical. Extract into ui/common/ExposedDropdownField
and route both call sites to it. No behavior change.

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-04 20:39:23 +03:00
1 parent e639e33188
commit 509a018434
3 files changed
+74 -103

No files matched your search

@@ -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<String>,
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<String, JsonElement>,