From 481704b754ee5960c8c5fcc656facaf8287c2ef0 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 14 May 2026 14:48:29 +0800 Subject: [PATCH 1/4] Support editing existing items in EditableTextMapScreen and EditableTextSetScreen --- .../settings/ui/EditableTextMapScreen.kt | 46 ++++++++++++++++--- .../settings/ui/EditableTextSetScreen.kt | 37 ++++++++++++--- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt index 42c0eca357..9ea010b499 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt @@ -1,6 +1,7 @@ package com.github.kr328.clash.settings.ui import androidx.annotation.StringRes +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.AlertDialog import androidx.compose.material3.HorizontalDivider @@ -77,6 +78,7 @@ private fun EditableTextMapScreen( initialValues?.entries.orEmpty().map { it.toPair() }.toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } + var editingIndex by remember { mutableStateOf(null) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -100,12 +102,17 @@ private fun EditableTextMapScreen( EmptyEditorContent(Modifier.weight(1f)) } else { LazyColumn(state = lazyListState, modifier = Modifier.weight(1f)) { - items(items = values, key = { it.first }) { entry -> + itemsIndexed(items = values, key = { _, entry -> entry.first }) { index, entry -> val (key, value) = entry ReorderableItem(reorderableLazyListState, key = key) { _ -> ListItem( headlineContent = { Text(key) }, supportingContent = { Text(value) }, + modifier = + Modifier.clickable { + editingIndex = index + showAddDialog = true + }, leadingContent = { Icon( imageVector = TabbyIcons.BaselineDragHandle, @@ -142,13 +149,36 @@ private fun EditableTextMapScreen( } if (showAddDialog) { + val editingEntry = editingIndex?.let(values::get) MapEntryInputDialog( title = title, - onDismiss = { showAddDialog = false }, + initialKey = editingEntry?.first.orEmpty(), + initialValue = editingEntry?.second.orEmpty(), + onDismiss = { + showAddDialog = false + editingIndex = null + }, onConfirm = { key, valueText -> - val index = values.indexOfFirst { it.first == key } - if (index >= 0) values[index] = key to valueText else values.add(key to valueText) + val currentEditingIndex = editingIndex + + if (currentEditingIndex == null) { + val index = values.indexOfFirst { it.first == key } + if (index >= 0) values[index] = key to valueText else values.add(key to valueText) + } else { + val existingIndex = values.indexOfFirst { it.first == key } + when { + existingIndex < 0 || existingIndex == currentEditingIndex -> { + values[currentEditingIndex] = key to valueText + } + else -> { + values[existingIndex] = key to valueText + values.removeAt(currentEditingIndex) + } + } + } + showAddDialog = false + editingIndex = null }, ) } @@ -157,11 +187,13 @@ private fun EditableTextMapScreen( @Composable private fun MapEntryInputDialog( @StringRes title: Int, + initialKey: String, + initialValue: String, onDismiss: () -> Unit, onConfirm: (String, String) -> Unit, ) { - var keyText by remember { mutableStateOf(initialTextFieldValue("")) } - var valueText by remember { mutableStateOf(initialTextFieldValue("")) } + var keyText by remember(initialKey) { mutableStateOf(initialTextFieldValue(initialKey)) } + var valueText by remember(initialValue) { mutableStateOf(initialTextFieldValue(initialValue)) } val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current val confirmEnabled = keyText.text.isNotBlank() && valueText.text.isNotBlank() diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt index 0db18326a3..aa1482def6 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt @@ -1,6 +1,7 @@ package com.github.kr328.clash.settings.ui import androidx.annotation.StringRes +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -8,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.AlertDialog import androidx.compose.material3.HorizontalDivider @@ -73,6 +74,7 @@ private fun EditableTextSetScreen( ) { val values = remember(initialValues) { initialValues.orEmpty().toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } + var editingIndex by remember { mutableStateOf(null) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -96,10 +98,15 @@ private fun EditableTextSetScreen( EmptyEditorContent(Modifier.weight(1f)) } else { LazyColumn(state = lazyListState, modifier = Modifier.weight(1f)) { - items(items = values, key = { it }) { value -> + itemsIndexed(items = values, key = { _, value -> value }) { index, value -> ReorderableItem(reorderableLazyListState, key = value) { _ -> ListItem( headlineContent = { Text(value) }, + modifier = + Modifier.clickable { + editingIndex = index + showAddDialog = true + }, leadingContent = { Icon( imageVector = TabbyIcons.BaselineDragHandle, @@ -138,12 +145,29 @@ private fun EditableTextSetScreen( if (showAddDialog) { SingleTextInputDialog( title = title, - onDismiss = { showAddDialog = false }, + initialText = editingIndex?.let(values::get).orEmpty(), + onDismiss = { + showAddDialog = false + editingIndex = null + }, onConfirm = { newValue -> - if (newValue.isNotBlank() && !values.contains(newValue)) { - values.add(newValue) + val normalizedValue = newValue.trim() + val currentEditingIndex = editingIndex + + if (normalizedValue.isNotBlank()) { + if (currentEditingIndex == null) { + if (!values.contains(normalizedValue)) { + values.add(normalizedValue) + } + } else if ( + !values.contains(normalizedValue) || values[currentEditingIndex] == normalizedValue + ) { + values[currentEditingIndex] = normalizedValue + } } + showAddDialog = false + editingIndex = null }, ) } @@ -152,10 +176,11 @@ private fun EditableTextSetScreen( @Composable private fun SingleTextInputDialog( @StringRes title: Int, + initialText: String, onDismiss: () -> Unit, onConfirm: (String) -> Unit, ) { - var inputText by remember { mutableStateOf(initialTextFieldValue("")) } + var inputText by remember(initialText) { mutableStateOf(initialTextFieldValue(initialText)) } val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current From 41ad4a9dd4b9061ef37137065c219bc28cd1a8e8 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 14:35:21 +0800 Subject: [PATCH 2/4] Prefer mutableIntStateOf --- .../kr328/clash/settings/ui/EditableTextMapScreen.kt | 7 ++++--- .../kr328/clash/settings/ui/EditableTextSetScreen.kt | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt index 9ea010b499..559f162a48 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -78,7 +79,7 @@ private fun EditableTextMapScreen( initialValues?.entries.orEmpty().map { it.toPair() }.toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } - var editingIndex by remember { mutableStateOf(null) } + var editingIndex by remember { mutableIntStateOf(-1) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -156,7 +157,7 @@ private fun EditableTextMapScreen( initialValue = editingEntry?.second.orEmpty(), onDismiss = { showAddDialog = false - editingIndex = null + editingIndex = -1 }, onConfirm = { key, valueText -> val currentEditingIndex = editingIndex @@ -178,7 +179,7 @@ private fun EditableTextMapScreen( } showAddDialog = false - editingIndex = null + editingIndex = -1 }, ) } diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt index aa1482def6..f4d8461b62 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -74,7 +75,7 @@ private fun EditableTextSetScreen( ) { val values = remember(initialValues) { initialValues.orEmpty().toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } - var editingIndex by remember { mutableStateOf(null) } + var editingIndex by remember { mutableIntStateOf(-1) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -148,7 +149,7 @@ private fun EditableTextSetScreen( initialText = editingIndex?.let(values::get).orEmpty(), onDismiss = { showAddDialog = false - editingIndex = null + editingIndex = -1 }, onConfirm = { newValue -> val normalizedValue = newValue.trim() @@ -167,7 +168,7 @@ private fun EditableTextSetScreen( } showAddDialog = false - editingIndex = null + editingIndex = -1 }, ) } From e6f87d145942c8d16fc29348c0f82355e675b95e Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 14:39:08 +0800 Subject: [PATCH 3/4] Revert "Prefer mutableIntStateOf" This reverts commit 41ad4a9dd4b9061ef37137065c219bc28cd1a8e8. --- .../kr328/clash/settings/ui/EditableTextMapScreen.kt | 7 +++---- .../kr328/clash/settings/ui/EditableTextSetScreen.kt | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt index 559f162a48..9ea010b499 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt @@ -22,7 +22,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.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -79,7 +78,7 @@ private fun EditableTextMapScreen( initialValues?.entries.orEmpty().map { it.toPair() }.toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } - var editingIndex by remember { mutableIntStateOf(-1) } + var editingIndex by remember { mutableStateOf(null) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -157,7 +156,7 @@ private fun EditableTextMapScreen( initialValue = editingEntry?.second.orEmpty(), onDismiss = { showAddDialog = false - editingIndex = -1 + editingIndex = null }, onConfirm = { key, valueText -> val currentEditingIndex = editingIndex @@ -179,7 +178,7 @@ private fun EditableTextMapScreen( } showAddDialog = false - editingIndex = -1 + editingIndex = null }, ) } diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt index f4d8461b62..aa1482def6 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt @@ -22,7 +22,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.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -75,7 +74,7 @@ private fun EditableTextSetScreen( ) { val values = remember(initialValues) { initialValues.orEmpty().toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } - var editingIndex by remember { mutableIntStateOf(-1) } + var editingIndex by remember { mutableStateOf(null) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -149,7 +148,7 @@ private fun EditableTextSetScreen( initialText = editingIndex?.let(values::get).orEmpty(), onDismiss = { showAddDialog = false - editingIndex = -1 + editingIndex = null }, onConfirm = { newValue -> val normalizedValue = newValue.trim() @@ -168,7 +167,7 @@ private fun EditableTextSetScreen( } showAddDialog = false - editingIndex = -1 + editingIndex = null }, ) } From c28c10c867a2abf2b079472325c3dd51649b803a Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 15 May 2026 14:42:16 +0800 Subject: [PATCH 4/4] Use editingKey and editingValue --- .../settings/ui/EditableTextMapScreen.kt | 25 +++++++++---------- .../settings/ui/EditableTextSetScreen.kt | 24 ++++++++---------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt index 9ea010b499..2bc7e96b7f 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextMapScreen.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.AlertDialog import androidx.compose.material3.HorizontalDivider @@ -78,7 +78,7 @@ private fun EditableTextMapScreen( initialValues?.entries.orEmpty().map { it.toPair() }.toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } - var editingIndex by remember { mutableStateOf(null) } + var editingKey by remember { mutableStateOf(null) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -102,7 +102,7 @@ private fun EditableTextMapScreen( EmptyEditorContent(Modifier.weight(1f)) } else { LazyColumn(state = lazyListState, modifier = Modifier.weight(1f)) { - itemsIndexed(items = values, key = { _, entry -> entry.first }) { index, entry -> + items(items = values, key = { it.first }) { entry -> val (key, value) = entry ReorderableItem(reorderableLazyListState, key = key) { _ -> ListItem( @@ -110,7 +110,7 @@ private fun EditableTextMapScreen( supportingContent = { Text(value) }, modifier = Modifier.clickable { - editingIndex = index + editingKey = key showAddDialog = true }, leadingContent = { @@ -149,36 +149,35 @@ private fun EditableTextMapScreen( } if (showAddDialog) { - val editingEntry = editingIndex?.let(values::get) + val editingEntry = editingKey?.let { k -> values.firstOrNull { it.first == k } } MapEntryInputDialog( title = title, initialKey = editingEntry?.first.orEmpty(), initialValue = editingEntry?.second.orEmpty(), onDismiss = { showAddDialog = false - editingIndex = null + editingKey = null }, onConfirm = { key, valueText -> - val currentEditingIndex = editingIndex - - if (currentEditingIndex == null) { + if (editingKey == null) { val index = values.indexOfFirst { it.first == key } if (index >= 0) values[index] = key to valueText else values.add(key to valueText) } else { + val currentIdx = values.indexOfFirst { it.first == editingKey } val existingIndex = values.indexOfFirst { it.first == key } when { - existingIndex < 0 || existingIndex == currentEditingIndex -> { - values[currentEditingIndex] = key to valueText + existingIndex < 0 || existingIndex == currentIdx -> { + if (currentIdx >= 0) values[currentIdx] = key to valueText } else -> { values[existingIndex] = key to valueText - values.removeAt(currentEditingIndex) + if (currentIdx >= 0) values.removeAt(currentIdx) } } } showAddDialog = false - editingIndex = null + editingKey = null }, ) } diff --git a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt index aa1482def6..d90fdbec49 100644 --- a/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt +++ b/ui/settings/src/main/kotlin/com/github/kr328/clash/settings/ui/EditableTextSetScreen.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.AlertDialog import androidx.compose.material3.HorizontalDivider @@ -74,7 +74,7 @@ private fun EditableTextSetScreen( ) { val values = remember(initialValues) { initialValues.orEmpty().toMutableStateList() } var showAddDialog by remember { mutableStateOf(false) } - var editingIndex by remember { mutableStateOf(null) } + var editingValue by remember { mutableStateOf(null) } val lazyListState = rememberLazyListState() val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to -> @@ -98,13 +98,13 @@ private fun EditableTextSetScreen( EmptyEditorContent(Modifier.weight(1f)) } else { LazyColumn(state = lazyListState, modifier = Modifier.weight(1f)) { - itemsIndexed(items = values, key = { _, value -> value }) { index, value -> + items(items = values, key = { it }) { value -> ReorderableItem(reorderableLazyListState, key = value) { _ -> ListItem( headlineContent = { Text(value) }, modifier = Modifier.clickable { - editingIndex = index + editingValue = value showAddDialog = true }, leadingContent = { @@ -145,29 +145,27 @@ private fun EditableTextSetScreen( if (showAddDialog) { SingleTextInputDialog( title = title, - initialText = editingIndex?.let(values::get).orEmpty(), + initialText = editingValue.orEmpty(), onDismiss = { showAddDialog = false - editingIndex = null + editingValue = null }, onConfirm = { newValue -> val normalizedValue = newValue.trim() - val currentEditingIndex = editingIndex if (normalizedValue.isNotBlank()) { - if (currentEditingIndex == null) { + if (editingValue == null) { if (!values.contains(normalizedValue)) { values.add(normalizedValue) } - } else if ( - !values.contains(normalizedValue) || values[currentEditingIndex] == normalizedValue - ) { - values[currentEditingIndex] = normalizedValue + } else if (!values.contains(normalizedValue) || editingValue == normalizedValue) { + val idx = values.indexOf(editingValue) + if (idx >= 0) values[idx] = normalizedValue } } showAddDialog = false - editingIndex = null + editingValue = null }, ) }