From 48749d216ab1ab5c3ea65d3414309ebaee67e9fc Mon Sep 17 00:00:00 2001 From: lukstbit <52494258+lukstbit@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:09:20 +0300 Subject: [PATCH 1/4] Promote find and replace dialog to production This menu option will be available for both states in browser: - when the user didn't select anything -> 'Selected notes only' will be disabled - when the user selected something -> 'Selected notes only' will be available to be toggled --- .../com/ichi2/anki/browser/CardBrowserFragment.kt | 13 ++++++++----- .../anki/ui/internationalization/SentenceCase.kt | 3 +++ AnkiDroid/src/main/res/menu/card_browser.xml | 5 +++-- .../src/main/res/menu/card_browser_multiselect.xml | 5 +++-- AnkiDroid/src/main/res/values/preferences.xml | 1 - .../main/res/xml/preferences_developer_options.xml | 5 ----- .../test/java/com/ichi2/anki/CardBrowserTest.kt | 14 +++++++------- 7 files changed, 24 insertions(+), 22 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserFragment.kt index 1fe5741aa6f4..8f1bdc4f2e88 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserFragment.kt @@ -416,6 +416,8 @@ class CardBrowserFragment : Timber.d("onCreateMenu()") menuInflater.inflate(R.menu.card_browser, menu) menu.findItem(R.id.action_search_by_flag).subMenu?.setupFlags() + // note: this menu item is available with and without a selection of items + menu.findItem(R.id.action_find_replace)?.title = TR.sentenceCase.findAndReplace if (!useSearchView) { searchItem = menu.findItem(R.id.action_search) @@ -562,6 +564,10 @@ class CardBrowserFragment : showFilteredDeckScreen() return true } + R.id.action_find_replace -> { + showFindAndReplaceDialog() + return true + } } return false @@ -594,11 +600,8 @@ class CardBrowserFragment : menu.findItem(R.id.action_reschedule_cards).title = TR.sentenceCase.setDueDate menu.findItem(R.id.action_grade_now).title = TR.sentenceCase.gradeNow - val isFindReplaceEnabled = sharedPrefs().getBoolean(getString(R.string.pref_browser_find_replace), false) - menu.findItem(R.id.action_find_replace).apply { - isVisible = isFindReplaceEnabled - title = TR.browsingFindAndReplace().toSentenceCase(R.string.sentence_find_and_replace) - } + // note: this menu item is available with and without a selection of items + menu.findItem(R.id.action_find_replace)?.title = TR.sentenceCase.findAndReplace menu.findItem(R.id.action_undo).setupUndo() diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt index 5b685bd8638e..cda5f0a6c38d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/internationalization/SentenceCase.kt @@ -127,6 +127,9 @@ object SentenceCase { context(_: Fragment) val toggleSuspend get() = TR.browsingToggleSuspend().toSentenceCase(R.string.sentence_toggle_suspend) + + context(_: Fragment) + val findAndReplace get() = TR.browsingFindAndReplace().toSentenceCase(R.string.sentence_find_and_replace) } /** diff --git a/AnkiDroid/src/main/res/menu/card_browser.xml b/AnkiDroid/src/main/res/menu/card_browser.xml index 28ec4182a7cf..9218b6902503 100644 --- a/AnkiDroid/src/main/res/menu/card_browser.xml +++ b/AnkiDroid/src/main/res/menu/card_browser.xml @@ -76,6 +76,7 @@ + app:showAsAction="never" + tools:title="Find and replace" + /> \ No newline at end of file diff --git a/AnkiDroid/src/main/res/menu/card_browser_multiselect.xml b/AnkiDroid/src/main/res/menu/card_browser_multiselect.xml index 616a3e7389b8..dc21cef52ee6 100644 --- a/AnkiDroid/src/main/res/menu/card_browser_multiselect.xml +++ b/AnkiDroid/src/main/res/menu/card_browser_multiselect.xml @@ -86,8 +86,9 @@ + app:showAsAction="never" + tools:title="Find and replace" + /> debug_corrupt_fsrs_params new_congrats_screen newReviewerOptions - browserFindReplace newReviewReminders devOptionsEnabledByUser workInProgressDevOptions diff --git a/AnkiDroid/src/main/res/xml/preferences_developer_options.xml b/AnkiDroid/src/main/res/xml/preferences_developer_options.xml index 90dbc3a1d432..0e86a57c92fb 100644 --- a/AnkiDroid/src/main/res/xml/preferences_developer_options.xml +++ b/AnkiDroid/src/main/res/xml/preferences_developer_options.xml @@ -111,11 +111,6 @@ android:summary="Not compatible with side-by-side editor" android:key="@string/dev_card_browser_search_view" android:defaultValue="false"/> - Date: Tue, 31 Mar 2026 12:38:37 +0300 Subject: [PATCH 2/4] Add minor cleanups to FindAndReplaceFragment --- .../com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt index c951d0fe5a4f..42e9adb11a0e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt @@ -99,6 +99,8 @@ class FindAndReplaceDialogFragment : AnalyticsDialogFragment() { } } + // TODO maybe get rid of the html tags that come with the backend strings and handle the + // sentence case for TR.browsingReplaceWith() private fun setupLabels() { binding.labelFind.text = HtmlCompat.fromHtml(TR.browsingFind(), HtmlCompat.FROM_HTML_MODE_LEGACY) @@ -218,7 +220,10 @@ class FindAndReplaceDialogFragment : AnalyticsDialogFragment() { ): FindAndReplaceDialogFragment { val file = IdsFile(context.cacheDir, noteIds, "find-replace") return FindAndReplaceDialogFragment().apply { - arguments = bundleOf(ARG_IDS to file) + arguments = + Bundle().apply { + putParcelable(ARG_IDS, file) + } } } } From 9c2a1563a426d15e19ba4213383a37521222c2e9 Mon Sep 17 00:00:00 2001 From: lukstbit <52494258+lukstbit@users.noreply.github.com> Date: Wed, 1 Apr 2026 11:14:06 +0300 Subject: [PATCH 3/4] Add warning label to FindAndReplaceFragment Inform the user that he could/should do a backup. --- .../main/res/layout/fragment_find_replace.xml | 17 ++++++++++++++++- AnkiDroid/src/main/res/values/02-strings.xml | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/res/layout/fragment_find_replace.xml b/AnkiDroid/src/main/res/layout/fragment_find_replace.xml index c32f6e6f3957..c40e8675110f 100644 --- a/AnkiDroid/src/main/res/layout/fragment_find_replace.xml +++ b/AnkiDroid/src/main/res/layout/fragment_find_replace.xml @@ -22,14 +22,29 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="16dp"> + + + Touch “%2$s” to confirm adding “%1$s” Existing tag “%1$s” selected Tag already exists + Consider backing up your collection from DeckPicker before using this option! From 11557b99692d98cff81110ddde8903bf39c666ad Mon Sep 17 00:00:00 2001 From: lukstbit <52494258+lukstbit@users.noreply.github.com> Date: Fri, 24 Apr 2026 20:51:01 +0300 Subject: [PATCH 4/4] Add preference upgrade class to remove find and replace dev setting --- .../ichi2/anki/servicelayer/PreferenceUpgradeService.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt index aaeea9119c7e..0fc74353b483 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/PreferenceUpgradeService.kt @@ -137,6 +137,7 @@ object PreferenceUpgradeService { yield(UpgradeToggleBacksideOnlyControl()) yield(UpgradeThemes()) yield(UpgradeAnswerControls()) + yield(RemoveDeveloperFindReplace()) } /** Returns a list of preference upgrade classes which have not been applied */ @@ -900,6 +901,14 @@ object PreferenceUpgradeService { } } } + + internal class RemoveDeveloperFindReplace : PreferenceUpgrade(28) { + override fun upgrade(preferences: SharedPreferences) { + preferences.edit { + remove("browserFindReplace") + } + } + } } }