Skip to content

UI/fix: Add direct routing-rule deletion and prevent duplicate-ID crashes - #6201

Open
eliotcougar wants to merge 2 commits into
2dust:masterfrom
eliotcougar:feat/routing-rule-list-delete
Open

eliotcougar wants to merge 2 commits into
2dust:masterfrom
eliotcougar:feat/routing-rule-list-delete

Conversation

@eliotcougar

Copy link
Copy Markdown
Contributor

Summary

Add a Delete button beside Edit in routing-rule rows, with the existing localized confirmation dialog. Harden rule identities so this new list action and the already-keyed Compose list remain safe with legacy data and repeated imports containing locked rules.

This is a standalone, non-accessibility extraction based on current master (2020807c255b76b09c9ced4255c95600250aef48). It does not restore the reverted ID-based editor migration.

Background: the reverted change and closed PRs

The duplicate-ID problem is not limited to the reverted editor: the routing list still uses rule IDs as Compose/reorderable keys. A normal export/import round trip retains the current locked rules and imports their exported copies again. Repairing only empty IDs does not prevent duplicate nonempty keys, and merely assigning new IDs to every imported copy still allows locked-rule contents to multiply on successive imports. Duplicate names themselves are valid and must not be treated as duplicate identities.

This PR addresses those data/key prerequisites while leaving the current Add/Edit and toggle contracts in place.

Addition: delete directly from the list

  • Reuse the existing Delete icon label and routing-rule confirmation text; no new translation keys are needed.
  • Remember the pending rule ID across activity recreation and resolve that ID against stored rules when confirming. A changed list position therefore does not redirect deletion to a neighbor.
  • Run deletion on Dispatchers.IO; remove the visible row only after storage completes successfully. A missing target does not delete another rule, and write failures are logged and reported using the existing failure message.
  • Keep explicit deletion of locked rules available, as it already is in the editor. The lock protects a rule during import, not against the user's confirmed deletion.

Fixes and advantages

  1. Unique, persisted identities before composition. Repair absent, null, blank, and repeated IDs when writing routing lists and before exposing them to the keyed UI. Preserve the first valid occurrence, list order, and every rule's contents. Reserve all original IDs before generating replacements, retry collisions, and persist the repaired list in one write instead of per-row saves. If persistence fails, do not expose temporary replacement IDs.
  2. Stable repeated imports without deleting user rules. Retain all existing locked rules, including duplicates already present. Skip imported copies matching their complete contents apart from ID, since IDs may have changed after an older export. Different rules with matching names or IDs are retained and assigned distinct identities where needed; this is not title-based deduplication.
  3. No collision with list headers. Use a non-string header key, so a valid imported string ID such as domain_strategy cannot collide with non-rule content.
  4. Consistent loading and failure handling. Load on IO, publish on the calling UI coroutine, and discard reload results superseded by newer list changes. Serialize identity repair and ID deletion with routing writes within the process. Check repair/deletion write results instead of reporting false success.
  5. A smaller migration boundary. Keep upstream's working Add/Edit flow; add bounds checks for obsolete index-based lookup/deletion requests. Raw core-routing reads remain unchanged: failure to persist UI identities does not turn usable routing contents into an empty core configuration. No editor ViewModel rewrite or general conversion of edit/toggle operations to IDs is included.

The user-facing benefit is fewer navigation steps for deletion. The correctness benefit is that the new action has an unambiguous target and the list can safely display ordinary legacy/imported data. The implementation is independent of the accessibility PRs and can be reviewed without their semantics, custom actions, or localization changes.

Validation

  • 87 Play Store debug JVM tests passed, including 17 routing identity/deletion tests. Coverage includes legacy JSON, generated-ID collisions, repeated locked exports, distinct rules with identical titles/IDs, persistence failures, missing targets, changed stored order, last-rule deletion, stale reloads versus toggle/reorder, and Add/out-of-range operations.
  • :app:compilePlaystoreDebugKotlin and :app:assemblePlaystoreDebug -PABI_FILTERS=x86_64 passed; git diff --check passed.
  • Android 13/API 33 Pixel 6a emulator: opened a fixture containing two identical locked rules with the same domain_strategy ID plus null/blank/missing IDs; confirmed five unique persisted identities and no list crash. Exported through the UI and reimported twice, then imported the pre-repair export three times through the normal settings import method. Counts and retained locked IDs remained stable.
  • On the final revision, Add stayed open, Save added a rule, and list deletion removed it. Deleting the second identical locked rule removed exactly its repaired ID and retained the first; a runtime storage probe verified the result. No v2rayNG crash appeared in the crash buffer.
  • Before the identity-hardening follow-up, the direct-delete addition also passed touch, Cancel, keyboard Enter, D-pad center, and confirmation-across-rotation checks. These were not all repeated after hardening.

Not run: physical devices/other Android versions, TalkBack speech, full theme matrices, QR-scanner/preset-import UI, F-Droid builds, and concurrent/cross-process mutation stress. Clipboard, QR, and preset imports share the tested merge/write path; the in-process synchronization is not a cross-process transaction guarantee. No new Android instrumentation-test source folder is included.

No import/export fields are renamed or removed, no native dependencies change, and no accessibility-specific behavior is bundled.

@eliotcougar eliotcougar changed the title Add direct routing-rule deletion and prevent duplicate-ID crashes UI/fix: Add direct routing-rule deletion and prevent duplicate-ID crashes Sep 6, 2026
Place Delete next to Edit and reuse the existing routing-rule confirmation dialog. Keep the pending rule ID across recreation and resolve that ID against stored rules when confirming, rather than retaining a list position. Perform removal on IO and update the visible list after it completes; log exceptions and show the existing failure message.

Extract the list-deletion flow from the accessibility assembly without importing custom actions, semantic changes, new dialog wording, or edit/toggle/reordering refactors.

Validation: 75 JVM tests passed, including five deletion regressions; Play Store debug Kotlin compilation and x86_64 assembly passed. Emulator checks covered touch, keyboard Enter, D-pad center, cancel, rotation, and persisted deletion by rule ID. Original emulator rules and APK were restored. TalkBack speech and concurrent-mutation stress tests were not run.
Prevent legacy missing, null, blank, and repeated rule IDs from reaching keyed routing rows. Reserve existing IDs before generating replacements, persist repaired identities before exposing them to the UI, and keep header keys separate from imported string IDs. Normalize every routing-list write without changing raw core-routing reads.

Retain existing locked rules without appending their exact exported copies, comparing contents independently of repaired IDs. Preserve genuinely different rules and existing duplicate contents. Serialize migration and ID deletion with routing writes within the process, respect failed writes, and discard stale reload results after newer list changes.

Keep the upstream index-based Add/Edit flow rather than restoring the reverted migration; guard obsolete lookup and removal positions. This covers the duplicate-key and Add regressions discussed in closed PRs 2dust#6171 and 2dust#6185 without taking their editor refactor.

Validation: all 87 Play Store debug JVM tests, Kotlin compilation, and x86_64 assembly passed. Emulator checks covered legacy duplicate/missing IDs, two UI export/reimport cycles, three old-export imports, Add/Save, and deleting only the intended locked duplicate. Original emulator rules and APK were restored. Physical-device, TalkBack speech, QR/preset UI, and cross-process stress checks were not run.
@eliotcougar
eliotcougar force-pushed the feat/routing-rule-list-delete branch from 0fd27a3 to 858fc9b Compare September 10, 2026 07:41
@2dust

2dust commented Sep 11, 2026

Copy link
Copy Markdown
Owner

routing 数量也不多,就保持现有的逻辑吧,不要改了

@eliotcougar

Copy link
Copy Markdown
Contributor Author

routing 数量也不多,就保持现有的逻辑吧,不要改了

It isn't necessary. I agree. It was done only to make all item lists follow the same logic. For uniformity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants