feat: make map and []any config fields editable as JSON text in Android UI
Replace ReadOnlyField with JsonField composable that provides multi-line JSON editing with real-time validation feedback. Add prettyPrint serialization for readable display and parseToJsonElement for save, letting parse errors propagate to the existing error snackbar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
76d669efb5
commit
7746cafe50
2 changed files with 23 additions and 6 deletions
|
|
@ -201,7 +201,7 @@ private fun ConfigField(
|
||||||
"float" -> NumberField(field, onValueChanged, KeyboardType.Decimal)
|
"float" -> NumberField(field, onValueChanged, KeyboardType.Decimal)
|
||||||
"[]string" -> StringArrayField(field, onValueChanged)
|
"[]string" -> StringArrayField(field, onValueChanged)
|
||||||
"directory" -> DirectoryField(field, onValueChanged, snackbarHostState)
|
"directory" -> DirectoryField(field, onValueChanged, snackbarHostState)
|
||||||
"map", "[]any" -> ReadOnlyField(field)
|
"map", "[]any" -> JsonField(field, onValueChanged)
|
||||||
else -> StringField(field, onValueChanged)
|
else -> StringField(field, onValueChanged)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -296,13 +296,26 @@ private fun StringArrayField(field: FieldState, onValueChanged: (String) -> Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ReadOnlyField(field: FieldState) {
|
private fun JsonField(field: FieldState, onValueChanged: (String) -> Unit) {
|
||||||
|
val jsonError = remember(field.value) {
|
||||||
|
if (field.value.isBlank()) null
|
||||||
|
else try {
|
||||||
|
kotlinx.serialization.json.Json.parseToJsonElement(field.value); null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = field.value.ifEmpty { "(complex value)" },
|
value = field.value,
|
||||||
onValueChange = {},
|
onValueChange = onValueChanged,
|
||||||
label = { Text(field.label, color = TextSecondary) },
|
label = { Text(field.label, color = TextSecondary) },
|
||||||
readOnly = true,
|
singleLine = false,
|
||||||
enabled = false,
|
minLines = 3,
|
||||||
|
isError = jsonError != null,
|
||||||
|
supportingText = if (jsonError != null) {
|
||||||
|
{ Text(jsonError, color = MaterialTheme.colorScheme.error) }
|
||||||
|
} else null,
|
||||||
colors = configFieldColors(),
|
colors = configFieldColors(),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonNull
|
import kotlinx.serialization.json.JsonNull
|
||||||
|
|
@ -29,6 +30,7 @@ class ConfigViewModel(private val apiClient: ConfigApiClient) : ViewModel() {
|
||||||
private var configValues: JsonObject? = null
|
private var configValues: JsonObject? = null
|
||||||
private var saveJob: Job? = null
|
private var saveJob: Job? = null
|
||||||
private var pendingSectionKey: String? = null
|
private var pendingSectionKey: String? = null
|
||||||
|
private val prettyJson = Json { prettyPrint = true }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadData()
|
loadData()
|
||||||
|
|
@ -193,6 +195,7 @@ class ConfigViewModel(private val apiClient: ConfigApiClient) : ViewModel() {
|
||||||
element.map { it.jsonPrimitive.contentOrNull ?: "" }.joinToString(", ")
|
element.map { it.jsonPrimitive.contentOrNull ?: "" }.joinToString(", ")
|
||||||
} else ""
|
} else ""
|
||||||
}
|
}
|
||||||
|
"map", "[]any" -> prettyJson.encodeToString(JsonElement.serializer(), element)
|
||||||
else -> (element as? JsonPrimitive)?.contentOrNull ?: element.toString()
|
else -> (element as? JsonPrimitive)?.contentOrNull ?: element.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -232,6 +235,7 @@ class ConfigViewModel(private val apiClient: ConfigApiClient) : ViewModel() {
|
||||||
.filter { it.isNotEmpty() }
|
.filter { it.isNotEmpty() }
|
||||||
.map { JsonPrimitive(it) }
|
.map { JsonPrimitive(it) }
|
||||||
)
|
)
|
||||||
|
"map", "[]any" -> prettyJson.parseToJsonElement(field.value)
|
||||||
else -> JsonPrimitive(field.value)
|
else -> JsonPrimitive(field.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue