From a1cfa2aa7a4cbcd81510e2bc5beb2f890583857d Mon Sep 17 00:00:00 2001 From: Eliot the Cougar Date: Mon, 21 Sep 2026 06:49:32 +0300 Subject: [PATCH] UI/UX: Add item names to deletion confirmations (#6202) * Name the targets in deletion confirmations Extract the visible named-confirmation wording from the accessibility assembly onto current upstream. Cover main-list profiles, standard and custom profile editors, policy groups, proxy-chain members, subscriptions, routing-rule editors, and asset sources. Reuse the six localized named variants in all nine catalogs; preserve existing generic strings and the already-named asset-file confirmation. Carry display names alongside existing GUIDs in main/subscription list confirmation requests, and retain the main target across recreation with its two-string saver. Use saved names in editors rather than uncommitted renames. Do not import accessibility semantics, action menus, live regions, the separate routing-list Delete button, or the proxy-chain stable-key migration. Validation: 72 JVM tests passed, including two deletion-target saver regressions; Play Store debug compilation and x86_64 assembly passed. An API 33 emulator verified all 54 compiled localized resources with Unicode and percent-containing names, the main profile confirmation across rotation, and subscription confirmation/cancel. No deletion was confirmed. Other editors, double-column menus, keyboard/D-pad, and TalkBack were not exercised. Original APK and rotation settings were restored. * Standardize named delete confirmation layout Center delete confirmation messages while retaining a single Text node for the question and target. Separate each item name with a blank line in all nine locales, including asset files, which previously embedded the filename in the question. Keep the title-free dialog and existing Cancel/Delete actions, and leave ordinary confirmation messages start-aligned. Bulk-delete prompts retain their collective wording. Validation: 72 JVM tests, Kotlin compilation and Play Store debug assembly passed. All 63 compiled localized templates preserved an exact Unicode/percent target. Emulator checks covered profile and asset message nodes, two action targets, cancellation and profile dialog recreation after rotation. Actual spoken TalkBack output and the full theme/input matrix were not verified. * Resolve named proxy-chain confirmations by stable member key Upstream #6200 replaced pending member indices with stable keys. The textual rebase of #6202 retained an out-of-scope index reference in the confirmation text, breaking compilation. Look up the current member name through memberKeys, using the same pending identity as deletion. Reordering no longer separates the displayed target from the member removed on confirmation. * Reuse deletion questions instead of duplicating named translations Let DeleteConfirmDialog append an optional item name after a blank line while keeping the complete message in one centered Text. Reuse the existing localized questions at all named deletion sites and remove six duplicate resource keys from all nine locales. Asset-file questions use the same shared layout. Unnamed and bulk prompts remain unchanged. Retain stable-key proxy-chain target lookup and the corrected Russian policy-group wording in the reused question. Validation: existing ServerDeleteTargetTest, all 79 JVM tests, x86_64 Play Store debug assembly and a Pixel 9 Pro emulator check passed. The profile dialog exposes one question/name text node plus Cancel/Delete, and canceling preserves the lab profiles. TalkBack was enabled for the smoke check; actual speech and the full device/theme/locale matrix were not independently verified. --- .../java/com/v2ray/ang/ui/compose/Dialog.kt | 15 ++++++++-- .../java/com/v2ray/ang/ui/main/MainDialogs.kt | 21 +++++++++++--- .../com/v2ray/ang/ui/main/MainImportMenu.kt | 4 +-- .../java/com/v2ray/ang/ui/main/MainScreen.kt | 13 +++++++-- .../com/v2ray/ang/ui/main/MainServerPager.kt | 6 ++-- .../ang/ui/routing/RoutingEditActivity.kt | 1 + .../v2ray/ang/ui/server/BaseServerActivity.kt | 1 + .../com/v2ray/ang/ui/server/ServerActivity.kt | 1 + .../ui/server/ServerCustomConfigActivity.kt | 1 + .../ang/ui/server/ServerGroupActivity.kt | 1 + .../ang/ui/server/ServerProxyChainActivity.kt | 2 ++ .../ang/ui/subscription/SubEditActivity.kt | 1 + .../ang/ui/subscription/SubSettingActivity.kt | 11 ++++--- .../ang/ui/userasset/UserAssetActivity.kt | 3 +- .../ang/ui/userasset/UserAssetUrlActivity.kt | 1 + .../app/src/main/res/values-ar/strings.xml | 2 +- .../app/src/main/res/values-bn/strings.xml | 2 +- .../src/main/res/values-bqi-rIR/strings.xml | 2 +- .../app/src/main/res/values-fa/strings.xml | 2 +- .../app/src/main/res/values-ru/strings.xml | 4 +-- .../app/src/main/res/values-vi/strings.xml | 2 +- .../src/main/res/values-zh-rCN/strings.xml | 2 +- .../src/main/res/values-zh-rTW/strings.xml | 2 +- V2rayNG/app/src/main/res/values/strings.xml | 2 +- .../ang/ui/main/ServerDeleteTargetTest.kt | 29 +++++++++++++++++++ 25 files changed, 102 insertions(+), 29 deletions(-) create mode 100644 V2rayNG/app/src/test/java/com/v2ray/ang/ui/main/ServerDeleteTargetTest.kt diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/Dialog.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/Dialog.kt index 775d7ab51..8252b0134 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/Dialog.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/compose/Dialog.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.v2ray.ang.R @@ -47,6 +48,7 @@ fun ConfirmDialog( confirmText: String = stringResource(R.string.action_ok), dismissText: String? = stringResource(R.string.action_cancel), confirmIcon: @Composable (() -> Unit)? = null, + messageTextAlign: TextAlign = TextAlign.Start, onConfirm: () -> Unit, onDismiss: () -> Unit ) { @@ -61,7 +63,14 @@ fun ConfirmDialog( AlertDialog( onDismissRequest = onDismiss, title = title?.let { { Text(it) } }, - text = { Text(message, style = MaterialTheme.typography.bodyMedium) }, + text = { + Text( + text = message, + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyMedium, + textAlign = messageTextAlign + ) + }, confirmButton = { TextButton( onClick = { onConfirm(); onDismiss() }, @@ -91,9 +100,11 @@ fun DeleteConfirmDialog( message: String, onConfirm: () -> Unit, onDismiss: () -> Unit, + itemName: String? = null, ) { ConfirmDialog( - message = message, + message = if (itemName.isNullOrBlank()) message else "$message\n\n$itemName", + messageTextAlign = TextAlign.Center, confirmText = stringResource(R.string.action_delete), confirmIcon = { Icon( diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainDialogs.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainDialogs.kt index 3eede6fbd..3cc69cf8b 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainDialogs.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainDialogs.kt @@ -1,10 +1,23 @@ package com.v2ray.ang.ui.main import androidx.compose.runtime.Composable +import androidx.compose.runtime.saveable.listSaver import androidx.compose.ui.res.stringResource import com.v2ray.ang.R import com.v2ray.ang.ui.compose.DeleteConfirmDialog +data class ServerDeleteTarget( + val guid: String, + val profileName: String, +) { + companion object { + val Saver = listSaver( + save = { target -> target?.let { listOf(it.guid, it.profileName) }.orEmpty() }, + restore = { ServerDeleteTarget(it[0], it[1]) }, + ) + } +} + @Composable fun MainDialogs( showDelAllConfirm: Boolean, @@ -16,7 +29,7 @@ fun MainDialogs( showDelInvalidConfirm: Boolean, onDismissDelInvalid: () -> Unit, onConfirmDelInvalid: () -> Unit, - showRemoveConfirm: String?, + showRemoveConfirm: ServerDeleteTarget?, onDismissRemove: () -> Unit, onConfirmRemove: (String) -> Unit, ) { @@ -41,11 +54,11 @@ fun MainDialogs( onDismiss = onDismissDelInvalid ) } - if (showRemoveConfirm != null) { - val guid = showRemoveConfirm + showRemoveConfirm?.let { target -> DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_profile), - onConfirm = { onConfirmRemove(guid) }, + itemName = target.profileName, + onConfirm = { onConfirmRemove(target.guid) }, onDismiss = onDismissRemove ) } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainImportMenu.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainImportMenu.kt index 92ff21ded..db17de274 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainImportMenu.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainImportMenu.kt @@ -79,7 +79,7 @@ fun ShareMethodDialog( more: Boolean, onDismiss: () -> Unit, onAction: (MainAction) -> Unit, - onRemove: (String) -> Unit, + onRemove: (String, String) -> Unit, ) { val menuActions = serverMenuActions( isComplexProfile = profile.configType.isComplexType(), @@ -95,7 +95,7 @@ fun ShareMethodDialog( ServerMenuAction.ShareClipboard -> onAction(MainAction.ShareClipboard(guid)) ServerMenuAction.ShareFullContent -> onAction(MainAction.ShareFullContent(guid)) ServerMenuAction.Edit -> onAction(MainAction.EditServer(guid, profile)) - ServerMenuAction.Delete -> onRemove(guid) + ServerMenuAction.Delete -> onRemove(guid, profile.remarks) } }, onDismiss = onDismiss diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt index 801d46c18..9dadc25b7 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier @@ -57,11 +58,17 @@ fun MainScreen( var showDelAllConfirm by remember { mutableStateOf(false) } var showDelDuplicateConfirm by remember { mutableStateOf(false) } var showDelInvalidConfirm by remember { mutableStateOf(false) } - var showRemoveConfirm by remember { mutableStateOf(null) } + var showRemoveConfirm by rememberSaveable(stateSaver = ServerDeleteTarget.Saver) { + mutableStateOf(null) + } var shareTarget by remember { mutableStateOf?>(null) } - val removeServer: (String) -> Unit = { guid -> - if (confirmRemove) showRemoveConfirm = guid else onAction(MainAction.RemoveServer(guid)) + val removeServer: (String, String) -> Unit = { guid, profileName -> + if (confirmRemove) { + showRemoveConfirm = ServerDeleteTarget(guid, profileName) + } else { + onAction(MainAction.RemoveServer(guid)) + } } val pagerState = rememberPagerState( diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt index 48f195d9e..d2f6c5dca 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/main/MainServerPager.kt @@ -75,7 +75,7 @@ fun GroupPagerPage( onEditServer: (String, ProfileItem) -> Unit, onShareServer: (String, ProfileItem) -> Unit, onMoreServer: (String, ProfileItem) -> Unit, - onRemoveServer: (String) -> Unit, + onRemoveServer: (String, String) -> Unit, contentPadding: PaddingValues ) { val groupStateFlow = remember(groupId) { @@ -121,7 +121,7 @@ private class ServerRowActions( val edit: (String, ProfileItem) -> Unit, val share: (String, ProfileItem) -> Unit, val more: (String, ProfileItem) -> Unit, - val remove: (String) -> Unit, + val remove: (String, String) -> Unit, ) @Composable @@ -374,7 +374,7 @@ private fun ServerListItem( Modifier.size(24.dp) ) } - IconButton(onClick = { actions.remove(row.guid) }, Modifier.size(36.dp)) { + IconButton(onClick = { actions.remove(row.guid, row.remarks) }, Modifier.size(36.dp)) { Icon( painterResource(R.drawable.ic_delete_24dp), stringResource(R.string.acc_delete), diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt index 9d428a4a5..e9ab0ae43 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/routing/RoutingEditActivity.kt @@ -309,6 +309,7 @@ fun RoutingEditScreen( if (showDeleteConfirm) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_routing_rule), + itemName = initial?.remarks.orEmpty(), onConfirm = onDelete, onDismiss = { showDeleteConfirm = false } ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt index de4cba5d8..ccde3bc8a 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/BaseServerActivity.kt @@ -491,6 +491,7 @@ abstract class BaseServerActivity : BaseComponentActivity() { if (showDeleteDialog) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_profile), + itemName = initialConfig.remarks, onConfirm = { showDeleteDialog = false deleteServer(editGuid) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerActivity.kt index f28f34cea..e70fad09d 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerActivity.kt @@ -519,6 +519,7 @@ fun ServerScreen( if (showDeleteDialog) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_profile), + itemName = initialConfig.remarks, onConfirm = { showDeleteDialog = false; onDelete() }, onDismiss = { showDeleteDialog = false } ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerCustomConfigActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerCustomConfigActivity.kt index 9fd33aa19..df6c37f0b 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerCustomConfigActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerCustomConfigActivity.kt @@ -486,6 +486,7 @@ fun ServerCustomConfigScreen( if (showDeleteConfirm) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_profile), + itemName = initialRemarks, onConfirm = { showDeleteConfirm = false onDelete() diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerGroupActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerGroupActivity.kt index 3b0723bf3..e4140071c 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerGroupActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerGroupActivity.kt @@ -306,6 +306,7 @@ fun ServerGroupScreen( if (showDeleteConfirm) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_policy_group), + itemName = initialRemarks, onConfirm = onDelete, onDismiss = { showDeleteConfirm = false } ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerProxyChainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerProxyChainActivity.kt index a0e1acc6d..3c72fedf7 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerProxyChainActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/server/ServerProxyChainActivity.kt @@ -337,6 +337,7 @@ fun ProxyChainScreen( if (showProfileDeleteConfirm) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_profile), + itemName = initialRemarks, onConfirm = { showProfileDeleteConfirm = false; onDelete() }, onDismiss = { showProfileDeleteConfirm = false } ) @@ -344,6 +345,7 @@ fun ProxyChainScreen( memberToDeleteKey?.let { memberKey -> DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_proxy_chain_member), + itemName = members.getOrNull(memberKeys.indexOf(memberKey)).orEmpty(), onConfirm = { val (remainingMembers, remainingKeys) = withoutProxyChainMember(members, memberKeys, memberKey) members = remainingMembers diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt index a2578a15e..d6a11d0e1 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubEditActivity.kt @@ -247,6 +247,7 @@ fun SubEditScreen( if (showDeleteConfirm) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_subscription_group), + itemName = initial.remarks, onConfirm = onDelete, onDismiss = { showDeleteConfirm = false } ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubSettingActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubSettingActivity.kt index 19b8d383b..d034d3f30 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubSettingActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/subscription/SubSettingActivity.kt @@ -61,6 +61,8 @@ private enum class SubscriptionShareAction(@StringRes val labelRes: Int) { Clipboard(R.string.share_subscription_clipboard) } +private data class SubscriptionDeleteTarget(val guid: String, val name: String) + class SubSettingActivity : BaseComponentActivity() { private val viewModel: SubscriptionsViewModel by viewModels() @@ -113,7 +115,7 @@ fun SubSettingScreen( ) { val subscriptions by viewModel.subsFlow.collectAsStateWithLifecycle() var showUpdateDialog by remember { mutableStateOf(false) } - var removeTarget by remember { mutableStateOf(null) } + var removeTarget by remember { mutableStateOf(null) } val confirmRemove = MmkvManager.decodeSettingsBool(AppConfig.PREF_CONFIRM_REMOVE, false) var shareTarget by remember { mutableStateOf?>(null) } @@ -212,7 +214,7 @@ fun SubSettingScreen( ) } IconButton(onClick = { - if (confirmRemove) removeTarget = subCache.guid + if (confirmRemove) removeTarget = SubscriptionDeleteTarget(subCache.guid, subCache.subscription.remarks) else onRemoveSub(subCache.guid) }) { Icon( @@ -268,12 +270,13 @@ fun SubSettingScreen( ) } - if (removeTarget != null) { + removeTarget?.let { target -> DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_subscription_group), + itemName = target.name, onConfirm = { - onRemoveSub(removeTarget!!) removeTarget = null + onRemoveSub(target.guid) }, onDismiss = { removeTarget = null } ) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetActivity.kt index ef87df6b9..b97f9c123 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetActivity.kt @@ -345,7 +345,8 @@ internal fun UserAssetScreen( deleteTarget?.let { asset -> DeleteConfirmDialog( - message = stringResource(R.string.confirm_delete_asset_file, asset.name), + message = stringResource(R.string.confirm_delete_asset_file), + itemName = asset.name, onConfirm = { deleteTarget = null onRemoveAsset(asset.guid, asset.name) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetUrlActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetUrlActivity.kt index e6f69dfab..fd8bb5265 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetUrlActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/userasset/UserAssetUrlActivity.kt @@ -197,6 +197,7 @@ fun UserAssetUrlScreen( if (showDeleteConfirm) { DeleteConfirmDialog( message = stringResource(R.string.confirm_delete_asset_source), + itemName = initialRemarks, onConfirm = onDelete, onDismiss = { showDeleteConfirm = false } ) diff --git a/V2rayNG/app/src/main/res/values-ar/strings.xml b/V2rayNG/app/src/main/res/values-ar/strings.xml index dfdb2fb91..ebeae87f1 100644 --- a/V2rayNG/app/src/main/res/values-ar/strings.xml +++ b/V2rayNG/app/src/main/res/values-ar/strings.xml @@ -50,7 +50,7 @@ هل أنت متأكد من أنك تريد حذف هذا العضو من سلسلة الوكلاء؟ هل أنت متأكد من أنك تريد حذف قاعدة التوجيه هذه؟ هل أنت متأكد من أنك تريد حذف مجموعة الاشتراك هذه؟ - هل أنت متأكد من أنك تريد حذف ملف الأصول \"%1$s\"؟ + هل أنت متأكد من أنك تريد حذف ملف الأصول هذا؟ هل أنت متأكد من أنك تريد حذف مصدر الأصول هذا؟ ملاحظات العنوان diff --git a/V2rayNG/app/src/main/res/values-bn/strings.xml b/V2rayNG/app/src/main/res/values-bn/strings.xml index 85e13f7fd..57756f7e7 100644 --- a/V2rayNG/app/src/main/res/values-bn/strings.xml +++ b/V2rayNG/app/src/main/res/values-bn/strings.xml @@ -50,7 +50,7 @@ আপনি কি এই প্রক্সি চেইন সদস্যটিকে মুছে ফেলতে চান? আপনি কি এই রাউটিং নিয়মটি মুছে ফেলতে চান? আপনি কি এই সাবস্ক্রিপশন গ্রুপটি মুছে ফেলতে চান? - আপনি কি \"%1$s\" অ্যাসেট ফাইলটি মুছে ফেলতে চান? + আপনি কি এই অ্যাসেট ফাইলটি মুছে ফেলতে চান? আপনি কি এই অ্যাসেট উৎসটি মুছে ফেলতে চান? মন্তব্য ঠিকানা diff --git a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml index 2e1053689..8b824b746 100644 --- a/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml +++ b/V2rayNG/app/src/main/res/values-bqi-rIR/strings.xml @@ -50,7 +50,7 @@ ای عوزو زنجیره پروکسی پاک ابۊ، هنی هم اخۊی پاکس کۊنی؟ ای قانووݩ تور جوستن پاک ابۊ، هنی هم اخۊی پاکس کۊنی؟ ای بونکۊ اشتراک پاک ابۊ، هنی هم اخۊی پاکس کۊنی؟ - فایل بونچک جغرافیایی «%1$s» پاک ابۊ، هنی هم اخۊی پاکس کۊنی؟ + ای فایل بونچک جغرافیایی پاک ابۊ، هنی هم اخۊی پاکس کۊنی؟ ای بونچک فایلا جغرافیایی پاک ابۊ، هنی هم اخۊی پاکس کۊنی؟ نیشتنا نشۊوی diff --git a/V2rayNG/app/src/main/res/values-fa/strings.xml b/V2rayNG/app/src/main/res/values-fa/strings.xml index 6a19ae11c..cb2211b6c 100644 --- a/V2rayNG/app/src/main/res/values-fa/strings.xml +++ b/V2rayNG/app/src/main/res/values-fa/strings.xml @@ -50,7 +50,7 @@ آیا مطمئن هستید که می‌خواهید این عضو زنجیره پراکسی را حذف کنید؟ آیا مطمئن هستید که می‌خواهید این قانون مسیریابی را حذف کنید؟ آیا مطمئن هستید که می‌خواهید این گروه اشتراک را حذف کنید؟ - آیا مطمئن هستید که می‌خواهید فایل دارایی \"%1$s\" را حذف کنید؟ + آیا مطمئن هستید که می‌خواهید این فایل دارایی را حذف کنید؟ آیا مطمئن هستید که می‌خواهید این منبع دارایی را حذف کنید؟ ملاحظات نشانی diff --git a/V2rayNG/app/src/main/res/values-ru/strings.xml b/V2rayNG/app/src/main/res/values-ru/strings.xml index 25c474111..0d5a61d02 100644 --- a/V2rayNG/app/src/main/res/values-ru/strings.xml +++ b/V2rayNG/app/src/main/res/values-ru/strings.xml @@ -46,11 +46,11 @@ Удалить все отображаемые дубликаты профилей? Выполните проверку перед удалением! Удалить все отображаемые нерабочие профили? Удалить этот профиль? - Удалить эту политику группы? + Удалить эту группу политик? Удалить этого участника цепочки прокси? Удалить это правило маршрутизации? Удалить эту подписку? - Удалить файл ресурсов «%1$s»? + Удалить этот файл ресурсов? Удалить этот источник ресурсов? Название Адрес diff --git a/V2rayNG/app/src/main/res/values-vi/strings.xml b/V2rayNG/app/src/main/res/values-vi/strings.xml index afefd5020..355b696a7 100644 --- a/V2rayNG/app/src/main/res/values-vi/strings.xml +++ b/V2rayNG/app/src/main/res/values-vi/strings.xml @@ -50,7 +50,7 @@ Bạn có chắc muốn xóa thành viên này khỏi chuỗi proxy không? Bạn có chắc muốn xóa quy tắc định tuyến này không? Bạn có chắc muốn xóa nhóm đăng ký này không? - Bạn có chắc muốn xóa tệp tài nguyên \"%1$s\" không? + Bạn có chắc muốn xóa tệp tài nguyên này không? Bạn có chắc muốn xóa nguồn tài nguyên này không? Tên cấu hình Địa chỉ diff --git a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml index a055ef59c..37e88f10b 100644 --- a/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rCN/strings.xml @@ -50,7 +50,7 @@ 确定要删除此代理链成员吗? 确定要删除此路由规则吗? 确定要删除此订阅组吗? - 确定要删除资源文件“%1$s”吗? + 确定要删除此资源文件吗? 确定要删除此资源来源吗? 别名 (remarks) 地址 (address) diff --git a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml index d816622dd..a2617856e 100644 --- a/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml +++ b/V2rayNG/app/src/main/res/values-zh-rTW/strings.xml @@ -50,7 +50,7 @@ 確定要刪除此代理鏈成員嗎? 確定要刪除此路由規則嗎? 確定要刪除此訂閱群組嗎? - 確定要刪除資源檔案「%1$s」嗎? + 確定要刪除此資源檔案嗎? 確定要刪除此資源來源嗎? 備註 位址 diff --git a/V2rayNG/app/src/main/res/values/strings.xml b/V2rayNG/app/src/main/res/values/strings.xml index a1bc80eb7..334689d55 100644 --- a/V2rayNG/app/src/main/res/values/strings.xml +++ b/V2rayNG/app/src/main/res/values/strings.xml @@ -50,7 +50,7 @@ Are you sure you want to delete this chain member? Are you sure you want to delete this routing rule? Are you sure you want to delete this subscription group? - Are you sure you want to delete the asset file \"%1$s\"? + Are you sure you want to delete this asset file? Are you sure you want to delete this asset source? remarks address diff --git a/V2rayNG/app/src/test/java/com/v2ray/ang/ui/main/ServerDeleteTargetTest.kt b/V2rayNG/app/src/test/java/com/v2ray/ang/ui/main/ServerDeleteTargetTest.kt new file mode 100644 index 000000000..3e2790ceb --- /dev/null +++ b/V2rayNG/app/src/test/java/com/v2ray/ang/ui/main/ServerDeleteTargetTest.kt @@ -0,0 +1,29 @@ +package com.v2ray.ang.ui.main + +import androidx.compose.runtime.saveable.SaverScope +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class ServerDeleteTargetTest { + @Test + fun pendingDeletionRestoresItsGuidAndDisplayName() { + val scope = SaverScope { it is String } + for (guid in listOf("first-guid", "second-guid")) { + for (name in listOf("Same name", "", "Профиль\n服务器 100%")) { + val target = ServerDeleteTarget(guid, name) + val saved = with(ServerDeleteTarget.Saver) { scope.save(target) } + + assertEquals(listOf(guid, name), saved) + assertEquals(target, ServerDeleteTarget.Saver.restore(requireNotNull(saved))) + } + } + } + + @Test + fun noPendingDeletionHasNoSavedTarget() { + val saved = with(ServerDeleteTarget.Saver) { SaverScope { it is String }.save(null) } + + assertNull(saved) + } +}