diff --git a/.github/tests/milestone.test.cjs b/.github/tests/milestone.test.cjs
index 5949cc554e40..e322f6fc9589 100644
--- a/.github/tests/milestone.test.cjs
+++ b/.github/tests/milestone.test.cjs
@@ -28,7 +28,7 @@ function fixture({ milestoneError, updateError, issues = [] } = {}) {
getMilestone: async args => {
reads.push(args);
if (milestoneError) throw milestoneError;
- return { data: { number: 77 } };
+ return { data: { number: 79 } };
},
update: async args => {
updates.push(args);
@@ -54,14 +54,14 @@ test('a missing milestone skips all PR and issue writes', async () => {
assert.deepEqual(f.updates, []);
assert.deepEqual(f.queries, []);
assert.equal(f.reads.length, 1);
- assert.ok(f.messages.some(message => /77/.test(message)));
+ assert.ok(f.messages.some(message => /79/.test(message)));
});
test('an existing milestone updates the actual PR and its local linked issues', async () => {
const f = fixture({ issues: [{ number: 12, repository: { nameWithOwner: 'float3/Anki-Android' } }] });
await f.run();
- assert.deepEqual(f.reads, [{ owner: 'float3', repo: 'Anki-Android', milestone_number: 77 }]);
- assert.deepEqual(f.updates, [7, 12].map(issue_number => ({ ...f.context.repo, issue_number, milestone: 77 })));
+ assert.deepEqual(f.reads, [{ owner: 'float3', repo: 'Anki-Android', milestone_number: 79 }]);
+ assert.deepEqual(f.updates, [7, 12].map(issue_number => ({ ...f.context.repo, issue_number, milestone: 79 })));
assert.equal(f.queries[0].variables._url, f.context.payload.pull_request.html_url);
assert.match(f.queries[0].query, /repository\s*\{\s*nameWithOwner\s*\}/);
});
diff --git a/.github/with_emulator_log.sh b/.github/with_emulator_log.sh
new file mode 100644
index 000000000000..6605f1a151f0
--- /dev/null
+++ b/.github/with_emulator_log.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+set -euo pipefail
+
+log_file=$1
+shift
+
+# Fail before starting the test command if we cannot capture its logs.
+: >> "$log_file"
+
+stop_process() {
+ kill "$1" 2>/dev/null || true
+ wait "$1" 2>/dev/null || true
+}
+
+capture_logcat() {
+ local child_pid=
+ trap 'exit 0' INT TERM
+ trap 'stop_process "$child_pid"' EXIT
+
+ # ADB can disconnect after boot, ending logcat before the tests start (Issue 22115).
+ # Reconnect even on a clean exit. Replaying the buffer also recovers events emitted
+ # while disconnected; the test reporter deduplicates ignored/failed events.
+ while true; do
+ # Background both commands so TERM interrupts wait, including during the delay.
+ adb logcat '*:D' >> "$log_file" &
+ child_pid=$!
+ wait "$child_pid" || true
+ echo 'Emulator logcat exited; reconnecting in 1 second.' >&2
+ sleep 1 &
+ child_pid=$!
+ wait "$child_pid" || true
+ done
+}
+
+capture_logcat &
+capture_pid=$!
+# Stop both the retry loop and its child before returning the test command's status.
+trap 'stop_process "$capture_pid"' EXIT
+"$@"
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index 36a6bbc036f1..a6051a7cbbdf 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -11,13 +11,14 @@ following scripts before submitting a PR.
Alternately, you may run the actions on your fork of `Anki-Android`.
-| **Job** | **Command** | **Comments** |
-|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|-----------------------------------------------------------|
-| [Lint (Kotlin)](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/lint.yml) | `./gradlew lintAll ktLintCheck lint-rules:test --daemon` | Android lint rules, formatting and tests for lint rules |
-| [Lint (JavaScript)](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/lint.yml) | See script | Prettier, lint & code formatting |
-| [Unit Tests](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/tests_unit.yml) | `./gradlew jacocoUnitTestReport --daemon` | Unit tests for the Android Project |
+| **Job** | **Command** | **Comments** |
+|------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Lint (Kotlin)](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/lint.yml) | `./gradlew lintAll ktLintCheck lint-rules:test --daemon` | Android lint rules, formatting and tests for lint rules |
+| [Lint (JavaScript)](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/lint.yml) | See script | Prettier, lint & code formatting |
+| [Build Logic](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/lint.yml) | `./gradlew -p buildSrc test` and `tools/validate-version-code.sh` | Version-code bump checks |
+| [Unit Tests](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/tests_unit.yml) | `./gradlew jacocoUnitTestReport --daemon` | Unit tests for the Android Project |
| [Emulator Tests](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/tests_emulator.yml) | `TEST_RELEASE_BUILD=true ./gradlew jacocoAndroidTestReport --daemon` | Emulator tests for the Android Project.
CI runs `bash .github/disable_art_metrics.sh` and `adb shell settings put global hide_error_dialogs 1` first |
-| [CodeQL](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/codeql.yml) | N/A | GitHub-only check.
[Docs](https://codeql.github.com/) |
+| [CodeQL](https://github.com/ankidroid/Anki-Android/blob/main/.github/workflows/codeql.yml) | N/A | GitHub-only check.
[Docs](https://codeql.github.com/) |
## Other Workflows
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 1f6c1854e51c..b95542719c11 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -26,7 +26,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
- fetch-depth: 1
+ fetch-depth: 2
- name: Configure JDK
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
@@ -47,6 +47,15 @@ jobs:
# Comment this and the with: above out for performance testing on a branch
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
+ - name: Test build logic
+ run: ./gradlew -p buildSrc test --daemon
+
+ # Reject versionCode decreases and increases over 100,000.
+ - name: Validate versionCode
+ env:
+ VERSION_CODE_BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
+ run: tools/validate-version-code.sh "$VERSION_CODE_BASE"
+
- name: Warm Gradle Cache
# This makes sure we fetch gradle network resources with a retry
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
diff --git a/.github/workflows/milestone.yml b/.github/workflows/milestone.yml
index f80fd084b4c7..00b536ef8c9a 100644
--- a/.github/workflows/milestone.yml
+++ b/.github/workflows/milestone.yml
@@ -16,7 +16,7 @@ jobs:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
- const milestone = 77;
+ const milestone = 79;
try {
await github.rest.issues.getMilestone({
...context.repo,
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 05a0ab5573bd..2adaf6bd1313 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -28,6 +28,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ fetch-depth: 2
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
name: Gradle Cache
diff --git a/.github/workflows/tests_emulator.yml b/.github/workflows/tests_emulator.yml
index 7efb3ac6c300..2f62d5f8de6e 100644
--- a/.github/workflows/tests_emulator.yml
+++ b/.github/workflows/tests_emulator.yml
@@ -222,13 +222,12 @@ jobs:
# update workflows/README.md when modifying this script
script: |
touch adb-log.txt
- $ANDROID_HOME/platform-tools/adb logcat '*:D' >> adb-log.txt &
bash .github/disable_art_metrics.sh
# Fix "Speech Recognition and Synthesis from Google keeps stopping" causing RootViewWithoutFocusException
bash .github/adb_retry.sh shell settings put global hide_error_dialogs 1
bash .github/adb_retry.sh emu screenrecord start --time-limit 1800 video.webm
sleep 5
- ./gradlew uninstallAll jacocoAndroidTestReport --daemon
+ bash .github/with_emulator_log.sh adb-log.txt ./gradlew uninstallAll jacocoAndroidTestReport --daemon
- name: Report Emulator Test Results
if: always() && hashFiles('AnkiDroid/build/outputs/androidTest-results/connected/**/TEST-*.xml') != ''
diff --git a/.github/workflows/tests_unit.yml b/.github/workflows/tests_unit.yml
index 989a1bc4d091..60d628eb8479 100644
--- a/.github/workflows/tests_unit.yml
+++ b/.github/workflows/tests_unit.yml
@@ -83,6 +83,7 @@ jobs:
// Goal is to run enough iterations on all systems that we have confidence there are no flakes
if (context.eventName === "schedule") {
const iterationCount = 15
+ iterationArray = []
for (let i = 1; i <= iterationCount; i++) {
iterationArray.push(i);
}
diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle
index 398512d0163f..4c106abb254a 100644
--- a/AnkiDroid/build.gradle
+++ b/AnkiDroid/build.gradle
@@ -1,3 +1,5 @@
+import com.ichi2.anki.gradle.VersionCodeTasks
+
plugins {
// Gradle plugin portal
alias(libs.plugins.tripletPlay)
@@ -116,7 +118,7 @@ android {
//
// This ensures the correct ordering between the various types of releases (dev < alpha < beta < release) which is
// needed for upgrades to be offered correctly.
- versionCode=22500202
+ versionCode=22600100
// Quest tags have their own monotonic upgrade sequence, independent of
// the upstream beta version. Keep the eight-digit ABI-split budget.
// quest- is 5nnnn000; nightly builds after it add their commit count,
@@ -136,7 +138,7 @@ android {
}
}
// If you change this to a new version, you probably also want to update .gradle/workflows/milestone.yml for the new version...
- versionName="2.25.0beta2"
+ versionName="2.26.0alpha0"
minSdk = libs.versions.minSdk.get().toInteger()
// After #13695: change .tests_emulator.yml
@@ -395,6 +397,15 @@ android {
}
}
+// register `validateVersionCode` with our (potentially new versionCode).
+androidComponents.finalizeDsl { androidDsl ->
+ def validation = VersionCodeTasks.registerValidationTask(project, androidDsl.defaultConfig.versionCode)
+ // handle all 'publish' calls, even if run directly
+ tasks.matching { it.name.startsWith("publish") }.configureEach {
+ dependsOn(validation)
+ }
+}
+
play {
serviceAccountCredentials.set(file("${homePath}/src/AnkiDroid-GCP-Publish-Credentials.json"))
track.set('alpha')
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/DeckPickerTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/DeckPickerTest.kt
index 8934159a6657..9374f58c9d43 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/DeckPickerTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/DeckPickerTest.kt
@@ -1,18 +1,5 @@
-/*
- * Copyright (c) 2021 Shridhar Goel
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2021 Shridhar Goel
package com.ichi2.anki
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/TestUtils.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/TestUtils.kt
index 74aa8b3f9c80..8682de330239 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/TestUtils.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/TestUtils.kt
@@ -1,18 +1,6 @@
-/*
- * Copyright (c) 2021 Shridhar Goel
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2021 Shridhar Goel
+
package com.ichi2.anki
import android.app.Activity
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/CollectionPathMismatchRecoveryTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/CollectionPathMismatchRecoveryTest.kt
index 9374bffe74f9..12a8f21ee4fa 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/CollectionPathMismatchRecoveryTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/CollectionPathMismatchRecoveryTest.kt
@@ -13,8 +13,8 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
-import com.ichi2.anki.R
import com.ichi2.anki.common.preferences.sharedPrefs
import com.ichi2.anki.common.storage.CollectionHelper
import com.ichi2.anki.startup.getDefaultAnkiDroidDirectory
@@ -61,14 +61,14 @@ class CollectionPathMismatchRecoveryTest : InstrumentedTest() {
@Test
fun errorHandlingShowsUseDefaultFolderOption() {
- onView(withText(R.string.open_collection_failed_title))
+ onView(withText(CommonString.open_collection_failed_title))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.error_handling_options)).inRoot(isDialog()).perform(click())
- onView(withText(R.string.error_handling_title))
+ onView(withText(CommonString.error_handling_options)).inRoot(isDialog()).perform(click())
+ onView(withText(CommonString.error_handling_title))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.backup_use_default_folder))
+ onView(withText(CommonString.backup_use_default_folder))
.inRoot(isDialog())
.check(matches(isDisplayed()))
}
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/DatabaseCorruptDialogTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/DatabaseCorruptDialogTest.kt
index dc93f9006267..668f7255784d 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/DatabaseCorruptDialogTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/DatabaseCorruptDialogTest.kt
@@ -11,6 +11,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.backend.DatabaseCorruption
@@ -55,7 +56,7 @@ class DatabaseCorruptDialogTest : InstrumentedTest() {
@Test
fun testOpenCollectionFailedDialog() {
- onView(withText(R.string.open_collection_failed_title))
+ onView(withText(CommonString.open_collection_failed_title))
.inRoot(isDialog())
.check(matches(isDisplayed()))
}
@@ -64,7 +65,7 @@ class DatabaseCorruptDialogTest : InstrumentedTest() {
fun testCorruptCollectionDialog() {
val corruptMsg =
testContext.getString(
- R.string.corrupt_db_message,
+ CommonString.corrupt_db_message,
testContext.getString(R.string.repair_deck),
)
onView(withText(corruptMsg))
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenuTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenuTest.kt
index d88d58435187..fc1b5a23b0a9 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenuTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenuTest.kt
@@ -1,18 +1,6 @@
-/*
- * Copyright (c) 2022 lukstbit
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2022 lukstbit
+
package com.ichi2.anki.dialogs
import android.os.Bundle
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt
index 296051cbd112..ae0ca7d6ba09 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/preferences/PreferencesNavigationTest.kt
@@ -23,6 +23,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.CommonString
import com.ichi2.anki.IntentHandler
import com.ichi2.anki.R
import com.ichi2.anki.testutil.GrantStoragePermission
@@ -82,9 +83,9 @@ class PreferencesNavigationTest {
openSettings()
onView(withId(com.bytehamster.lib.preferencesearch.R.id.search)).perform(click())
onView(allOf(withId(com.bytehamster.lib.preferencesearch.R.id.search), hasFocus())).perform(typeText("Card"))
- onView(withText(R.string.card_zoom)).perform(click())
+ onView(withText(CommonString.card_zoom)).perform(click())
onView(withId(R.id.settings_container)).check(matches(isDisplayed()))
- onView(withText(R.string.notification_pref)).perform(click())
+ onView(withText(CommonString.notification_pref)).perform(click())
pressBack()
onView(withClassName(endsWith("PreferencesActivity"))).check(doesNotExist())
}
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ACRATest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ACRATest.kt
index b285a85a1671..a414f17acfbe 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ACRATest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ACRATest.kt
@@ -10,7 +10,7 @@ import androidx.core.content.edit
import androidx.test.annotation.UiThreadTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.AnkiDroidApp
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.acraCoreConfigBuilder
import com.ichi2.anki.analytics.AnalyticsExceptionHandler
import com.ichi2.anki.analytics.AnkiDroidUsageAnalytics
@@ -111,7 +111,7 @@ class ACRATest : InstrumentedTest() {
setProductionACRAConfig(sharedPrefs)
verifyACRANotDisabled()
- assertToastMessage(R.string.feedback_for_manual_toast_text)
+ assertToastMessage(CommonString.feedback_for_manual_toast_text)
assertToastIsEnabled()
assertDialogEnabledStatus("Dialog should be enabled", true)
}
@@ -188,7 +188,7 @@ class ACRATest : InstrumentedTest() {
setProductionACRAConfig(sharedPrefs)
verifyACRANotDisabled()
- assertToastMessage(R.string.feedback_auto_toast_text)
+ assertToastMessage(CommonString.feedback_auto_toast_text)
assertToastIsEnabled()
assertDialogEnabledStatus("Dialog should not be enabled", false)
}
@@ -204,12 +204,12 @@ class ACRATest : InstrumentedTest() {
verifyACRANotDisabled()
assertDialogEnabledStatus("dialog should be disabled when status is ALWAYS", false)
- assertToastMessage(R.string.feedback_auto_toast_text)
+ assertToastMessage(CommonString.feedback_auto_toast_text)
setAcraReportingMode(FEEDBACK_REPORT_ASK)
assertDialogEnabledStatus("dialog should be re-enabled after changed to ASK", true)
- assertToastMessage(R.string.feedback_for_manual_toast_text)
+ assertToastMessage(CommonString.feedback_for_manual_toast_text)
}
@Test
@@ -222,11 +222,11 @@ class ACRATest : InstrumentedTest() {
setProductionACRAConfig(sharedPrefs)
verifyACRANotDisabled()
- assertToastMessage(R.string.feedback_for_manual_toast_text)
+ assertToastMessage(CommonString.feedback_for_manual_toast_text)
setAcraReportingMode(FEEDBACK_REPORT_ALWAYS)
- assertToastMessage(R.string.feedback_auto_toast_text)
+ assertToastMessage(CommonString.feedback_auto_toast_text)
}
@Test
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/CollectionTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/CollectionTest.kt
index 14e040787cbc..24699ca5947c 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/CollectionTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/CollectionTest.kt
@@ -1,18 +1,6 @@
-/*
- * Copyright (c) 2015 Timothy Rae
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2015 Timothy Rae
+
package com.ichi2.anki.tests
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt
index c2e0184ffd9c..ebf64add99c1 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt
@@ -1,18 +1,5 @@
-/*
- * Copyright (c) 2020 Mike Hardy
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2020 Mike Hardy
package com.ichi2.anki.tests
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/Shared.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/Shared.kt
index 47239169c66d..9f104ec2a93f 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/Shared.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/Shared.kt
@@ -1,18 +1,6 @@
-/*
- * Copyright (c) 2014 Houssam Salem
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2014 Houssam Salem
+
package com.ichi2.anki.tests
import android.annotation.SuppressLint
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/MediaTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/MediaTest.kt
index bd2041b2f4c4..6cb40e63f10d 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/MediaTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/MediaTest.kt
@@ -1,18 +1,6 @@
-/*
- * Copyright (c) 2014 Houssam Salem
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2014 Houssam Salem
+
package com.ichi2.anki.tests.libanki
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/NotetypeTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/NotetypeTest.kt
index 19b2cb9389bc..8e065836efe9 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/NotetypeTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/NotetypeTest.kt
@@ -1,19 +1,5 @@
-/*
- *
- * Copyright (c) 2020 Arthur Milchior
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2020 Arthur Milchior
package com.ichi2.anki.tests.libanki
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/RetryRule.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/RetryRule.kt
index d669fecbda9d..197dca614985 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/RetryRule.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/libanki/RetryRule.kt
@@ -1,19 +1,6 @@
-/*
- *
- * Copyright (c) 2018 Mike Hardy
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2018 Mike Hardy
+
package com.ichi2.anki.tests.libanki
import org.junit.rules.TestRule
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/DeckPicker.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/DeckPicker.kt
index df5502c02f1f..5349b53595f0 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/DeckPicker.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/DeckPicker.kt
@@ -1,18 +1,6 @@
-/*
- * Copyright (c) 2024 Arthur Milchior
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2024 Arthur Milchior
+
package com.ichi2.anki.testutil
import android.annotation.SuppressLint
@@ -25,6 +13,7 @@ import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.TestUtils.clickChildViewWithId
import com.ichi2.anki.tests.checkWithTimeout
@@ -39,7 +28,7 @@ fun closeGetStartedScreenIfExists() {
}
fun closeBackupCollectionDialogIfExists() {
- onView(withText(R.string.button_backup_later))
+ onView(withText(CommonString.button_backup_later))
.withFailureHandler { _, _ -> }
.perform(click())
}
@@ -63,7 +52,7 @@ fun createDeckWithUniqueName(): String {
onView(withId(R.id.fab_main)).perform(click())
onView(withId(R.id.add_deck_button)).perform(click())
onView(withId(R.id.dialog_text_input)).perform(typeText(deckName))
- onView(withText(R.string.dialog_ok)).perform(click())
+ onView(withText(CommonString.dialog_ok)).perform(click())
return deckName
}
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/IntroductionSlide.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/IntroductionSlide.kt
index 43c5d486f31e..be89daa4df15 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/IntroductionSlide.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/IntroductionSlide.kt
@@ -1,18 +1,5 @@
-/*
- * Copyright (c) 2024 Arthur Milchior
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2024 Arthur Milchior
package com.ichi2.anki.testutil
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/StudyOptionsFragment.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/StudyOptionsFragment.kt
index f836caf83d82..5cbed83e8946 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/StudyOptionsFragment.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/testutil/StudyOptionsFragment.kt
@@ -1,18 +1,5 @@
-/*
- * Copyright (c) 2024 Arthur Milchior
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3 of the License, or (at your option) any later
- * version.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- * PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program. If not, see .
- */
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2024 Arthur Milchior
package com.ichi2.anki.testutil
diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/utils/AssetHelperTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/utils/AssetHelperTest.kt
index eee5afcd0381..6d168b22540b 100644
--- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/utils/AssetHelperTest.kt
+++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/utils/AssetHelperTest.kt
@@ -1,18 +1,6 @@
-/*
- Copyright (c) 2021 Piyush Goel
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2021 Piyush Goel
- This program is free software; you can redistribute it and/or modify it under
- the terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3 of the License, or (at your option) any later
- version.
-
- This program is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
- PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- this program. If not, see .
- */
package com.ichi2.anki.utils
import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt
index 85b09a1f79ed..67c448947a0d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.kt
@@ -821,16 +821,16 @@ abstract class AbstractFlashcardViewer :
protected fun showDeleteNoteDialog() {
Timber.i("Displaying 'delete note' dialog")
AlertDialog.Builder(this).show {
- title(R.string.delete_card_title)
+ title(CommonString.delete_card_title)
setIcon(R.drawable.ic_warning)
message(
text =
resources.getString(
- R.string.delete_note_message,
+ CommonString.delete_note_message,
stripHTMLAndSpecialFields(currentCard!!.question(getColUnsafe, true)).trim(),
),
)
- positiveButton(R.string.dialog_positive_delete) {
+ positiveButton(CommonString.dialog_positive_delete) {
Timber.i(
"AbstractFlashcardViewer:: OK button pressed to delete note %d",
currentCard!!.nid,
@@ -838,7 +838,7 @@ abstract class AbstractFlashcardViewer :
launchCatchingTask { stopCardMediaPlayer() }
deleteNoteWithoutConfirmation()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
@@ -854,12 +854,12 @@ abstract class AbstractFlashcardViewer :
}
val deletedMessage =
resources.getQuantityString(
- R.plurals.card_browser_cards_deleted,
+ CommonPlurals.card_browser_cards_deleted,
noteCount,
noteCount,
)
showSnackbar(deletedMessage, Snackbar.LENGTH_LONG) {
- setAction(R.string.undo) { launchCatchingTask { undoAndShowSnackbar() } }
+ setAction(CommonString.undo) { launchCatchingTask { undoAndShowSnackbar() } }
}
}
}
@@ -1613,7 +1613,7 @@ abstract class AbstractFlashcardViewer :
}
}
stopCardMediaPlayer()
- showSnackbar(R.string.card_buried, Reviewer.ACTION_SNACKBAR_TIME)
+ showSnackbar(CommonString.card_buried, Reviewer.ACTION_SNACKBAR_TIME)
}
return true
}
@@ -1642,7 +1642,7 @@ abstract class AbstractFlashcardViewer :
}
}
val count = changed.count
- val noteSuspended = resources.getQuantityString(R.plurals.note_suspended, count, count)
+ val noteSuspended = resources.getQuantityString(CommonPlurals.note_suspended, count, count)
stopCardMediaPlayer()
showSnackbar(noteSuspended, Reviewer.ACTION_SNACKBAR_TIME)
}
@@ -1895,12 +1895,12 @@ abstract class AbstractFlashcardViewer :
showSnackbar(
getString(
- R.string.show_answer_hint_long_press,
- getString(R.string.pref_show_answer_long_press_time),
+ CommonString.show_answer_hint_long_press,
+ getString(CommonString.pref_show_answer_long_press_time),
),
minimalClickSpeed + Reviewer.ACTION_SNACKBAR_TIME,
) {
- setAction(R.string.settings) {
+ setAction(CommonString.settings) {
navigate(PreferencesDestination.Accessibility)
}
}
@@ -2471,13 +2471,13 @@ abstract class AbstractFlashcardViewer :
}
if (url.startsWith("missing-user-action:")) {
val actionNumber = url.substringAfter(":")
- val message = getString(R.string.missing_user_action_dialog_message, actionNumber)
+ val message = getString(CommonString.missing_user_action_dialog_message, actionNumber)
Timber.i("showing 'missing user action' dialog")
AlertDialog.Builder(this@AbstractFlashcardViewer).show {
- setTitle(R.string.vague_error)
+ setTitle(CommonString.vague_error)
setMessage(message)
- setPositiveButton(R.string.dialog_ok) { _, _ -> }
- setNeutralButton(R.string.help) { _, _ ->
+ setPositiveButton(CommonString.dialog_ok) { _, _ -> }
+ setNeutralButton(CommonString.help) { _, _ ->
openUrl(R.string.link_user_actions_help)
}
}
@@ -2638,7 +2638,7 @@ abstract class AbstractFlashcardViewer :
startActivity(intent)
} catch (_: ActivityNotFoundException) {
Timber.w("No app found to handle open external url from AbstractFlashcardViewer")
- showSnackbar(R.string.activity_start_failed)
+ showSnackbar(CommonString.activity_start_failed)
}
return true
}
@@ -2703,7 +2703,7 @@ abstract class AbstractFlashcardViewer :
Timber.e(e, "Exception decoding: '%s'", url)
showThemedToast(
this@AbstractFlashcardViewer,
- getString(R.string.card_viewer_url_decode_error),
+ getString(CommonString.card_viewer_url_decode_error),
true,
)
}
@@ -2715,8 +2715,8 @@ abstract class AbstractFlashcardViewer :
}
internal fun displayCouldNotFindMediaSnackbar(filename: String?) {
- showSnackbar(getString(R.string.card_viewer_could_not_find_image, filename)) {
- setAction(R.string.help) { openUrl(R.string.link_faq_missing_media) }
+ showSnackbar(getString(CommonString.card_viewer_could_not_find_image, filename)) {
+ setAction(CommonString.help) { openUrl(R.string.link_faq_missing_media) }
}
}
@@ -2864,7 +2864,7 @@ abstract class AbstractFlashcardViewer :
is AndroidTtsError.MissingVoiceError ->
TtsPlaybackErrorDialog.ttsPlaybackErrorDialog(activity, supportFragmentManager, error.tag)
is AndroidTtsError.InvalidVoiceError ->
- activity.showSnackbar(getString(R.string.voice_not_supported))
+ activity.showSnackbar(getString(CommonString.voice_not_supported))
else -> activity.showSnackbar(error.localizedErrorMessage(activity))
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AcraCrashReporter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AcraCrashReporter.kt
index 1721ce5faefa..f09e090f34b6 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/AcraCrashReporter.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/AcraCrashReporter.kt
@@ -70,8 +70,8 @@ private object AcraCrashReporter : CrashReporter {
private enum class ToastType(
@StringRes private val toastMessageRes: Int,
) {
- AUTO_TOAST(R.string.feedback_auto_toast_text),
- MANUAL_TOAST(R.string.feedback_for_manual_toast_text),
+ AUTO_TOAST(CommonString.feedback_auto_toast_text),
+ MANUAL_TOAST(CommonString.feedback_for_manual_toast_text),
;
fun getToastMessage(context: Context) = context.getString(toastMessageRes)
@@ -114,10 +114,10 @@ private object AcraCrashReporter : CrashReporter {
.withPluginConfigurations(
DialogConfigurationBuilder()
.withReportDialogClass(AnkiDroidCrashReportDialog::class.java)
- .withCommentPrompt(application.getString(R.string.empty_string))
- .withTitle(application.getString(R.string.feedback_title))
- .withText(application.getString(R.string.feedback_default_text))
- .withPositiveButtonText(application.getString(R.string.feedback_report))
+ .withCommentPrompt(application.getString(CommonString.empty_string))
+ .withTitle(application.getString(CommonString.feedback_title))
+ .withText(application.getString(CommonString.feedback_default_text))
+ .withPositiveButtonText(application.getString(CommonString.feedback_report))
.withResIcon(R.drawable.logo_star_144dp)
.withEnabled(dialogEnabled)
.build(),
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AndroidTtsPlayer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AndroidTtsPlayer.kt
index a7f19d5df1fe..03a94c8c82fe 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/AndroidTtsPlayer.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/AndroidTtsPlayer.kt
@@ -299,7 +299,7 @@ fun TtsPlayer.TtsError.localizedErrorMessage(context: Context): String =
// TODO: Do we want a human readable string here as well - snackbar has limited room
// but developerString is currently not translated as it returns
// developerString: ERROR_NETWORK_TIMEOUT, so "Audio error (ERROR_NETWORK_TIMEOUT)"
- context.getString(R.string.tts_voices_playback_error_new, this.developerString)
+ context.getString(CommonString.tts_voices_playback_error_new, this.developerString)
} else {
this.toString()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
index 702ea1c6559b..8a2691a03cac 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.kt
@@ -297,7 +297,7 @@ open class AnkiActivity(
}
protected fun onSdCardNotMounted() {
- showThemedToast(this, resources.getString(R.string.sd_card_not_mounted), false)
+ showThemedToast(this, resources.getString(CommonString.sd_card_not_mounted), false)
finish()
}
@@ -472,7 +472,7 @@ open class AnkiActivity(
*/
open fun openUrl(url: Uri) {
if (!AdaptionUtil.hasWebBrowser(this)) {
- showSnackbar(getString(R.string.no_browser_msg, url.toString()))
+ showSnackbar(getString(CommonString.no_browser_msg, url.toString()))
return
}
val toolbarColor = MaterialColors.getColor(this, CommonR.attr.appBarColor, 0)
@@ -691,9 +691,9 @@ open class AnkiActivity(
val generalShortcutGroup =
ShortcutGroup(
listOf(
- shortcut("Ctrl+Z", R.string.undo),
+ shortcut("Ctrl+Z", CommonString.undo),
),
- R.string.pref_cat_general,
+ CommonString.pref_cat_general,
).toShortcutGroup(this)
return listOfNotNull(shortcuts?.toShortcutGroup(this), generalShortcutGroup)
@@ -718,7 +718,7 @@ open class AnkiActivity(
val attachment = File(path)
if (!attachment.exists()) {
Timber.e("Specified apkg file %s does not exist", path)
- showThemedToast(this, resources.getString(R.string.apk_share_error), false)
+ showThemedToast(this, resources.getString(CommonString.apk_share_error), false)
return
}
val authority = "${this.packageName}.apkgfileprovider"
@@ -729,7 +729,7 @@ open class AnkiActivity(
FileProvider.getUriForFile(this, authority, attachment)
} catch (e: IllegalArgumentException) {
Timber.e(e, "Could not generate a valid URI for the apkg file")
- showThemedToast(this, resources.getString(R.string.apk_share_error), false)
+ showThemedToast(this, resources.getString(CommonString.apk_share_error), false)
return
}
val targetMimeType = if (asText) "text/plain" else "application/apkg"
@@ -739,10 +739,10 @@ open class AnkiActivity(
.IntentBuilder(this)
.setType(targetMimeType)
.setStream(uri)
- .setSubject(getString(R.string.export_email_subject, attachment.name))
+ .setSubject(getString(CommonString.export_email_subject, attachment.name))
.setHtmlText(
getString(
- R.string.export_email_text,
+ CommonString.export_email_text,
getString(R.string.link_manual),
getString(R.string.link_distributions),
),
@@ -754,13 +754,13 @@ open class AnkiActivity(
val shareFileIntent =
Intent.createChooser(
sendIntent,
- getString(R.string.export_share_title),
+ getString(CommonString.export_share_title),
)
if (shareFileIntent.resolveActivity(packageManager) != null) {
startActivity(shareFileIntent)
} else {
// Try to save it?
- showSnackbar(R.string.export_send_no_handlers)
+ showSnackbar(CommonString.export_send_no_handlers)
saveExportFile(path)
}
}
@@ -770,7 +770,7 @@ open class AnkiActivity(
val attachment = File(exportPath)
if (!attachment.exists()) {
Timber.e("saveExportFile() Specified apkg file %s does not exist", exportPath)
- showSnackbar(R.string.export_save_apkg_unsuccessful)
+ showSnackbar(CommonString.export_save_apkg_unsuccessful)
return
}
@@ -790,22 +790,22 @@ open class AnkiActivity(
saveFileLauncher.launch(saveIntent)
} catch (ex: ActivityNotFoundException) {
Timber.w(ex, "No activity found to handle saveExportFile request")
- showSnackbar(R.string.activity_start_failed)
+ showSnackbar(CommonString.activity_start_failed)
}
}
private fun saveFileCallback(result: ActivityResult) {
launchCatchingTask {
- withProgress(getString(R.string.export_saving_exported_collection)) {
+ withProgress(getString(CommonString.export_saving_exported_collection)) {
val isSuccessful =
withContext(Dispatchers.IO) {
exportToProvider(result.data!!)
}
if (isSuccessful) {
- showSnackbar(R.string.export_save_apkg_successful, Snackbar.LENGTH_SHORT)
+ showSnackbar(CommonString.export_save_apkg_successful, Snackbar.LENGTH_SHORT)
} else {
- showSnackbar(R.string.export_save_apkg_unsuccessful)
+ showSnackbar(CommonString.export_save_apkg_unsuccessful)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt
index f8688fd9f4cd..be4d699e2429 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.kt
@@ -53,6 +53,7 @@ import com.ichi2.anki.logging.RobolectricDebugTree
import com.ichi2.anki.logging.logActivityCreation
import com.ichi2.anki.model.FieldFilters.NoSuggestFilter
import com.ichi2.anki.multimedia.MultimediaArgsStorage
+import com.ichi2.anki.multiprofile.ProfileManager
import com.ichi2.anki.navigation.initializeNavigator
import com.ichi2.anki.observability.ChangeManager
import com.ichi2.anki.preferences.SharedPreferencesProvider
@@ -80,6 +81,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
import timber.log.Timber.DebugTree
+import java.io.File
import java.util.Locale
/**
@@ -125,6 +127,42 @@ open class AnkiDroidApp :
}
}
+ private var profileManager: ProfileManager? = null
+
+ private val profileContext: Context?
+ get() = profileManager?.activeProfileContext
+
+ /**
+ * Runs before [onCreate] and before any ContentProvider, which is what the
+ * profile environment needs: WebView data directories must be set before any
+ * WebView exists, and every preference read must already be namespaced.
+ */
+ override fun attachBaseContext(base: Context) {
+ super.attachBaseContext(base)
+ if (isAcraSenderProcess()) return
+ profileManager = ProfileManager.createOrNull(base)
+ }
+
+ override fun getSharedPreferences(
+ name: String,
+ mode: Int,
+ ): SharedPreferences = profileContext?.getSharedPreferences(name, mode) ?: super.getSharedPreferences(name, mode)
+
+ override fun getFilesDir(): File = profileContext?.filesDir ?: super.getFilesDir()
+
+ override fun getCacheDir(): File = profileContext?.cacheDir ?: super.getCacheDir()
+
+ override fun getCodeCacheDir(): File = profileContext?.codeCacheDir ?: super.getCodeCacheDir()
+
+ override fun getNoBackupFilesDir(): File = profileContext?.noBackupFilesDir ?: super.getNoBackupFilesDir()
+
+ override fun getDatabasePath(name: String): File = profileContext?.getDatabasePath(name) ?: super.getDatabasePath(name)
+
+ override fun getDir(
+ name: String,
+ mode: Int,
+ ): File = profileContext?.getDir(name, mode) ?: super.getDir(name, mode)
+
/**
* On application creation, i.e. when the application process starts.
* This is called before any activities, services, or receivers are created.
@@ -175,9 +213,12 @@ open class AnkiDroidApp :
Timber.d("Skipping AnkiDroidApp.onCreate from ACRA sender process")
return
}
+ ProfileManager.attachError?.let {
+ Timber.w(it, "Failed to load the profile environment, running on the base context")
+ }
launchCacheCleanup()
if (AdaptionUtil.isUserATestClient) {
- showThemedToast(this.applicationContext, getString(R.string.user_is_a_robot), false)
+ showThemedToast(this.applicationContext, getString(CommonString.user_is_a_robot), false)
}
setWebContentsDebuggingEnabled(Prefs.isWebDebugEnabled)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidJsAPI.kt b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidJsAPI.kt
index 74acdaa2f2c6..fd5f9224c038 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidJsAPI.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidJsAPI.kt
@@ -115,7 +115,7 @@ open class AnkiDroidJsAPI(
} catch (j: JSONException) {
Timber.w(j)
activity.runOnUiThread {
- activity.showSnackbar(context.getString(R.string.invalid_json_data, j.localizedMessage))
+ activity.showSnackbar(context.getString(CommonString.invalid_json_data, j.localizedMessage))
}
}
return null
@@ -133,12 +133,12 @@ open class AnkiDroidJsAPI(
errorCode: Int,
apiDevContact: String,
) {
- val errorMsg: String = context.getString(R.string.anki_js_error_code, errorCode)
- val snackbarMsg: String = context.getString(R.string.api_version_developer_contact, apiDevContact, errorMsg)
+ val errorMsg: String = context.getString(CommonString.anki_js_error_code, errorCode)
+ val snackbarMsg: String = context.getString(CommonString.api_version_developer_contact, apiDevContact, errorMsg)
activity.showSnackbar(snackbarMsg, Snackbar.LENGTH_INDEFINITE) {
setMaxLines(3)
- setAction(R.string.reviewer_invalid_api_version_visit_documentation) {
+ setAction(CommonString.reviewer_invalid_api_version_visit_documentation) {
activity.openUrl("https://github.com/ankidroid/Anki-Android/wiki")
}
}
@@ -154,7 +154,7 @@ open class AnkiDroidJsAPI(
try {
if (apiDevContact.isEmpty() || apiVer.isEmpty()) {
activity.runOnUiThread {
- activity.showSnackbar(context.getString(R.string.invalid_json_data, ""))
+ activity.showSnackbar(context.getString(CommonString.invalid_json_data, ""))
}
return false
}
@@ -172,13 +172,13 @@ open class AnkiDroidJsAPI(
}
versionSupplied.isLowerThan(versionCurrent) -> {
activity.runOnUiThread {
- activity.showSnackbar(context.getString(R.string.update_js_api_version, apiDevContact))
+ activity.showSnackbar(context.getString(CommonString.update_js_api_version, apiDevContact))
}
versionSupplied.isHigherThanOrEquivalentTo(Version.parse(AnkiDroidJsAPIConstants.MINIMUM_JS_API_VERSION))
}
else -> {
activity.runOnUiThread {
- activity.showSnackbar(context.getString(R.string.valid_js_api_version, apiDevContact))
+ activity.showSnackbar(context.getString(CommonString.valid_js_api_version, apiDevContact))
}
false
}
@@ -568,7 +568,7 @@ open class AnkiDroidJsAPI(
.map { withCol { getCard(it.cardOrNoteId) } }
} catch (_: Exception) {
activity.webView!!.evaluateJavascript(
- "console.log('${context.getString(R.string.search_card_js_api_no_results)}')",
+ "console.log('${context.getString(CommonString.search_card_js_api_no_results)}')",
null,
)
showDeveloperContact(AnkiDroidJsAPIConstants.ANKI_JS_ERROR_CODE_SEARCH_CARD, apiContract.cardSuppliedDeveloperContact)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt b/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt
index d37f250de099..a4a746c3c50a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/BottomNavController.kt
@@ -16,6 +16,11 @@ import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.common.annotations.NeedsTest
import timber.log.Timber
+private const val DECK_PICKER_SCREEN_NAME = "DeckPicker"
+private const val CARD_BROWSER_SCREEN_NAME = "CardBrowser"
+private const val STATISTICS_SCREEN_NAME = "Statistics"
+private const val MORE_SCREEN_NAME = "MoreFragment"
+
/**
* Controls bottom navigation bar behaviour.
*
@@ -55,19 +60,20 @@ class BottomNavController(
/** Fragment tag used to find and reuse this destination's fragment across tab switches. */
val tag: String,
@StringRes val shortcutLabel: Int,
+ val analyticsScreenName: String,
) {
- HOME(R.id.nav_home, "home", R.string.deck_picker_group),
- BROWSER(R.id.nav_browser, "browser", R.string.card_browser_context_menu),
- STATS(R.id.nav_stats, "stats", R.string.open_statistics),
- MORE(R.id.nav_more, "more", R.string.bottom_nav_more),
+ HOME(R.id.nav_home, "home", CommonString.deck_picker_group, DECK_PICKER_SCREEN_NAME),
+ BROWSER(R.id.nav_browser, "browser", CommonString.card_browser_context_menu, CARD_BROWSER_SCREEN_NAME),
+ STATS(R.id.nav_stats, "stats", CommonString.open_statistics, STATISTICS_SCREEN_NAME),
+ MORE(R.id.nav_more, "more", CommonString.bottom_nav_more, MORE_SCREEN_NAME),
;
fun title(context: Context): String =
when (this) {
HOME -> TR.actionsDecks()
- BROWSER -> context.getString(R.string.bottom_nav_browse)
+ BROWSER -> context.getString(CommonString.bottom_nav_browse)
STATS -> TR.statisticsTitle()
- MORE -> context.getString(R.string.bottom_nav_more)
+ MORE -> context.getString(CommonString.bottom_nav_more)
}
companion object {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
index 51d9aa03593f..bffb0b738e02 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt
@@ -151,14 +151,14 @@ open class CardBrowser :
when (saveSearchResult) {
SaveSearchResult.ALREADY_EXISTS ->
showSnackbar(
- R.string.card_browser_list_my_searches_new_search_error_dup,
+ CommonString.card_browser_list_my_searches_new_search_error_dup,
Snackbar.LENGTH_SHORT,
)
SaveSearchResult.SUCCESS -> {
searchView!!.setQuery("", false)
mySearchesItem!!.isVisible = true
showSnackbar(
- R.string.card_browser_list_my_searches_successful_save,
+ CommonString.card_browser_list_my_searches_successful_save,
Snackbar.LENGTH_SHORT,
)
}
@@ -256,13 +256,8 @@ open class CardBrowser :
registerDeckSelectedHandler(action = ::onDeckSelected)
registerFindReplaceHandler { result ->
launchCatchingTask {
- withProgress {
- val count =
- withProgress {
- viewModel.findAndReplace(result)
- }.await()
- showSnackbar(TR.browsingNotesUpdated(count))
- }
+ val count = viewModel.findAndReplace(result).await()
+ showSnackbar(TR.browsingNotesUpdated(count))
}
}
registerSavedSearchActionHandler { type, searchName ->
@@ -385,12 +380,12 @@ open class CardBrowser :
private fun showSaveChangesDialog(destination: NoteEditorDestination) {
DiscardChangesDialog.showDialog(
context = this,
- positiveButtonText = this.getString(R.string.save),
- negativeButtonText = this.getString(R.string.discard),
+ positiveButtonText = this.getString(CommonString.save),
+ negativeButtonText = this.getString(CommonString.discard),
// The neutral button allows the user to back out of the action,
// e.g., if they accidentally triggered a navigation or card selection.
- neutralButtonText = this.getString(R.string.dialog_cancel),
- message = this.getString(R.string.save_changes_message),
+ neutralButtonText = this.getString(CommonString.dialog_cancel),
+ message = this.getString(CommonString.save_changes_message),
positiveMethod = {
launchCatchingTask {
fragment?.saveNote()
@@ -751,7 +746,7 @@ suspend fun searchForRows(
/** Renders the row count for [this] event as a localized "X cards/notes" string. */
fun SearchState.Completed.formatCardCount(resources: android.content.res.Resources): String =
resources.getQuantityString(
- if (cardsOrNotes == CARDS) R.plurals.card_browser_subtitle else R.plurals.card_browser_subtitle_notes_mode,
+ if (cardsOrNotes == CARDS) CommonPlurals.card_browser_subtitle else CommonPlurals.card_browser_subtitle_notes_mode,
rowCount,
rowCount,
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateBrowserAppearanceEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateBrowserAppearanceEditor.kt
index 6a7cb68bde4a..3f3e446b9b55 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateBrowserAppearanceEditor.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateBrowserAppearanceEditor.kt
@@ -71,7 +71,7 @@ class CardTemplateBrowserAppearanceEditor : AnkiActivity(R.layout.activity_card_
}
val bundle = savedInstanceState ?: intent.extras
if (bundle == null) {
- showThemedToast(this, getString(R.string.something_wrong), true)
+ showThemedToast(this, getString(CommonString.something_wrong), true)
finish()
return
}
@@ -128,11 +128,11 @@ class CardTemplateBrowserAppearanceEditor : AnkiActivity(R.layout.activity_card_
private fun showRestoreDefaultDialog() {
AlertDialog.Builder(this).show {
setTitle(TR.sentenceCase.restoreToDefault)
- positiveButton(R.string.restore) {
+ positiveButton(CommonString.restore) {
restoreDefaultAndClose()
}
- negativeButton(R.string.dialog_cancel)
- message(R.string.card_template_browser_appearance_restore_default_dialog)
+ negativeButton(CommonString.dialog_cancel)
+ message(CommonString.card_template_browser_appearance_restore_default_dialog)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt
index c0a8a8b631d5..a9385bc6370b 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.kt
@@ -389,7 +389,7 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
// Set activity title
supportActionBar?.let {
- it.setTitle(R.string.title_activity_template_editor)
+ it.setTitle(CommonString.title_activity_template_editor)
it.subtitle = tempNoteType!!.notetype.name
}
// Close collection opening dialog if needed
@@ -444,7 +444,7 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
require(deck is SelectableDeck.Deck?)
if (tempNoteType!!.notetype.isCloze) {
Timber.w("Attempted to set deck for cloze note type")
- showSnackbar(getString(R.string.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
+ showSnackbar(getString(CommonString.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
return
}
@@ -453,7 +453,7 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
if (deck != null && getColUnsafe.decks.isFiltered(deck.deckId)) {
Timber.w("Attempted to set default deck of %s to dynamic deck %s", templateName, deck.name)
- showSnackbar(getString(R.string.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
+ showSnackbar(getString(CommonString.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
return
}
@@ -461,11 +461,11 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
if (deck == null) {
Timber.i("Removing default template from template '%s'", templateName)
template.jsonObject.put("did", JSONObject.NULL)
- getString(R.string.model_manager_deck_override_removed_message, templateName)
+ getString(CommonString.model_manager_deck_override_removed_message, templateName)
} else {
Timber.i("Setting template '%s' to '%s'", templateName, deck.name)
template.jsonObject.put("did", deck.deckId)
- getString(R.string.model_manager_deck_override_added_message, templateName, deck.name)
+ getString(CommonString.model_manager_deck_override_added_message, templateName, deck.name)
}
showSnackbar(message, Snackbar.LENGTH_SHORT)
@@ -587,20 +587,20 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
get() =
ShortcutGroup(
listOf(
- shortcut("Ctrl+P", R.string.card_editor_preview_card),
- shortcut("Ctrl+1", R.string.edit_front_template),
- shortcut("Ctrl+2", R.string.edit_back_template),
- shortcut("Ctrl+3", R.string.edit_styling),
- shortcut("Ctrl+S", R.string.save),
- shortcut("Ctrl+I", R.string.card_template_editor_insert_field),
+ shortcut("Ctrl+P", CommonString.card_editor_preview_card),
+ shortcut("Ctrl+1", CommonString.edit_front_template),
+ shortcut("Ctrl+2", CommonString.edit_back_template),
+ shortcut("Ctrl+3", CommonString.edit_styling),
+ shortcut("Ctrl+S", CommonString.save),
+ shortcut("Ctrl+I", CommonString.card_template_editor_insert_field),
shortcut("Ctrl+A", Translations::cardTemplatesAddCardType),
shortcut("Ctrl+R", Translations::cardTemplatesRenameCardType),
- shortcut("Ctrl+B", R.string.edit_browser_appearance),
+ shortcut("Ctrl+B", CommonString.edit_browser_appearance),
shortcut("Ctrl+D", Translations::cardTemplatesRemoveCardType),
shortcut("Ctrl+O", Translations::cardTemplatesDeckOverride),
- shortcut("Ctrl+M", R.string.copy_the_template),
+ shortcut("Ctrl+M", CommonString.copy_the_template),
),
- R.string.card_template_editor_group,
+ CommonString.card_template_editor_group,
)
class CardTemplateFragment : Fragment(R.layout.fragment_card_template_editor_template) {
@@ -844,7 +844,7 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
if (currentEditorViewId != R.id.styling_edit) {
// 10644: Do not pass in a R.string as the final parameter as MIUI on Android 12 crashes.
- menu.add(Menu.FIRST, insertFieldId, 0, getString(R.string.card_template_editor_insert_field))
+ menu.add(Menu.FIRST, insertFieldId, 0, getString(CommonString.card_template_editor_insert_field))
}
return initialSize != menu.size
@@ -1030,7 +1030,7 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
val template = tempModel!!.getTemplate(ordinal)
// Don't do anything if only one template
if (tempModel.templateCount < 2) {
- templateEditor.showSimpleMessageDialog(resources.getString(R.string.card_template_editor_cant_delete))
+ templateEditor.showSimpleMessageDialog(resources.getString(CommonString.card_template_editor_cant_delete))
return@launch
}
@@ -1058,8 +1058,8 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
val builder =
AlertDialog
.Builder(requireContext())
- .setTitle(R.string.orphan_note_title)
- .setMessage(R.string.orphan_note_message)
+ .setTitle(CommonString.orphan_note_title)
+ .setMessage(CommonString.orphan_note_message)
.setPositiveButton(android.R.string.ok, null)
builder.show()
@@ -1098,9 +1098,9 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
}
confirmButton.isEnabled = false
}
- launchCatchingTask(resources.getString(R.string.card_template_editor_save_error)) {
+ launchCatchingTask(resources.getString(CommonString.card_template_editor_save_error)) {
try {
- requireActivity().withProgress(resources.getString(R.string.saving_model)) {
+ requireActivity().withProgress(resources.getString(CommonString.saving_model)) {
templateEditor.tempNoteType!!.saveToDatabase()
}
onModelSaved()
@@ -1137,9 +1137,9 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
@StringRes val overrideStringRes =
if (template != null && template.jsonObject.has("did") && !template.jsonObject.isNull("did")) {
- R.string.card_template_editor_deck_override_on
+ CommonString.card_template_editor_deck_override_on
} else {
- R.string.card_template_editor_deck_override_off
+ CommonString.card_template_editor_deck_override_off
}
menu.findItem(R.id.action_add_deck_override).setTitle(overrideStringRes)
}
@@ -1233,12 +1233,12 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
AlertDialog.Builder(requireContext()).show {
setTitle(TR.sentenceCase.restoreToDefault)
setMessage(TR.cardTemplatesRestoreToDefaultConfirmation())
- setPositiveButton(R.string.restore) { _, _ ->
+ setPositiveButton(CommonString.restore) { _, _ ->
launchCatchingTask {
restoreNotetypeToStock(kind)
}
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
}
@@ -1258,7 +1258,7 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
}
AlertDialog.Builder(requireContext()).show {
setTitle(TR.sentenceCase.restoreToDefault)
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
listItems(stockNotetypesNames) { _: DialogInterface, index: Int ->
val kind = stockNotetypeKinds[index]
askUser(kind)
@@ -1340,12 +1340,12 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
launchCatchingTask {
val activity = requireAnkiActivity()
if (tempModel.notetype.isCloze) {
- showSnackbar(getString(R.string.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
+ showSnackbar(getString(CommonString.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
return@launchCatchingTask
}
val name = getCurrentTemplateName(tempModel)
- val title = getString(R.string.card_template_editor_deck_override)
- val explanation = getString(R.string.deck_override_explanation, name)
+ val title = getString(CommonString.card_template_editor_deck_override)
+ val explanation = getString(CommonString.deck_override_explanation, name)
// Anki Desktop allows Dynamic decks, have reported this as a bug:
// https://forums.ankiweb.net/t/minor-bug-deck-override-to-filtered-deck/1493
startDeckSelection(title = title, templateEditorMessage = explanation, allowAll = false, allowFiltered = false)
@@ -1459,16 +1459,16 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
val msg =
String.format(
resources.getQuantityString(
- R.plurals.card_template_editor_confirm_delete,
+ CommonPlurals.card_template_editor_confirm_delete,
numAffectedCards,
),
numAffectedCards,
tmpl.jsonObject.optString("name"),
)
d.setArgs(
- title = getString(R.string.delete_card_type),
+ title = getString(CommonString.delete_card_type),
message = msg,
- positiveButtonText = getString(R.string.dialog_positive_delete),
+ positiveButtonText = getString(CommonString.dialog_positive_delete),
)
val deleteCard = Runnable { deleteTemplate(tmpl, notetype) }
@@ -1490,15 +1490,15 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
val msg =
String.format(
resources.getQuantityString(
- R.plurals.card_template_editor_confirm_add,
+ CommonPlurals.card_template_editor_confirm_add,
numAffectedCards,
),
numAffectedCards,
)
d.setArgs(
- title = getString(R.string.add_card_type),
+ title = getString(CommonString.add_card_type),
message = msg,
- positiveButtonText = getString(R.string.menu_add),
+ positiveButtonText = getString(CommonString.menu_add),
)
val addCard = Runnable { addNewTemplate(notetype) }
@@ -1528,7 +1528,7 @@ open class CardTemplateEditor : AnkiActivity(R.layout.activity_card_template_edi
} catch (e: ConfirmModSchemaException) {
e.log()
val d = ConfirmationDialog()
- d.setArgs(resources.getString(R.string.full_sync_confirmation))
+ d.setArgs(resources.getString(CommonString.full_sync_confirmation))
val confirm =
Runnable {
templateEditor.getColUnsafe.modSchema(check = false)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionIntegrityStorageCheck.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionIntegrityStorageCheck.kt
index a061d9807319..67aa5a5630b0 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionIntegrityStorageCheck.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionIntegrityStorageCheck.kt
@@ -53,14 +53,14 @@ class CollectionIntegrityStorageCheck {
Timber.e("CollectionIntegrityCheckStatus in an invalid state")
val defaultRequiredFreeSpace = defaultRequiredFreeSpace(context)
return context.resources.getString(
- R.string.integrity_check_insufficient_space,
+ CommonString.integrity_check_insufficient_space,
defaultRequiredFreeSpace,
)
}
val required = Formatter.formatShortFileSize(context, requiredSpace)
val insufficientSpace =
context.resources.getString(
- R.string.integrity_check_insufficient_space,
+ CommonString.integrity_check_insufficient_space,
required,
)
@@ -68,7 +68,7 @@ class CollectionIntegrityStorageCheck {
val currentFree = Formatter.formatShortFileSize(context, freeSpace)
val insufficientSpaceCurrentFree =
context.resources.getString(
- R.string.integrity_check_insufficient_space_extra_content,
+ CommonString.integrity_check_insufficient_space_extra_content,
currentFree,
)
return insufficientSpace + insufficientSpaceCurrentFree
@@ -90,7 +90,7 @@ class CollectionIntegrityStorageCheck {
val requiredFreeSpace = defaultRequiredFreeSpace(context)
return fromError(
context.resources.getString(
- R.string.integrity_check_insufficient_space,
+ CommonString.integrity_check_insufficient_space,
requiredFreeSpace,
),
)
@@ -108,7 +108,7 @@ class CollectionIntegrityStorageCheck {
val readableFileSize = Formatter.formatFileSize(context, requiredSpaceInBytes)
return fromError(
context.resources.getString(
- R.string.integrity_check_insufficient_space,
+ CommonString.integrity_check_insufficient_space,
readableFileSize,
),
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt
index 83a33d1016c1..058b76403b1d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/CoroutineHelpers.kt
@@ -253,9 +253,9 @@ fun Context.showError(
AlertDialog
.Builder(this)
.create {
- title(R.string.vague_error)
+ title(CommonString.vague_error)
message(text = message)
- positiveButton(R.string.dialog_ok)
+ positiveButton(CommonString.dialog_ok)
helpAction?.let { neutralButton(text = it.buttonText(this@showError)) }
if (crashReportData?.reportableException == true) {
Timber.w("sending crash report on close")
@@ -355,7 +355,7 @@ suspend fun FragmentActivity.withProgress(
* flashes of a dialog.
*/
suspend fun Activity.withProgress(
- message: String = resources.getString(R.string.dialog_processing),
+ message: String = resources.getString(CommonString.dialog_processing),
op: suspend () -> T,
): T =
withProgressDialog(
@@ -369,7 +369,7 @@ suspend fun Activity.withProgress(
/** @see withProgress(String, ...) */
suspend fun Fragment.withProgress(
- message: String = getString(R.string.dialog_processing),
+ message: String = getString(CommonString.dialog_processing),
block: suspend () -> T,
): T = requireActivity().withProgress(message, block)
@@ -528,7 +528,7 @@ data class ProgressContext(
// replace spaces with NBSP so newlines are handled better
val curStr = Formatter.formatShortFileSize(context, current).replace(' ', '\u00A0')
val maxStr = Formatter.formatShortFileSize(context, max).replace(' ', '\u00A0')
- context.getString(R.string.progress_amount_bytes, curStr, maxStr)
+ context.getString(CommonString.progress_amount_bytes, curStr, maxStr)
},
)
}
@@ -657,7 +657,7 @@ data class CrashReportData(
*/
sealed class HelpAction {
/** Label for the 'help' button on the error dialog. Defaults to "Help". */
- open fun buttonText(context: Context): CharSequence = context.getString(R.string.help)
+ open fun buttonText(context: Context): CharSequence = context.getString(CommonString.help)
/** `false` hides the help button. */
open fun canExecute(context: Context): Boolean = true
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
index 12202b245f19..080cc3be479c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
@@ -297,6 +297,9 @@ open class DeckPicker :
get() = binding.studyoptionsFragment != null
set(_) = throw UnsupportedOperationException()
+ override val analyticsScreenName: String
+ get() = selectedBottomNavItem()?.analyticsScreenName ?: super.analyticsScreenName
+
// Short animation duration from system
private var shortAnimDuration = 0
@@ -395,7 +398,7 @@ open class DeckPicker :
DeckPickerActivityResultCallback {
if (it.resultCode == RESULT_OK) {
lifecycleScope.launch {
- withProgress(message = getString(R.string.import_preparing_file)) {
+ withProgress(message = getString(CommonString.import_preparing_file)) {
withContext(Dispatchers.IO) {
onSelectedPackageToImport(it.data!!)
}
@@ -793,13 +796,13 @@ open class DeckPicker :
fun onDeckDeleted(result: DeckDeletionResult) {
floatingActionButtonBinding.fabMain.isVisible = true
showSnackbar(result.toHumanReadableString(), Snackbar.LENGTH_SHORT) {
- setAction(R.string.undo) { undo() }
+ setAction(CommonString.undo) { undo() }
}
}
fun onCardsEmptied(result: EmptyCardsResult) {
showSnackbar(result.toHumanReadableString(), Snackbar.LENGTH_SHORT) {
- setAction(R.string.undo) { undo() }
+ setAction(CommonString.undo) { undo() }
}
}
@@ -887,7 +890,7 @@ open class DeckPicker :
}
supportActionBar?.apply {
- subtitle = if (dueCount == 0) null else resources.getQuantityString(R.plurals.widget_cards_due, dueCount, dueCount)
+ subtitle = if (dueCount == 0) null else resources.getQuantityString(CommonPlurals.widget_cards_due, dueCount, dueCount)
val toolbar = findViewById(R.id.toolbar)
TooltipCompat.setTooltipText(toolbar, toolbar.subtitle)
}
@@ -965,12 +968,13 @@ open class DeckPicker :
fun onError(errorMessage: String) {
AlertDialog
.Builder(this)
- .setTitle(R.string.vague_error)
+ .setTitle(CommonString.vague_error)
.setMessage(errorMessage)
.show()
}
viewModel.deckDeletedNotification.launchCollectionInLifecycleScope(::onDeckDeleted)
+ viewModel.flowOfDeleteDeckConfirmation.launchCollectionInLifecycleScope(::showDeleteDeckConfirmationDialog)
viewModel.emptyCardsNotification.launchCollectionInLifecycleScope(::onCardsEmptied)
viewModel.flowOfDeckCountsChanged.launchCollectionInLifecycleScope(::onDeckCountsChanged)
viewModel.flowOfDestination.launchCollectionInLifecycleScope(::onDestinationChanged)
@@ -1011,7 +1015,7 @@ open class DeckPicker :
val clip = uriContent?.clip ?: return@OnReceiveContentListener remaining
val uri = clip.getItemAt(0).uri
if (!ImportUtils.FileImporter().isValidImportType(this, uri)) {
- showSnackbar(R.string.import_log_no_apkg)
+ showSnackbar(CommonString.import_log_no_apkg)
return@OnReceiveContentListener remaining
}
@@ -1180,19 +1184,19 @@ open class DeckPicker :
linksClickable = true
text =
getString(
- R.string.directory_inaccessible_info,
+ CommonString.directory_inaccessible_info,
getString(R.string.link_full_storage_access),
)
}
AlertDialog.Builder(this).show {
- title(R.string.directory_inaccessible)
+ title(CommonString.directory_inaccessible)
customView(
contentView,
paddingTop = 16.dp.toPx(this@DeckPicker),
paddingStart = 32.dp.toPx(this@DeckPicker),
paddingEnd = 32.dp.toPx(this@DeckPicker),
)
- positiveButton(R.string.open_settings) {
+ positiveButton(CommonString.open_settings) {
requestPathUpdateLauncher.navigate(PreferencesDestination.Advanced)
}
}
@@ -1216,7 +1220,7 @@ open class DeckPicker :
val drawable = (result as? BackgroundImage.ResolveResult.Ready)?.drawable
val applied =
deckPickerBinding.background.setImageDrawableSafe(drawable) {
- showThemedToast(this, getString(R.string.background_image_too_large), shortLength = false)
+ showThemedToast(this, getString(CommonString.background_image_too_large), shortLength = false)
}
// activityHasBackground calls notifyDataSetChanged, ensure only 1 call
deckListAdapter.activityHasBackground = applied && drawable != null
@@ -1323,7 +1327,7 @@ open class DeckPicker :
)
(menuItem.actionView as AccessibleSearchView).run {
- queryHint = getString(R.string.search_decks)
+ queryHint = getString(CommonString.search_decks)
setOnQueryTextListener(
object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
@@ -1391,9 +1395,9 @@ open class DeckPicker :
?: return
val tooltipText =
when (state.syncIcon) {
- SyncIconState.Normal, SyncIconState.PendingChanges -> R.string.button_sync
- SyncIconState.OneWay -> R.string.sync_menu_title_one_way_sync
- SyncIconState.NotLoggedIn -> R.string.sync_menu_title_no_account
+ SyncIconState.Normal, SyncIconState.PendingChanges -> CommonString.button_sync
+ SyncIconState.OneWay -> CommonString.sync_menu_title_one_way_sync
+ SyncIconState.NotLoggedIn -> CommonString.sync_menu_title_no_account
}
provider.setTooltipText(getString(tooltipText))
when (state.syncIcon) {
@@ -1491,7 +1495,7 @@ open class DeckPicker :
}
R.id.action_deck_delete -> {
launchCatchingTask {
- withProgress(resources.getString(R.string.delete_deck)) {
+ withProgress(resources.getString(CommonString.delete_deck)) {
viewModel.deleteSelectedDeck().join()
}
}
@@ -1524,12 +1528,12 @@ open class DeckPicker :
Timber.i("showing media check dialog")
AlertDialog.Builder(this).show {
title(text = TR.sentenceCase.checkMediaTitle)
- message(text = getString(R.string.check_media_warning))
- positiveButton(R.string.dialog_ok) {
+ message(text = getString(CommonString.check_media_warning))
+ positiveButton(CommonString.dialog_ok) {
Timber.i("Starting media check")
startActivity(MediaCheckFragment.getIntent(this@DeckPicker))
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
@@ -1849,7 +1853,7 @@ open class DeckPicker :
} else {
// Shortcut: DEL
Timber.i("Delete Deck from keypress")
- showDeleteDeckConfirmationDialog()
+ viewModel.requestDeleteDeckConfirmation()
}
return true
}
@@ -1906,31 +1910,16 @@ open class DeckPicker :
/**
* Displays a confirmation dialog for deleting deck.
*/
- private fun showDeleteDeckConfirmationDialog() =
- launchCatchingTask {
- val focusedDeck =
- viewModel.focusedDeck ?: run {
- Timber.w("no focused deck")
- return@launchCatchingTask
- }
-
- val (deckName, totalCards, isFilteredDeck) =
- withCol {
- Triple(
- decks.name(focusedDeck),
- decks.cardCount(focusedDeck, includeSubdecks = true),
- decks.isFiltered(focusedDeck),
- )
- }
- val confirmDeleteDeckDialog =
- DeckPickerConfirmDeleteDeckDialog.newInstance(
- deckName = deckName,
- deckId = focusedDeck,
- totalCards = totalCards,
- isFilteredDeck = isFilteredDeck,
- )
- showDialogFragment(confirmDeleteDeckDialog)
- }
+ private fun showDeleteDeckConfirmationDialog(request: DeckPickerViewModel.DeleteDeckConfirmationRequest) {
+ val confirmDeleteDeckDialog =
+ DeckPickerConfirmDeleteDeckDialog.newInstance(
+ deckName = request.deckName,
+ deckId = request.deckId,
+ totalCards = request.totalCards,
+ isFilteredDeck = request.isFilteredDeck,
+ )
+ showDialogFragment(confirmDeleteDeckDialog)
+ }
/**
* Perform the following tasks:
@@ -2019,7 +2008,7 @@ open class DeckPicker :
Timber.i("Dev Build - not showing 'new features'")
// Don't show new features dialog for development builds
InitialActivity.setUpgradedToLatestVersion(preferences)
- val ver = resources.getString(R.string.updated_version, VersionUtils.pkgVersionName)
+ val ver = resources.getString(CommonString.updated_version, VersionUtils.pkgVersionName)
postSnackbar(ver, Snackbar.LENGTH_SHORT)
showStartupScreensAndDialogs(preferences, 2)
}
@@ -2079,10 +2068,10 @@ open class DeckPicker :
fun repairCollection() {
Timber.i("Repairing the Collection")
// TODO: doesn't work on null collection-only on non-openable(is this still relevant with withCol?)
- launchCatchingTask(resources.getString(R.string.deck_repair_error)) {
+ launchCatchingTask(resources.getString(CommonString.deck_repair_error)) {
Timber.d("doInBackgroundRepairCollection")
val result =
- withProgress(resources.getString(R.string.backup_repair_deck_progress)) {
+ withProgress(resources.getString(CommonString.backup_repair_deck_progress)) {
Timber.i("RepairCollection: Closing collection")
CollectionManager.ensureClosed()
val colFile =
@@ -2090,7 +2079,7 @@ open class DeckPicker :
BackupManager.repairCollection(colFile)
}
if (!result) {
- showThemedToast(this@DeckPicker, resources.getString(R.string.deck_repair_error), true)
+ showThemedToast(this@DeckPicker, resources.getString(CommonString.deck_repair_error), true)
showCollectionErrorDialog()
}
}
@@ -2106,10 +2095,10 @@ open class DeckPicker :
AlertDialog.Builder(this).show {
title(text = TR.sentenceCase.checkDatabase)
message(text = status.getWarningDetails(this@DeckPicker))
- positiveButton(R.string.integrity_check_continue_anyway) {
+ positiveButton(CommonString.integrity_check_continue_anyway) {
performIntegrityCheck()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
} else {
performIntegrityCheck()
@@ -2235,7 +2224,7 @@ open class DeckPicker :
fun openAnkiWebSharedDecks() {
if (!NetworkUtils.isOnline) {
- showSnackbar(R.string.check_network)
+ showSnackbar(CommonString.check_network)
Timber.d("DeckPicker:: No network, Shared deck download failed")
return
}
@@ -2285,8 +2274,8 @@ open class DeckPicker :
}
fun showEmptyDeckSnackbar() =
- showSnackbar(R.string.empty_deck) {
- setAction(R.string.menu_add) { viewModel.addNote(did, true) }
+ showSnackbar(CommonString.empty_deck) {
+ setAction(CommonString.menu_add) { viewModel.addNote(did, true) }
}
/** Check if we need to update the fragment or update the deck list */
@@ -2350,27 +2339,27 @@ open class DeckPicker :
// User report: "success" is true even if Vivo does not have permission
if (AdaptionUtil.isVivo) {
- showThemedToast(this, getString(R.string.create_shortcut_error_vivo), false)
+ showThemedToast(this, getString(CommonString.create_shortcut_error_vivo), false)
}
// #18601: MIUI gates shortcut creation behind Settings - Privacy Protection -
// Other permissions - AnkiDroid - Home screen shortcuts [add shortcuts to Home screen]
// TODO: Determine the result of 'success'
if (AdaptionUtil.isMiui) {
- showThemedToast(this, getString(R.string.create_shortcut_error_miui, getString(R.string.app_name)), false)
+ showThemedToast(this, getString(CommonString.create_shortcut_error_miui, getString(R.string.app_name)), false)
}
if (!success) {
- showThemedToast(this, getString(R.string.create_shortcut_failed), false)
+ showThemedToast(this, getString(CommonString.create_shortcut_failed), false)
}
} catch (e: Exception) {
Timber.w(e)
- showThemedToast(this, getString(R.string.create_shortcut_error, e.localizedMessage), false)
+ showThemedToast(this, getString(CommonString.create_shortcut_error, e.localizedMessage), false)
}
}
/** Disables the shortcut of the deck and the children belonging to it.*/
@NeedsTest("ensure collapsed decks are also deleted")
private fun disableDeckAndChildrenShortcuts(deckTreeDids: List) {
- val errorMessage: CharSequence = getString(R.string.deck_shortcut_doesnt_exist)
+ val errorMessage: CharSequence = getString(CommonString.deck_shortcut_doesnt_exist)
ShortcutUtils.disableShortcuts(this, deckTreeDids, errorMessage)
}
@@ -2432,7 +2421,7 @@ open class DeckPicker :
*/
fun deleteDeck(did: DeckId) =
launchCatchingTask {
- withProgress(resources.getString(R.string.delete_deck)) {
+ withProgress(resources.getString(CommonString.delete_deck)) {
viewModel.deleteDeck(did).join()
}
}
@@ -2440,7 +2429,7 @@ open class DeckPicker :
@NeedsTest("14285: regression test to ensure UI is updated after this call")
fun rebuildFiltered(did: DeckId) {
launchCatchingTask {
- withProgress(resources.getString(R.string.rebuild_filtered_deck)) {
+ withProgress(resources.getString(CommonString.rebuild_filtered_deck)) {
viewModel.rebuildFilteredDeck(did).join()
}
updateDeckList()
@@ -2503,7 +2492,7 @@ open class DeckPicker :
val createDeckDialog =
CreateDeckDialog(
context = this@DeckPicker,
- title = getString(R.string.create_subdeck),
+ title = getString(CommonString.create_subdeck),
deckDialogType = CreateDeckDialog.DeckDialogType.SUB_DECK,
parentId = did,
)
@@ -2564,29 +2553,29 @@ open class DeckPicker :
return ShortcutGroup(
listOfNotNull(
- shortcut("A", R.string.menu_add_note),
+ shortcut("A", CommonString.menu_add_note),
bottomNavShortcut("Alt+1", NavigationItem.HOME),
bottomNavShortcut("Alt+2", NavigationItem.BROWSER),
bottomNavShortcut("Alt+3", NavigationItem.STATS),
bottomNavShortcut("Alt+4", NavigationItem.MORE),
- shortcut("B", R.string.card_browser_context_menu),
- shortcut("Y", R.string.pref_cat_sync),
- shortcut("/", R.string.deck_conf_cram_search),
+ shortcut("B", CommonString.card_browser_context_menu),
+ shortcut("Y", CommonString.pref_cat_sync),
+ shortcut("/", CommonString.deck_conf_cram_search),
shortcut("S", Translations::decksStudyDeck),
- shortcut("T", R.string.open_statistics),
+ shortcut("T", CommonString.open_statistics),
shortcut("C") { this.sentenceCase.checkDatabase },
shortcut("D") { sentenceCase.createDeck },
- shortcut("F", R.string.new_dynamic_deck),
+ shortcut("F", CommonString.new_dynamic_deck),
if (fragmented) shortcut("DEL") { this.sentenceCase.deleteDeck } else null,
- if (fragmented) shortcut("Shift+DEL", R.string.delete_deck_without_confirmation) else null,
+ if (fragmented) shortcut("Shift+DEL", CommonString.delete_deck_without_confirmation) else null,
if (fragmented) shortcut("R") { this.sentenceCase.renameDeck } else null,
- shortcut("P", R.string.open_settings),
+ shortcut("P", CommonString.open_settings),
shortcut("M") { this.sentenceCase.checkMediaAction },
- shortcut("Ctrl+E", R.string.export_collection),
+ shortcut("Ctrl+E", CommonString.export_collection),
shortcut("Ctrl+Shift+I", Translations::actionsImport),
- shortcut("Ctrl+Shift+N", R.string.model_browser_label),
+ shortcut("Ctrl+Shift+N", CommonString.model_browser_label),
),
- R.string.deck_picker_group,
+ CommonString.deck_picker_group,
)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/FieldEditText.kt b/AnkiDroid/src/main/java/com/ichi2/anki/FieldEditText.kt
index 9689a4737498..725a779b58e4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/FieldEditText.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/FieldEditText.kt
@@ -179,7 +179,7 @@ class FieldEditText :
pasteListener!!.onPaste(this, mediaUri, description)
} catch (e: Exception) {
Timber.w(e, "Failed to paste media")
- showSnackbar(context.getString(R.string.multimedia_editor_something_wrong))
+ showSnackbar(context.getString(CommonString.multimedia_editor_something_wrong))
false
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt b/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt
index dd08cbf865c4..8bca97a116ac 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/HomeScreenNavigation.kt
@@ -21,6 +21,7 @@ import com.ichi2.anki.BottomNavController.NavigationItem
import com.ichi2.anki.ankiquest.AnkiquestNavigation
import com.ichi2.anki.browser.CardBrowserFragment
import com.ichi2.anki.browser.CardBrowserViewModel
+import com.ichi2.anki.common.analytics.Analytics
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.pages.Statistics
import com.ichi2.anki.settings.Prefs
@@ -85,6 +86,9 @@ fun setupBottomNavigation() {
bottomNav.setOnItemSelectedListener { item ->
val navItem = NavigationItem.fromId(item.itemId) ?: return@setOnItemSelectedListener false
+ if (item.itemId != bottomNav.selectedItemId) {
+ Analytics.sendAnalyticsScreenView(navItem.analyticsScreenName)
+ }
handleNavigationItemSelected(navItem, contentWrapper, fragmentContainer, bottomNavBackCallback)
}
@@ -106,6 +110,13 @@ fun showRestoredBottomNavTab() {
bottomNav.selectedItemId = bottomNav.selectedItemId
}
+context(deckPicker: DeckPicker)
+fun selectedBottomNavItem(): NavigationItem? {
+ if (!Prefs.devBottomNavEnabled || deckPicker.fragmented) return null
+ val bottomNav = deckPicker.binding.bottomNavigation ?: return null
+ return NavigationItem.fromId(bottomNav.selectedItemId)
+}
+
context(deckPicker: DeckPicker)
private fun handleNavigationItemSelected(
item: NavigationItem,
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Info.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Info.kt
index 7e1f388aa962..d9d639fae1c6 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/Info.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/Info.kt
@@ -83,7 +83,7 @@ class Info :
}
binding.leftButton.run {
if (canOpenMarketUri()) {
- setText(R.string.info_rate)
+ setText(CommonString.info_rate)
setOnClickListener {
tryOpenIntent(
this@Info,
@@ -113,7 +113,7 @@ class Info :
binding.webView.settings.allowContentAccess = true
setRenderWorkaround(this)
binding.rightButton.run {
- text = getString(R.string.dialog_continue)
+ text = getString(CommonString.dialog_continue)
setOnClickListener { close() }
}
val background = backgroundColor.toRGBHex()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.kt
index fd4e1d65d34f..73df41266d34 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.kt
@@ -187,12 +187,12 @@ object InitialActivity {
when (error) {
is FatalInitializationError.WebViewError ->
context.getString(
- R.string.ankidroid_init_failed_webview,
+ CommonString.ankidroid_init_failed_webview,
error.errorDetail,
)
is FatalInitializationError.StorageError ->
context.getString(
- R.string.ankidroid_init_failed_storage,
+ CommonString.ankidroid_init_failed_storage,
error.errorDetail,
)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt
index 5b33eec87769..0cf54fec60d7 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt
@@ -121,7 +121,7 @@ class IntentHandler : AbstractIntentHandler() {
}
this.copyToClipboard(
text = TruncatedString.from(clipboardData),
- failureMessageId = R.string.about_ankidroid_error_copy_debug_info,
+ failureMessageId = CommonString.about_ankidroid_error_copy_debug_info,
)
}
@@ -219,7 +219,7 @@ class IntentHandler : AbstractIntentHandler() {
Timber.i("Handling file import")
if (!hasShownAppIntro()) {
Timber.i("Trying to import a file when the app was not started at all")
- showThemedToast(this, R.string.app_not_initialized_new, false)
+ showThemedToast(this, CommonString.app_not_initialized_new, false)
finish()
return
}
@@ -377,7 +377,7 @@ class IntentHandler : AbstractIntentHandler() {
val granted = grantedStoragePermissions(context)
if (!granted && showToast) {
- showThemedToast(context, context.getString(R.string.intent_handler_failed_no_storage_permission), false)
+ showThemedToast(context, context.getString(CommonString.intent_handler_failed_no_storage_permission), false)
}
return granted
@@ -456,7 +456,7 @@ class IntentHandler : AbstractIntentHandler() {
if (!limited && !hkey.isNullOrEmpty() && NetworkUtils.isOnline) {
deckPicker.sync()
} else {
- val err = res.getString(R.string.sync_error)
+ val err = res.getString(CommonString.sync_error)
if (limited) {
val remainingTimeInSeconds =
max((INTENT_SYNC_MIN_INTERVAL - millisecondsSinceLastSync) / 1000, 1)
@@ -464,7 +464,7 @@ class IntentHandler : AbstractIntentHandler() {
val remaining = min(Int.MAX_VALUE.toLong(), remainingTimeInSeconds).toInt()
val message =
res.getQuantityString(
- R.plurals.sync_automatic_sync_needs_more_time,
+ CommonPlurals.sync_automatic_sync_needs_more_time,
remaining,
remaining,
)
@@ -472,7 +472,7 @@ class IntentHandler : AbstractIntentHandler() {
} else {
deckPicker.showSimpleNotification(
err,
- res.getString(R.string.youre_offline),
+ res.getString(CommonString.youre_offline),
NotificationChannel.SYNC,
)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler2.kt b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler2.kt
index 37c4fe79ebad..75ab625e6ce6 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler2.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler2.kt
@@ -28,7 +28,7 @@ class IntentHandler2 : AbstractIntentHandler() {
val intentExtras = intent.extras
if (intentExtras == null) {
Timber.w("Intent unexpectedly has no extras. Notifying user, defaulting to add note.")
- showThemedToast(this, getString(R.string.something_wrong), false)
+ showThemedToast(this, getString(CommonString.something_wrong), false)
navigate(NoteEditorDestination.AddNote())
finish()
} else {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/MediaRegistration.kt b/AnkiDroid/src/main/java/com/ichi2/anki/MediaRegistration.kt
index 256da728b3a9..2037dec39c2f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/MediaRegistration.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/MediaRegistration.kt
@@ -46,36 +46,36 @@ object MediaRegistration {
* Represents different types of media errors.
*/
sealed class MediaError {
- /** [Something wrong wrong][R.string.multimedia_editor_something_wrong] */
+ /** [Something wrong wrong][CommonString.multimedia_editor_something_wrong] */
data object GenericError : MediaError()
- /** [Something went wrong, please try again[R.string.something_wrong] */
+ /** [Something went wrong, please try again[CommonString.something_wrong] */
data class GenericErrorTryAgain(
val details: String?,
) : MediaError()
- /** [Error converting clipboard image to png][R.string.multimedia_editor_png_paste_error] */
+ /** [Error converting clipboard image to png][CommonString.multimedia_editor_png_paste_error] */
class ConversionError(
val message: String,
) : MediaError()
- /** [The image is too large, please insert the image manually][R.string.note_editor_image_too_large] */
+ /** [The image is too large, please insert the image manually][CommonString.note_editor_image_too_large] */
data object ImageTooLarge : MediaError()
- /** [The video file is too large, please insert the video manually][R.string.note_editor_video_too_large] */
+ /** [The video file is too large, please insert the video manually][CommonString.note_editor_video_too_large] */
data object VideoTooLarge : MediaError()
- /** [The audio file is too large, please insert the audio manually][R.string.note_editor_audio_too_large] */
+ /** [The audio file is too large, please insert the audio manually][CommonString.note_editor_audio_too_large] */
data object AudioTooLarge : MediaError()
fun toHumanReadableString(context: Context): String =
when (this) {
- is GenericError -> context.getString(R.string.multimedia_editor_something_wrong)
- is GenericErrorTryAgain -> context.getString(R.string.something_wrong) + details?.let { "\n\n$it" }.orEmpty()
- is ConversionError -> context.getString(R.string.multimedia_editor_png_paste_error, message)
- is ImageTooLarge -> context.getString(R.string.note_editor_image_too_large)
- is VideoTooLarge -> context.getString(R.string.note_editor_video_too_large)
- is AudioTooLarge -> context.getString(R.string.note_editor_audio_too_large)
+ is GenericError -> context.getString(CommonString.multimedia_editor_something_wrong)
+ is GenericErrorTryAgain -> context.getString(CommonString.something_wrong) + details?.let { "\n\n$it" }.orEmpty()
+ is ConversionError -> context.getString(CommonString.multimedia_editor_png_paste_error, message)
+ is ImageTooLarge -> context.getString(CommonString.note_editor_image_too_large)
+ is VideoTooLarge -> context.getString(CommonString.note_editor_video_too_large)
+ is AudioTooLarge -> context.getString(CommonString.note_editor_audio_too_large)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/MoreFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/MoreFragment.kt
index 67ed42781e0d..58c4e17fb673 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/MoreFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/MoreFragment.kt
@@ -48,19 +48,19 @@ class MoreFragment : Fragment(R.layout.fragment_more) {
// Help section: each opens HelpDialog with the relevant sub-items
binding.moreHelpManual.setOnClickListener {
Analytics.send(LinkClicked(LinkAction.OPENED_USING_ANKIDROID))
- openHelpSection(R.string.help_title_using_ankidroid)
+ openHelpSection(CommonString.help_title_using_ankidroid)
}
binding.moreHelpGetHelp.setOnClickListener {
Analytics.send(LinkClicked(LinkAction.OPENED_GET_HELP))
- openHelpSection(R.string.help_title_get_help)
+ openHelpSection(CommonString.help_title_get_help)
}
binding.moreHelpCommunity.setOnClickListener {
Analytics.send(LinkClicked(LinkAction.OPENED_COMMUNITY))
- openHelpSection(R.string.help_title_community)
+ openHelpSection(CommonString.help_title_community)
}
binding.moreHelpPrivacy.setOnClickListener {
Analytics.send(LinkClicked(LinkAction.OPENED_PRIVACY))
- openHelpSection(R.string.help_title_privacy)
+ openHelpSection(CommonString.help_title_privacy)
}
// Support section: direct URL actions
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt
index fcb0c5e38fa4..2d4ec5e17096 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.kt
@@ -191,8 +191,8 @@ abstract class NavigationDrawerActivity(
object : ActionBarDrawerToggle(
this,
drawerLayout,
- R.string.drawer_open,
- R.string.drawer_close,
+ CommonString.drawer_open,
+ CommonString.drawer_close,
) {
override fun onDrawerClosed(drawerView: View) {
super.onDrawerClosed(drawerView)
@@ -528,8 +528,8 @@ abstract class NavigationDrawerActivity(
val reviewCardsShortcut =
ShortcutInfoCompat
.Builder(context, "reviewCardsShortcutId")
- .setShortLabel(context.getString(R.string.studyoptions_start))
- .setLongLabel(context.getString(R.string.studyoptions_start))
+ .setShortLabel(context.getString(CommonString.studyoptions_start))
+ .setLongLabel(context.getString(CommonString.studyoptions_start))
.setIcon(IconCompat.createWithResource(context, R.drawable.review_shortcut))
.setIntents(arrayOf(deckPickerIntent, intentReviewCards))
.build()
@@ -542,8 +542,8 @@ abstract class NavigationDrawerActivity(
val noteEditorShortcut =
ShortcutInfoCompat
.Builder(context, "noteEditorShortcutId")
- .setShortLabel(context.getString(R.string.menu_add))
- .setLongLabel(context.getString(R.string.menu_add))
+ .setShortLabel(context.getString(CommonString.menu_add))
+ .setLongLabel(context.getString(CommonString.menu_add))
.setIcon(IconCompat.createWithResource(context, R.drawable.add_shortcut))
.setIntent(intentAddNote)
.build()
@@ -555,8 +555,8 @@ abstract class NavigationDrawerActivity(
val cardBrowserShortcut =
ShortcutInfoCompat
.Builder(context, "cardBrowserShortcutId")
- .setShortLabel(context.getString(R.string.card_browser))
- .setLongLabel(context.getString(R.string.card_browser))
+ .setShortLabel(context.getString(CommonString.card_browser))
+ .setLongLabel(context.getString(CommonString.card_browser))
.setIcon(IconCompat.createWithResource(context, R.drawable.browse_shortcut))
.setIntent(intentCardBrowser)
.build()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorActivity.kt
index c0e2b9c808f5..21f83cc02351 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorActivity.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorActivity.kt
@@ -8,6 +8,7 @@ import android.os.Build
import android.os.Bundle
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
+import androidx.core.content.edit
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat.Type.displayCutout
import androidx.core.view.WindowInsetsCompat.Type.ime
@@ -70,14 +71,30 @@ class NoteEditorActivity :
*/
private var previewerFrame: FragmentContainerView? = null
+ /**
+ * The user's persisted previewer visibility preference.
+ * Applied to the views by [applyPreviewerVisibility], restored in [onCreate].
+ */
+ private var previewerVisibilityPref: Boolean = true
+
/**
* Job for managing delayed previewer refresh operations.
* Automatically cancelled when the lifecycle scope is destroyed, preventing memory leaks.
*/
private var refreshPreviewerJob: Job? = null
- val fragmented: Boolean
- get() = previewerFrame?.isVisible == true
+ /**
+ * Whether the previewer pane is currently visible.
+ *
+ * `false` in single-pane layouts, and in split layouts where the user has
+ * hidden the pane via the "Show preview sidebar" menu action.
+ */
+ val isPreviewerVisible: Boolean
+ get() = binding.previewerFrameLayout?.isVisible == true
+
+ /** Whether this layout contains a previewer pane (split layouts only); never changes at runtime. */
+ val hasPreviewerPane: Boolean
+ get() = previewerFrame != null
private lateinit var binding: ActivityNoteEditorBinding
@@ -123,7 +140,9 @@ class NoteEditorActivity :
setupEdgeToEdge()
previewerFrame = binding.previewerFrame
- Timber.i("Note Editor is in %s mode", if (fragmented) "split" else "single-pane")
+ previewerVisibilityPref = loadPreviewerVisibilityPreference()
+ applyPreviewerVisibility()
+ Timber.i("Note Editor is in %s mode", if (isPreviewerVisible) "split" else "single-pane")
// TODO: specify how non-null but invalid extras are handled
val fragmentArgs = intent.extras ?: NoteEditorFragment.addNoteArgs()
@@ -158,11 +177,7 @@ class NoteEditorActivity :
onBackPressedDispatcher.onBackPressed()
}
- if (fragmented) {
- // Defer previewer loading to avoid blocking onCreate
- binding.previewerFrame!!.post {
- loadNoteEditorPreviewer(true)
- }
+ if (hasPreviewerPane) {
val parentLayout = binding.noteEditorXlView!!
val divider = binding.noteEditorResizingDivider!!
val noteEditorPane = binding.noteEditorFragmentFrame
@@ -176,11 +191,40 @@ class NoteEditorActivity :
leftPaneWeightKey = PREF_NOTE_EDITOR_PANE_WEIGHT,
rightPaneWeightKey = PREF_PREVIEWER_PANE_WEIGHT,
)
+ if (isPreviewerVisible) {
+ // Defer previewer loading to avoid blocking onCreate
+ binding.previewerFrame!!.post {
+ loadNoteEditorPreviewer(true)
+ }
+ }
}
startLoadingCollection()
}
+ fun setPreviewerVisible(visible: Boolean) {
+ if (!hasPreviewerPane) return
+
+ previewerVisibilityPref = visible
+ savePreviewerVisibilityPreference(visible)
+ applyPreviewerVisibility()
+ if (visible) {
+ loadNoteEditorPreviewer(true)
+ }
+ }
+
+ private fun loadPreviewerVisibilityPreference(): Boolean = Prefs.getUiConfig(this).getBoolean(PREF_SHOW_PREVIEWER, true)
+
+ private fun savePreviewerVisibilityPreference(visible: Boolean) {
+ Prefs.getUiConfig(this).edit { putBoolean(PREF_SHOW_PREVIEWER, visible) }
+ }
+
+ private fun applyPreviewerVisibility() {
+ val visible = hasPreviewerPane && previewerVisibilityPref
+ binding.previewerFrameLayout?.isVisible = visible
+ binding.noteEditorResizingDivider?.isVisible = visible
+ }
+
/**
* Loads and configures the note editor previewer.
*
@@ -203,7 +247,7 @@ class NoteEditorActivity :
* - Cloze notes: Fragment recreation with preserved tab selection to handle dynamic cloze changes
*/
fun loadNoteEditorPreviewer(forceReplace: Boolean) {
- if (!fragmented) {
+ if (!isPreviewerVisible) {
return
}
@@ -320,7 +364,7 @@ class NoteEditorActivity :
for ((index, templateName) in templateNames.withIndex()) {
val tabTitle =
if (cardsWithEmptyFronts?.get(index) == true) {
- getString(R.string.card_previewer_empty_front_indicator, templateName)
+ getString(CommonString.card_previewer_empty_front_indicator, templateName)
} else {
templateName
}
@@ -363,7 +407,7 @@ class NoteEditorActivity :
override fun onResume() {
super.onResume()
// Refresh the previewer when activity resumes, if needed
- if (fragmented) {
+ if (isPreviewerVisible) {
loadNoteEditorPreviewer(false)
}
}
@@ -371,14 +415,14 @@ class NoteEditorActivity :
//region NoteEditorFragmentDelegate Protocol Methods
override fun onNoteEditorReady() {
- // Load the if fragmented, else does nothing
- if (!fragmented) return
+ // Load the previewer if visible, else do nothing
+ if (!isPreviewerVisible) return
loadNoteEditorPreviewer(false)
}
override fun onNoteTextChanged() {
- if (!fragmented) return
+ if (!isPreviewerVisible) return
refreshPreviewerJob?.cancel()
refreshPreviewerJob =
@@ -389,13 +433,13 @@ class NoteEditorActivity :
}
override fun onNoteSaved() {
- if (!fragmented) return
+ if (!isPreviewerVisible) return
loadNoteEditorPreviewer(true)
}
override fun onNoteTypeChanged() {
- if (!fragmented) return
+ if (!isPreviewerVisible) return
loadNoteEditorPreviewer(true)
}
@@ -407,6 +451,7 @@ class NoteEditorActivity :
// Keys for saving pane weights in SharedPreferences
private const val PREF_NOTE_EDITOR_PANE_WEIGHT = "noteEditorPaneWeight"
private const val PREF_PREVIEWER_PANE_WEIGHT = "previewerPaneWeight"
+ private const val PREF_SHOW_PREVIEWER = "noteEditorShowPreviewer"
private val REFRESH_NOTE_EDITOR_PREVIEW_DELAY = 100.milliseconds
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt
index 950a0301dfa5..132aa3c4f565 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditorFragment.kt
@@ -373,7 +373,7 @@ class NoteEditorFragment :
* type was changed without moving this
* card to another type. */
Timber.d("onActivityResult() template edit return - current card is gone, close note editor")
- showSnackbar(getString(R.string.template_for_current_card_deleted))
+ showSnackbar(getString(CommonString.template_for_current_card_deleted))
closeNoteEditor()
} else {
Timber.d("onActivityResult() template edit return, in add mode, just re-display")
@@ -497,7 +497,7 @@ class NoteEditorFragment :
saveNoteWithProgress()
}
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
@@ -512,8 +512,8 @@ class NoteEditorFragment :
get() =
when {
addNoteErrorMessage != null -> addNoteErrorMessage!!
- allFieldsHaveContent() -> resources.getString(R.string.note_editor_no_cards_created_all_fields)
- else -> resources.getString(R.string.note_editor_no_cards_created)
+ allFieldsHaveContent() -> resources.getString(CommonString.note_editor_no_cards_created_all_fields)
+ else -> resources.getString(CommonString.note_editor_no_cards_created)
}
override val baseSnackbarBuilder: SnackbarBuilder = {
@@ -713,7 +713,7 @@ class NoteEditorFragment :
ioEditorLauncher.launch("image/*")
} catch (_: ActivityNotFoundException) {
Timber.w("No app found to handle onGalleryClicked request")
- activity?.showSnackbar(R.string.activity_start_failed)
+ activity?.showSnackbar(CommonString.activity_start_failed)
}
}
pasteOcclusionImageButton?.setOnClickListener {
@@ -736,7 +736,7 @@ class NoteEditorFragment :
} else {
imageSelectionForOcclusionContainer?.visibility = View.GONE
editOcclusionsButton?.visibility = View.VISIBLE
- editOcclusionsButton?.text = resources.getString(R.string.edit_occlusions)
+ editOcclusionsButton?.text = resources.getString(CommonString.edit_occlusions)
editOcclusionsButton?.setOnClickListener {
setupImageOcclusionEditor()
}
@@ -750,7 +750,7 @@ class NoteEditorFragment :
val deckTextView = requireView().findViewById(R.id.CardEditorDeckText)
// If edit mode and more than one card template distinguish between "Deck" and "Card deck"
if (!addNote && editorNote!!.notetype.templates.length() > 1) {
- deckTextView.setText(R.string.CardEditorCardDeck)
+ deckTextView.setText(CommonString.CardEditorCardDeck)
}
deckId = requireArguments().getLong(EXTRA_DID, deckId)
@@ -786,7 +786,7 @@ class NoteEditorFragment :
setNote(editorNote, FieldChangeType.onActivityCreation(shouldReplaceNewlines()))
if (addNote) {
noteTypeSpinner!!.onItemSelectedListener = SetNoteTypeListener()
- requireAnkiActivity().setToolbarText(titleRes = R.string.menu_add)
+ requireAnkiActivity().setToolbarText(titleRes = CommonString.menu_add)
// set information transferred by intent
var contents: String? = null
val tags = requireArguments().getStringArray(EXTRA_TAGS)
@@ -801,7 +801,7 @@ class NoteEditorFragment :
changeNoteType(noteType.id)
}
} catch (e: NoSuchElementException) {
- showSnackbar(R.string.missing_note_type)
+ showSnackbar(CommonString.missing_note_type)
// setting the text to null & caller to CALLER_NO_CALLER would skip adding text/image to edit field
sourceText = null
caller = NoteEditorCaller.NO_CALLER
@@ -843,7 +843,7 @@ class NoteEditorFragment :
reloadRequired = true
closeNoteEditorAfterSave()
}
- requireAnkiActivity().setTitle(R.string.cardeditor_title_edit_card)
+ requireAnkiActivity().setTitle(CommonString.cardeditor_title_edit_card)
}
requireView().findViewById(R.id.CardEditorTagButton).setOnClickListener {
Timber.i("NoteEditor:: Tags button pressed... opening tags editor")
@@ -960,7 +960,7 @@ class NoteEditorFragment :
cameraLauncher.launch(photoURI)
} catch (_: ActivityNotFoundException) {
Timber.w("No app found to handle image capture")
- activity?.showSnackbar(R.string.activity_start_failed)
+ activity?.showSnackbar(CommonString.activity_start_failed)
}
}
}
@@ -1044,7 +1044,7 @@ class NoteEditorFragment :
insertCloze(if (event.isAltPressed) AddClozeType.SAME_NUMBER else AddClozeType.INCREMENT_NUMBER)
// Anki Desktop warns, but still inserts the cloze
if (!isClozeType) {
- showSnackbar(R.string.note_editor_insert_cloze_no_cloze_note_type)
+ showSnackbar(CommonString.note_editor_insert_cloze_no_cloze_note_type)
}
return true
}
@@ -1150,12 +1150,12 @@ class NoteEditorFragment :
return false
}
}
- showSnackbar(resources.getString(R.string.intent_aedict_empty))
+ showSnackbar(resources.getString(CommonString.intent_aedict_empty))
return true
}
}
}
- showSnackbar(resources.getString(R.string.intent_aedict_category))
+ showSnackbar(resources.getString(CommonString.intent_aedict_category))
return true
}
@@ -1256,7 +1256,7 @@ class NoteEditorFragment :
if (closeEditorAfterSave) {
if (caller == NoteEditorCaller.NOTEEDITOR_INTENT_ADD || aedictIntent) {
- showThemedToast(requireContext(), R.string.note_message, shortLength = true)
+ showThemedToast(requireContext(), CommonString.note_message, shortLength = true)
}
closeNoteEditor(closeIntent ?: Intent())
} else {
@@ -1269,7 +1269,7 @@ class NoteEditorFragment :
private suspend fun saveNoteWithProgress() {
// adding current note to collection
val changes =
- requireActivity().withProgress(resources.getString(R.string.saving_facts)) {
+ requireActivity().withProgress(resources.getString(CommonString.saving_facts)) {
undoableOp {
addNote(editorNote!!, deckId)
}
@@ -1307,7 +1307,7 @@ class NoteEditorFragment :
addNoteJob.launch {
val noteFieldsCheck = checkNoteFieldsResponse(editorNote!!)
if (noteFieldsCheck is NoteFieldsCheckResult.Failure) {
- addNoteErrorMessage = noteFieldsCheck.localizedMessage ?: getString(R.string.something_wrong)
+ addNoteErrorMessage = noteFieldsCheck.localizedMessage ?: getString(CommonString.something_wrong)
displayErrorSavingNote()
return@launch
}
@@ -1432,6 +1432,16 @@ class NoteEditorFragment :
// because this item is already present in CardBrowser
menu.findItem(R.id.action_add_note_from_note_editor).isVisible = !inCardBrowserActivity
}
+ menu.findItem(R.id.action_toggle_previewer).apply {
+ isVisible = inNoteEditorActivity && noteEditorActivity?.hasPreviewerPane == true
+ isChecked = noteEditorActivity?.isPreviewerVisible == true
+ title =
+ if (isChecked) {
+ getString(CommonString.note_editor_hide_previewer)
+ } else {
+ getString(CommonString.note_editor_show_previewer)
+ }
+ }
if (editFields != null) {
for (i in editFields!!.indices) {
val fieldText = editFields!![i].text
@@ -1476,7 +1486,7 @@ class NoteEditorFragment :
// TODO: Move this logic to a [NoteEditorActivity.kt]
private fun allowPreviewAction(): Boolean =
when {
- inNoteEditorActivity && noteEditorActivity?.fragmented == true -> false
+ inNoteEditorActivity && noteEditorActivity?.isPreviewerVisible == true -> false
addNote && currentNotetypeIsImageOcclusion() -> false
else -> true
}
@@ -1491,6 +1501,17 @@ class NoteEditorFragment :
}
return true
}
+ R.id.action_toggle_previewer -> {
+ Timber.i("NoteEditor:: Toggle previewer pressed")
+ val editorActivity = noteEditorActivity
+ if (editorActivity == null) {
+ Timber.w("NoteEditor:: Toggle previewer pressed outside of NoteEditorActivity")
+ return true
+ }
+ editorActivity.setPreviewerVisible(!editorActivity.isPreviewerVisible)
+ editorActivity.invalidateOptionsMenu()
+ return true
+ }
R.id.action_save -> {
Timber.i("NoteEditor:: Save note button pressed")
if (allowSaveAction()) {
@@ -1511,7 +1532,7 @@ class NoteEditorFragment :
R.id.action_font_size -> {
Timber.i("NoteEditor:: Font Size button pressed")
val fontSizeDialog = IntegerDialog()
- fontSizeDialog.setArgs(getString(R.string.menu_font_size), editTextFontSize, 2)
+ fontSizeDialog.setArgs(getString(CommonString.menu_font_size), editTextFontSize, 2)
fontSizeDialog.setCallbackRunnable { fontSizeSp: Int? -> setFontSize(fontSizeSp) }
showDialogFragment(fontSizeDialog)
return true
@@ -1901,9 +1922,9 @@ class NoteEditorFragment :
previous.lastViewInTabOrder.nextFocusForwardId = R.id.CardEditorTagButton
}
mediaButton.contentDescription =
- getString(R.string.multimedia_editor_attach_mm_content, editLineView.name)
+ getString(CommonString.multimedia_editor_attach_mm_content, editLineView.name)
toggleStickyButton.contentDescription =
- getString(R.string.note_editor_toggle_sticky, editLineView.name)
+ getString(CommonString.note_editor_toggle_sticky, editLineView.name)
editLineView.isVisible = i !in indicesToHide
fieldsLayoutContainer!!.addView(editLineView)
@@ -1916,7 +1937,7 @@ class NoteEditorFragment :
): ActionMode.Callback =
CustomActionModeCallback(
isClozeType,
- getString(R.string.multimedia_editor_popup_cloze),
+ getString(CommonString.multimedia_editor_popup_cloze),
clozeMenuId,
onActionItemSelected = { mode, item ->
if (item.itemId == clozeMenuId) {
@@ -1946,11 +1967,11 @@ class NoteEditorFragment :
val limitStr = Backend.MAX_INDIVIDUAL_MEDIA_FILE_SIZE.toBytesShortString(context)
MaterialAlertDialogBuilder(context).show {
- title(R.string.media_file_size_warning_title)
+ title(CommonString.media_file_size_warning_title)
iconAttr(R.drawable.ic_warning)
- message(text = getString(R.string.media_file_size_warning_message, fileName, fileSizeStr, limitStr))
- positiveButton(R.string.dialog_cancel)
- negativeButton(R.string.media_file_size_add_anyway) {
+ message(text = getString(CommonString.media_file_size_warning_message, fileName, fileSizeStr, limitStr))
+ positiveButton(CommonString.dialog_cancel)
+ negativeButton(CommonString.media_file_size_add_anyway) {
onForceAdd()
}
}
@@ -2236,7 +2257,7 @@ class NoteEditorFragment :
val bars = getInsets(systemBars() or displayCutout() or ime())
return insetsOf(
left = if (inCardBrowserActivity) 0 else bars.left,
- right = if (noteEditorActivity?.fragmented == true) 0 else bars.right,
+ right = if (noteEditorActivity?.isPreviewerVisible == true) 0 else bars.right,
bottom = bars.bottom,
)
}
@@ -2250,7 +2271,7 @@ class NoteEditorFragment :
val corners = insets.bottomCornerSideClearance(bars.bottom)
toolbar.setSideClearance(
left = if (inCardBrowserActivity) 0 else (corners.left - bars.left).coerceAtLeast(0),
- right = if (noteEditorActivity?.fragmented == true) 0 else (corners.right - bars.right).coerceAtLeast(0),
+ right = if (noteEditorActivity?.isPreviewerVisible == true) 0 else (corners.right - bars.right).coerceAtLeast(0),
)
applyBottomInset()
insets
@@ -2324,8 +2345,8 @@ class NoteEditorFragment :
val drawable = ResourcesCompat.getDrawable(resources, R.drawable.ic_add_toolbar_icon, null)
drawable!!.setTint(MaterialColors.getColor(requireContext(), R.attr.toolbarIconColor, 0))
val addButton = toolbar.insertItem(0, drawable) { displayAddToolbarDialog() }
- addButton.contentDescription = resources.getString(R.string.add_toolbar_item)
- addButton.setTooltipTextCompat(resources.getString(R.string.add_toolbar_item))
+ addButton.contentDescription = resources.getString(CommonString.add_toolbar_item)
+ addButton.setTooltipTextCompat(resources.getString(CommonString.add_toolbar_item))
}
private val toolbarButtons: ArrayList
@@ -2381,12 +2402,12 @@ class NoteEditorFragment :
editToolbarItemDialog: AlertDialog,
) {
AlertDialog.Builder(requireContext()).show {
- title(R.string.remove_toolbar_item)
- positiveButton(R.string.dialog_positive_delete) {
+ title(CommonString.remove_toolbar_item)
+ positiveButton(CommonString.dialog_positive_delete) {
editToolbarItemDialog.dismiss()
removeButton(button)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
@@ -2401,16 +2422,16 @@ class NoteEditorFragment :
get() =
AlertDialog
.Builder(requireContext())
- .neutralButton(R.string.help) {
+ .neutralButton(CommonString.help) {
requireContext().openUrl(R.string.link_manual_note_format_toolbar)
- }.negativeButton(R.string.dialog_cancel)
+ }.negativeButton(CommonString.dialog_cancel)
private fun displayAddToolbarDialog() {
val v = layoutInflater.inflate(R.layout.dialog_note_editor_toolbar_add_custom_item, null)
toolbarDialog.show {
- title(R.string.add_toolbar_item)
+ title(CommonString.add_toolbar_item)
setView(v)
- positiveButton(R.string.dialog_positive_create) {
+ positiveButton(CommonString.dialog_positive_create) {
val etIcon = v.findViewById(R.id.note_editor_toolbar_item_icon)
val et = v.findViewById(R.id.note_editor_toolbar_before)
val et2 = v.findViewById(R.id.note_editor_toolbar_after)
@@ -2431,7 +2452,7 @@ class NoteEditorFragment :
val editToolbarDialog =
toolbarDialog
.setView(view)
- .positiveButton(R.string.save) {
+ .positiveButton(CommonString.save) {
editToolbarButton(
etIcon.text.toString(),
et.text.toString(),
@@ -2459,15 +2480,15 @@ class NoteEditorFragment :
get() =
ShortcutGroup(
listOf(
- shortcut("Ctrl+ENTER") { getString(R.string.save) },
+ shortcut("Ctrl+ENTER") { getString(CommonString.save) },
shortcut("Ctrl+D") { TR.sentenceCase.selectDeck },
- shortcut("Ctrl+L") { getString(R.string.card_template_editor_group) },
- shortcut("Ctrl+N") { getString(R.string.select_note_type) },
- shortcut("Ctrl+Shift+T") { getString(R.string.tag_editor) },
- shortcut("Ctrl+Shift+C") { getString(R.string.multimedia_editor_popup_cloze) },
- shortcut("Ctrl+P") { getString(R.string.card_editor_preview_card) },
+ shortcut("Ctrl+L") { getString(CommonString.card_template_editor_group) },
+ shortcut("Ctrl+N") { getString(CommonString.select_note_type) },
+ shortcut("Ctrl+Shift+T") { getString(CommonString.tag_editor) },
+ shortcut("Ctrl+Shift+C") { getString(CommonString.multimedia_editor_popup_cloze) },
+ shortcut("Ctrl+P") { getString(CommonString.card_editor_preview_card) },
),
- R.string.note_editor_group,
+ CommonString.note_editor_group,
)
private fun updateTags() {
@@ -2476,7 +2497,7 @@ class NoteEditorFragment :
}
tagsButton!!.text =
resources.getString(
- R.string.CardEditorTags,
+ CommonString.CardEditorTags,
getColUnsafe.tags
.join(getColUnsafe.tags.canonify(selectedTags!!))
.trim()
@@ -2514,7 +2535,7 @@ class NoteEditorFragment :
}
cardsButton!!.text =
HtmlCompat.fromHtml(
- resources.getString(R.string.CardEditorCards, cardsList.toString()),
+ resources.getString(CommonString.CardEditorCards, cardsList.toString()),
HtmlCompat.FROM_HTML_MODE_LEGACY,
)
}
@@ -2588,10 +2609,8 @@ class NoteEditorFragment :
currentDeck.put("mid", newId)
getColUnsafe.decks.save(currentDeck)
- // Update deck
- if (getColUnsafe.config.addingDefaultsMode == AddingDefaultsMode.DECIDE_BY_NOTE_TYPE) {
- deckId = getColUnsafe.defaultsForAdding().deckId
- }
+ // Preserves the editor's deck when the new note type has no remembered destination.
+ getColUnsafe.defaultDeckForNoteType(newId)?.let { deckId = it }
refreshNoteData(FieldChangeType.changeFieldCount(shouldReplaceNewlines()))
setDuplicateFieldStyles()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteTypeFieldEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NoteTypeFieldEditor.kt
index 6cf6309ab92d..b24855f06423 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteTypeFieldEditor.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteTypeFieldEditor.kt
@@ -153,7 +153,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
val noteTypeID = intent.getLongExtra(EXTRA_NOTETYPE_ID, 0)
val collectionModel = getColUnsafe.notetypes.get(noteTypeID)
if (collectionModel == null) {
- showThemedToast(this, R.string.field_editor_model_not_available, true)
+ showThemedToast(this, CommonString.field_editor_model_not_available, true)
finish()
return
}
@@ -193,11 +193,11 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
}
input = input.substring(offset).trim()
if (input.isEmpty()) {
- showThemedToast(this, resources.getString(R.string.toast_empty_name), true)
+ showThemedToast(this, resources.getString(CommonString.toast_empty_name), true)
return null
}
if (fieldsLabels.any { input == it }) {
- showThemedToast(this, resources.getString(R.string.toast_duplicate_field), true)
+ showThemedToast(this, resources.getString(CommonString.toast_duplicate_field), true)
return null
}
return input
@@ -216,7 +216,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
AlertDialog.Builder(this).show {
customView(view = fieldNameInput, paddingStart = 64, paddingEnd = 64, paddingTop = 32)
title(text = TR.sentenceCase.addField)
- positiveButton(R.string.menu_add) {
+ positiveButton(CommonString.menu_add) {
// Name is valid, now field is added
val fieldName = uniqueName(fieldNameInput)
try {
@@ -226,7 +226,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
// Create dialogue to for schema change
val c = ConfirmationDialog()
- c.setArgs(resources.getString(R.string.full_sync_confirmation))
+ c.setArgs(resources.getString(CommonString.full_sync_confirmation))
val confirm =
Runnable {
try {
@@ -242,7 +242,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
getColUnsafe.notetypes.update(notetype)
initialize()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
}
@@ -290,7 +290,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
}
if (fieldsLabels.size < 2) {
- showThemedToast(this, resources.getString(R.string.toast_last_field), true)
+ showThemedToast(this, resources.getString(CommonString.toast_last_field), true)
} else {
try {
getColUnsafe.modSchema(check = true)
@@ -298,8 +298,8 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
ConfirmationDialog().let {
it.setArgs(
title = fieldName,
- message = resources.getString(R.string.field_delete_warning),
- positiveButtonText = getString(R.string.dialog_positive_delete),
+ message = resources.getString(CommonString.field_delete_warning),
+ positiveButtonText = getString(CommonString.dialog_positive_delete),
)
it.setConfirm(confirm)
showDialogFragment(it)
@@ -308,7 +308,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
e.log()
ConfirmationDialog().let {
it.setConfirm(confirm)
- it.setArgs(resources.getString(R.string.full_sync_confirmation))
+ it.setArgs(resources.getString(CommonString.full_sync_confirmation))
showDialogFragment(it)
}
}
@@ -318,7 +318,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
private fun deleteField() {
launchCatchingTask {
Timber.d("doInBackGroundDeleteField")
- withProgress(message = getString(R.string.model_field_editor_changing)) {
+ withProgress(message = getString(CommonString.model_field_editor_changing)) {
val result =
withCol {
try {
@@ -350,8 +350,8 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
fieldNameInput.moveCursorToEnd()
AlertDialog.Builder(this).show {
customView(view = fieldNameInput, paddingStart = 64, paddingEnd = 64, paddingTop = 32)
- title(R.string.model_field_editor_rename)
- positiveButton(R.string.rename) {
+ title(CommonString.model_field_editor_rename)
+ positiveButton(CommonString.rename) {
if (uniqueName(fieldNameInput) == null) {
return@positiveButton
}
@@ -363,7 +363,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
// Handler mod schema confirmation
val c = ConfirmationDialog()
- c.setArgs(resources.getString(R.string.full_sync_confirmation))
+ c.setArgs(resources.getString(CommonString.full_sync_confirmation))
val confirm =
Runnable {
getColUnsafe.modSchema(check = false)
@@ -378,7 +378,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
this@NoteTypeFieldEditor.showDialogFragment(c)
}
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
}
@@ -400,12 +400,12 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
AlertDialog
.Builder(this)
.show {
- title(R.string.model_field_editor_reposition_menu)
+ title(CommonString.model_field_editor_reposition_menu)
positiveButton(text = TR.actionsReposition()) {
val input = (it as AlertDialog).getInputField()
result(input.text.toString().toInt())
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
setMessage(TR.fieldsNewPosition1(numberOfTemplates))
setView(R.layout.dialog_generic_text_input)
}.input(
@@ -432,7 +432,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
// Handle mod schema confirmation
val c = ConfirmationDialog()
- c.setArgs(resources.getString(R.string.full_sync_confirmation))
+ c.setArgs(resources.getString(CommonString.full_sync_confirmation))
val confirm =
Runnable {
try {
@@ -450,7 +450,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
private fun repositionField(index: Int) {
launchCatchingTask {
- withProgress(message = getString(R.string.model_field_editor_changing)) {
+ withProgress(message = getString(CommonString.model_field_editor_changing)) {
val result =
withCol {
Timber.d("doInBackgroundRepositionField")
@@ -497,7 +497,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
e.log()
// Handler mMod schema confirmation
val c = ConfirmationDialog()
- c.setArgs(resources.getString(R.string.full_sync_confirmation))
+ c.setArgs(resources.getString(CommonString.full_sync_confirmation))
val confirm =
Runnable {
getColUnsafe.modSchema(check = false)
@@ -512,7 +512,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
notetype: NotetypeJson,
idx: Int,
) {
- withProgress(resources.getString(R.string.model_field_editor_changing)) {
+ withProgress(resources.getString(CommonString.model_field_editor_changing)) {
withCol {
Timber.d("doInBackgroundChangeSortField")
notetypes.setSortIndex(notetype, idx)
@@ -548,7 +548,7 @@ class NoteTypeFieldEditor : AnkiActivity(R.layout.activity_note_type_field_edito
*/
private fun addFieldLocaleHint(selectedLocale: Locale) {
setLanguageHintForField(getColUnsafe.notetypes, notetype, currentPos, selectedLocale)
- val format = getString(R.string.model_field_editor_language_hint_dialog_success_result, selectedLocale.displayName)
+ val format = getString(CommonString.model_field_editor_language_hint_dialog_success_result, selectedLocale.displayName)
showSnackbar(format, Snackbar.LENGTH_SHORT)
initialize()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NotificationChannel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NotificationChannel.kt
index 30a05d7c52f1..7d14741477a5 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/NotificationChannel.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/NotificationChannel.kt
@@ -68,8 +68,8 @@ enum class NotificationChannel(
val importance: Int,
) {
GENERAL("General Notifications", R.string.app_name, NotificationManagerCompat.IMPORTANCE_DEFAULT),
- SYNC("Synchronization", R.string.sync_title, NotificationManagerCompat.IMPORTANCE_LOW),
- REVIEW_REMINDERS("Review Reminders", R.string.review_reminders_title, NotificationManagerCompat.IMPORTANCE_DEFAULT),
+ SYNC("Synchronization", CommonString.sync_title, NotificationManagerCompat.IMPORTANCE_LOW),
+ REVIEW_REMINDERS("Review Reminders", CommonString.review_reminders_title, NotificationManagerCompat.IMPORTANCE_DEFAULT),
;
fun getName(res: Resources) = res.getString(nameId)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.kt
index 9a258e9009de..adb77a528c74 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.kt
@@ -69,7 +69,7 @@ object ReadText {
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
showThemedToast(
flashCardViewer.get()!!,
- flashCardViewer.get()!!.getString(R.string.no_tts_available_message) +
+ flashCardViewer.get()!!.getString(CommonString.no_tts_available_message) +
" (" + loc + ")",
false,
)
@@ -115,13 +115,13 @@ object ReadText {
if (availableLocales().isEmpty()) {
Timber.w("ReadText.textToSpeech() no TTS languages available")
dialog
- .message(R.string.no_tts_available_message)
+ .message(CommonString.no_tts_available_message)
.setIcon(R.drawable.ic_warning)
- .positiveButton(R.string.dialog_ok)
+ .positiveButton(CommonString.dialog_ok)
} else {
val localeMappings: List> =
mutableListOf>().apply {
- add(Pair(NO_TTS, res.getString(R.string.tts_no_tts))) // add option: "no tts"
+ add(Pair(NO_TTS, res.getString(CommonString.tts_no_tts))) // add option: "no tts"
val (validLocales, invalidLocales) =
availableLocales()
.sortedWith(compareBy { it.displayName })
@@ -137,7 +137,7 @@ object ReadText {
}
Timber.i("showing 'select language' dialog")
dialog
- .title(R.string.select_locale_title)
+ .title(CommonString.select_locale_title)
.setItems(localeMappings.map { it.second }.toTypedArray()) { _, index ->
val locale = localeMappings[index].first
Timber.d("ReadText.selectTts() user chose locale '%s'", locale)
@@ -267,7 +267,7 @@ object ReadText {
// they originally requested)
showThemedToast(
flashCardViewer.get()!!,
- flashCardViewer.get()!!.getString(R.string.no_tts_available_message) +
+ flashCardViewer.get()!!.getString(CommonString.no_tts_available_message) +
" (" + originalLocaleCode + ")",
false,
)
@@ -301,7 +301,7 @@ object ReadText {
Timber.d("TTS initialized and available languages found")
(context as AbstractFlashcardViewer).ttsInitialized()
} else {
- ankiActivityContext?.showSnackbar(R.string.no_tts_available_message)
+ ankiActivityContext?.showSnackbar(CommonString.no_tts_available_message)
Timber.w("TTS initialized but no available languages found")
}
textToSpeech!!.setOnUtteranceProgressListener(
@@ -324,8 +324,8 @@ object ReadText {
val ankiActivity = context as AnkiActivity
ankiActivity.mayOpenUrl(helpUrl)
// TODO: We can do better in this UI now we have a reason for failure
- ankiActivity.showSnackbar(R.string.no_tts_available_message) {
- setAction(R.string.help) { openTtsHelpUrl(helpUrl) }
+ ankiActivity.showSnackbar(CommonString.no_tts_available_message) {
+ setAction(CommonString.help) { openTtsHelpUrl(helpUrl) }
}
}
@@ -342,7 +342,7 @@ object ReadText {
},
)
} else {
- showThemedToast(context, context.getString(R.string.no_tts_available_message), false)
+ showThemedToast(context, context.getString(CommonString.no_tts_available_message), false)
Timber.w("TTS not successfully initialized")
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt
index bd00120241c1..886924b0fed4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.kt
@@ -836,10 +836,10 @@ open class Reviewer :
if (whiteboard != null) {
try {
val savedWhiteboardFileName = whiteboard!!.saveWhiteboard(TimeManager.time).path
- showSnackbar(getString(R.string.white_board_image_saved, savedWhiteboardFileName), Snackbar.LENGTH_SHORT)
+ showSnackbar(getString(CommonString.white_board_image_saved, savedWhiteboardFileName), Snackbar.LENGTH_SHORT)
} catch (e: Exception) {
Timber.w(e)
- showSnackbar(getString(R.string.white_board_image_save_failed, e.localizedMessage), Snackbar.LENGTH_SHORT)
+ showSnackbar(getString(CommonString.white_board_image_save_failed, e.localizedMessage), Snackbar.LENGTH_SHORT)
}
}
}
@@ -938,10 +938,10 @@ open class Reviewer :
if (isEraserMode) {
startMonitoringEraserButtonRipple()
- showSnackbar(getString(R.string.white_board_eraser_enabled), 1000)
+ showSnackbar(getString(CommonString.white_board_eraser_enabled), 1000)
} else {
stopMonitoringEraserButtonRipple()
- showSnackbar(getString(R.string.white_board_eraser_disabled), 1000)
+ showSnackbar(getString(CommonString.white_board_eraser_disabled), 1000)
}
}
}
@@ -1097,7 +1097,7 @@ open class Reviewer :
CrashReportService.sendExceptionReport(e, "Unable to create recorder tool bar")
showThemedToast(
this,
- this.getText(R.string.multimedia_editor_audio_view_create_failed).toString(),
+ this.getText(CommonString.multimedia_editor_audio_view_create_failed).toString(),
true,
)
}
@@ -1147,7 +1147,7 @@ open class Reviewer :
@NeedsTest("Starting animation from swipe is inverse to the finishing one")
protected fun openCardInfo(fromGesture: Gesture? = null) {
if (currentCard == null) {
- showSnackbar(getString(R.string.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
+ showSnackbar(getString(CommonString.multimedia_editor_something_wrong), Snackbar.LENGTH_SHORT)
return
}
Timber.i("opening card info")
@@ -1182,7 +1182,7 @@ open class Reviewer :
val alpha = Themes.ALPHA_ICON_ENABLED_LIGHT
val markCardIcon = menu.findItem(R.id.action_mark_card)
if (currentCard != null && isMarked(getColUnsafe, currentCard!!.note(getColUnsafe))) {
- markCardIcon.setTitle(R.string.menu_unmark_note).setIcon(R.drawable.ic_star_white)
+ markCardIcon.setTitle(CommonString.menu_unmark_note).setIcon(R.drawable.ic_star_white)
} else {
markCardIcon.setTitle(TR.sentenceCase.markNote).setIcon(R.drawable.ic_star_border_white)
}
@@ -1242,7 +1242,7 @@ open class Reviewer :
if (colIsOpenUnsafe()) { // Required mostly because there are tests where `col` is null
if (whiteboardIsShownAndHasStrokes) {
// Make Undo action title to whiteboard Undo specific one
- undoIcon.title = resources.getString(R.string.undo_action_whiteboard_last_stroke)
+ undoIcon.title = resources.getString(CommonString.undo_action_whiteboard_last_stroke)
// Show whiteboard Eraser action button
if (!actionButtons.status.toggleEraserIsDisabled()) {
@@ -1260,7 +1260,7 @@ open class Reviewer :
// In this case, there is no object word for the verb, "Undo",
// so in some languages such as Japanese, which have pre/post-positional particle with the object,
// we need to use the string for just "Undo" instead of the string for "Undo %s".
- undoIcon.title = resources.getString(R.string.undo)
+ undoIcon.title = resources.getString(CommonString.undo)
undoIcon.iconAlpha = Themes.ALPHA_ICON_DISABLED_LIGHT
}
}
@@ -1276,7 +1276,7 @@ open class Reviewer :
iconAlpha = Themes.ALPHA_ICON_ENABLED_LIGHT
isEnabled = true
} else {
- setTitle(R.string.redo)
+ setTitle(CommonString.redo)
iconAlpha = Themes.ALPHA_ICON_DISABLED_LIGHT
isEnabled = false
}
@@ -1296,7 +1296,7 @@ open class Reviewer :
// White board button
if (prefWhiteboard) {
// Configure the whiteboard related items in the action bar
- toggleWhiteboardIcon.setTitle(R.string.disable_whiteboard)
+ toggleWhiteboardIcon.setTitle(CommonString.disable_whiteboard)
// Always allow "Disable Whiteboard", even if "Enable Whiteboard" is disabled
toggleWhiteboardIcon.isVisible = true
if (!actionButtons.status.toggleStylusIsDisabled()) {
@@ -1322,29 +1322,29 @@ open class Reviewer :
// "hide whiteboard" icon
whiteboardIcon.alpha = Themes.ALPHA_ICON_ENABLED_LIGHT
hideWhiteboardIcon.icon = whiteboardIcon
- hideWhiteboardIcon.setTitle(R.string.hide_whiteboard)
+ hideWhiteboardIcon.setTitle(CommonString.hide_whiteboard)
whiteboardColorPaletteIcon.alpha = Themes.ALPHA_ICON_ENABLED_LIGHT
// eraser icon
toggleEraserIcon.icon = eraserIcon
if (isEraserMode) {
- toggleEraserIcon.setTitle(R.string.disable_eraser)
+ toggleEraserIcon.setTitle(CommonString.disable_eraser)
} else { // default
- toggleEraserIcon.setTitle(R.string.enable_eraser)
+ toggleEraserIcon.setTitle(CommonString.enable_eraser)
}
// whiteboard editor icon
changePenColorIcon.icon = whiteboardColorPaletteIcon
if (toggleStylus) {
- toggleStylusIcon.setTitle(R.string.disable_stylus)
+ toggleStylusIcon.setTitle(CommonString.disable_stylus)
stylusIcon.alpha = Themes.ALPHA_ICON_ENABLED_LIGHT
} else {
- toggleStylusIcon.setTitle(R.string.enable_stylus)
+ toggleStylusIcon.setTitle(CommonString.enable_stylus)
stylusIcon.alpha = Themes.ALPHA_ICON_DISABLED_LIGHT
}
toggleStylusIcon.icon = stylusIcon
} else {
whiteboardIcon.alpha = Themes.ALPHA_ICON_DISABLED_LIGHT
hideWhiteboardIcon.icon = whiteboardIcon
- hideWhiteboardIcon.setTitle(R.string.show_whiteboard)
+ hideWhiteboardIcon.setTitle(CommonString.show_whiteboard)
whiteboardColorPaletteIcon.alpha = Themes.ALPHA_ICON_DISABLED_LIGHT
stylusIcon.alpha = Themes.ALPHA_ICON_DISABLED_LIGHT
toggleStylusIcon.isEnabled = false
@@ -1354,7 +1354,7 @@ open class Reviewer :
colorPalette.visibility = View.GONE
}
} else {
- toggleWhiteboardIcon.setTitle(R.string.enable_whiteboard)
+ toggleWhiteboardIcon.setTitle(CommonString.enable_whiteboard)
}
if (colIsOpenUnsafe() && getColUnsafe.decks.isFiltered(parentDid)) {
menu.findItem(R.id.action_open_deck_options).isVisible = false
@@ -1373,11 +1373,11 @@ open class Reviewer :
val voicePlaybackIcon = menu.findItem(R.id.action_toggle_mic_tool_bar)
if (isMicToolBarVisible) {
- voicePlaybackIcon.setTitle(R.string.menu_disable_voice_playback)
+ voicePlaybackIcon.setTitle(CommonString.menu_disable_voice_playback)
// #18477: always show 'disable', even if 'enable' was invisible
voicePlaybackIcon.isVisible = true
} else {
- voicePlaybackIcon.setTitle(R.string.menu_enable_voice_playback)
+ voicePlaybackIcon.setTitle(CommonString.menu_enable_voice_playback)
}
increaseHorizontalPaddingOfOverflowMenuIcons(menu)
@@ -1446,10 +1446,10 @@ open class Reviewer :
easeButton1!!.setColor(background[0])
easeButton4!!.setColor(background[3])
// Ease 2 is "hard"
- easeButton2!!.setup(background[1], textColor[1], R.string.ease_button_hard)
+ easeButton2!!.setup(background[1], textColor[1], CommonString.ease_button_hard)
easeButton2!!.requestFocus()
// Ease 3 is good
- easeButton3!!.setup(background[2], textColor[2], R.string.ease_button_good)
+ easeButton3!!.setup(background[2], textColor[2], CommonString.ease_button_good)
easeButton4!!.setVisibility(View.VISIBLE)
easeButton3!!.requestFocus()
@@ -1591,9 +1591,9 @@ open class Reviewer :
state.topCard.load(getColUnsafe)
val leechMessage: String =
if (state.topCard.queue.buriedOrSuspended()) {
- resources.getString(R.string.leech_suspend_notification)
+ resources.getString(CommonString.leech_suspend_notification)
} else {
- resources.getString(R.string.leech_notification)
+ resources.getString(CommonString.leech_notification)
}
showSnackbar(leechMessage, Snackbar.LENGTH_SHORT)
}
@@ -1609,14 +1609,14 @@ open class Reviewer :
private suspend fun dealWithTimeBox(timebox: Collection.TimeboxReached) {
val nCards = timebox.reps
val nMins = timebox.secs / 60
- val mins = resources.getQuantityString(R.plurals.in_minutes, nMins, nMins)
- val timeboxMessage = resources.getQuantityString(R.plurals.timebox_reached, nCards, nCards, mins)
+ val mins = resources.getQuantityString(CommonPlurals.in_minutes, nMins, nMins)
+ val timeboxMessage = resources.getQuantityString(CommonPlurals.timebox_reached, nCards, nCards, mins)
suspendCancellableCoroutine { coroutines ->
Timber.i("Showing timebox reached dialog")
AlertDialog.Builder(this).show {
- title(R.string.timebox_reached_title)
+ title(CommonString.timebox_reached_title)
message(text = timeboxMessage)
- positiveButton(R.string.dialog_continue) {
+ positiveButton(CommonString.dialog_continue) {
coroutines.resume(Unit)
}
negativeButton(text = CollectionManager.TR.studyingFinish()) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StringResAliases.kt b/AnkiDroid/src/main/java/com/ichi2/anki/StringResAliases.kt
new file mode 100644
index 000000000000..d054529dc01c
--- /dev/null
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/StringResAliases.kt
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki
+
+/**
+ * String resources: `getString(CommonString.card_browser)`.
+ *
+ * A plain import (`import com.ichi2.anki.CommonString`), which the IDE auto-inserts,
+ * unlike `as` alias imports. Files in package `com.ichi2.anki` need no import.
+ */
+typealias CommonString = com.ichi2.anki.R.string
+
+/** Plural resources: `getQuantityString(CommonPlurals.widget_cards_due, n, n)`. */
+typealias CommonPlurals = com.ichi2.anki.R.plurals
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt
index 07d0bdd8616a..c4f18b8e9b03 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.kt
@@ -243,7 +243,7 @@ class StudyOptionsFragment :
R.id.action_rebuild -> {
Timber.i("StudyOptionsFragment:: rebuild cram deck button pressed")
launchCatchingTask {
- withProgress(R.string.rebuild_filtered_deck) {
+ withProgress(CommonString.rebuild_filtered_deck) {
viewModel.rebuildCram()
}
}
@@ -252,7 +252,7 @@ class StudyOptionsFragment :
R.id.action_empty -> {
Timber.i("StudyOptionsFragment:: empty cram deck button pressed")
launchCatchingTask {
- withProgress(R.string.empty_filtered_deck) {
+ withProgress(CommonString.empty_filtered_deck) {
viewModel.emptyCram()
}
}
@@ -272,7 +272,7 @@ class StudyOptionsFragment :
menu.findItem(R.id.action_rebuild)?.isVisible = true
menu.findItem(R.id.action_empty)?.isVisible = true
menu.findItem(R.id.action_custom_study)?.isVisible = false
- menu.findItem(R.id.action_deck_or_study_options)?.setTitle(R.string.menu__study_options)
+ menu.findItem(R.id.action_deck_or_study_options)?.setTitle(CommonString.menu__study_options)
} else {
menu.findItem(R.id.action_rebuild)?.isVisible = false
menu.findItem(R.id.action_empty)?.isVisible = false
@@ -344,7 +344,7 @@ class StudyOptionsFragment :
is StudyOptionsState.StudyOptions -> {
deckInfoLayout.visibility = View.VISIBLE
buttonStart.visibility = View.VISIBLE
- buttonStart.setText(R.string.studyoptions_start)
+ buttonStart.setText(CommonString.studyoptions_start)
}
is StudyOptionsState.Loading -> return
}
@@ -365,7 +365,7 @@ class StudyOptionsFragment :
@Language("HTML")
val desc: String =
if (isDynamic) {
- resources.getString(R.string.dyn_deck_desc)
+ resources.getString(CommonString.dyn_deck_desc)
} else {
description ?: ""
}
@@ -400,7 +400,7 @@ class StudyOptionsFragment :
when {
count > 0 ->
requireContext().resources.getQuantityString(
- R.plurals.studyoptions_buried_count,
+ CommonPlurals.studyoptions_buried_count,
count,
count,
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
index 1348196e5eb5..54ea704fb7d4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/Sync.kt
@@ -104,6 +104,10 @@ fun DeckPicker.handleNewSync(
// auth failed; log out
updateLogin("", "")
throw exc
+ } catch (exc: BackendSyncException) {
+ if (!exc.isClockOffException()) throw exc
+ showSyncErrorDialog(SyncErrorDialog.Type.DIALOG_SYNC_CLOCK_OFF, exc.localizedMessage)
+ return@launchCatchingTask
}
withCol { notetypes.clearCache() }
notifySubscribersAllValuesChanged(deckPicker)
@@ -116,6 +120,14 @@ fun DeckPicker.handleNewSync(
}
}
+/** The backend currently exposes ClockIncorrect as a localized sync error message. */
+private fun BackendSyncException.isClockOffException(): Boolean =
+ try {
+ message == TR.syncClockOff()
+ } catch (_: Throwable) {
+ false
+ }
+
fun updateLogin(
username: String,
hkey: String,
@@ -144,7 +156,7 @@ private suspend fun handleNormalSync(
}
},
onCancel = ::cancelSync,
- manualCancelButton = R.string.dialog_cancel,
+ manualCancelButton = CommonString.dialog_cancel,
) {
withColExclusive(
operation = CollectionOperation.SYNC,
@@ -176,7 +188,7 @@ private suspend fun handleNormalSync(
SyncCollectionResponse.ChangesRequired.NO_CHANGES -> {
// scheduler version may have changed
withCol { _loadScheduler() }
- val message = if (syncMedia) R.string.col_synced_media_in_background else R.string.sync_database_acknowledge
+ val message = if (syncMedia) CommonString.col_synced_media_in_background else CommonString.sync_database_acknowledge
deckPicker.showSyncLogMessage(message, output.serverMessage)
deckPicker.refreshState()
if (syncMedia) {
@@ -226,7 +238,7 @@ private suspend fun handleDownload(
progressContext = ProgressContext.ofBytes(context = deckPicker).copy(separator = "\n"),
extractProgress = fullDownloadProgress(TR.syncDownloadingFromAnkiweb()),
onCancel = ::cancelSync,
- manualCancelButton = R.string.dialog_cancel,
+ manualCancelButton = CommonString.dialog_cancel,
) {
withColExclusive(
operation = CollectionOperation.FULL_DOWNLOAD,
@@ -251,7 +263,7 @@ private suspend fun handleDownload(
}
Timber.i("Full Download Completed")
- deckPicker.showSyncLogMessage(R.string.backup_one_way_sync_from_server, "")
+ deckPicker.showSyncLogMessage(CommonString.backup_one_way_sync_from_server, "")
}
private suspend fun handleUpload(
@@ -264,7 +276,7 @@ private suspend fun handleUpload(
progressContext = ProgressContext.ofBytes(context = deckPicker).copy(separator = "\n"),
extractProgress = fullDownloadProgress(TR.syncUploadingToAnkiweb()),
onCancel = ::cancelSync,
- manualCancelButton = R.string.dialog_cancel,
+ manualCancelButton = CommonString.dialog_cancel,
) {
withColExclusive(
operation = CollectionOperation.FULL_UPLOAD,
@@ -283,7 +295,7 @@ private suspend fun handleUpload(
}
}
Timber.i("Full Upload Completed")
- deckPicker.showSyncLogMessage(R.string.sync_log_uploading_message, "")
+ deckPicker.showSyncLogMessage(CommonString.sync_log_uploading_message, "")
}
fun cancelMediaSync(backend: Backend) {
@@ -314,7 +326,7 @@ suspend fun monitorMediaSync(deckPicker: DeckPicker) {
.Builder(deckPicker)
.setTitle(with(deckPicker) { TR.sentenceCase.mediaSyncLog })
.setMessage("")
- .setPositiveButton(R.string.dialog_continue) { _, _ ->
+ .setPositiveButton(CommonString.dialog_continue) { _, _ ->
scope.cancel()
}.setNegativeButton(TR.syncAbortButton()) { _, _ ->
isAborted = true
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ViewerResourceHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ViewerResourceHandler.kt
index d26379e76ed7..6fccdad86ecd 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ViewerResourceHandler.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ViewerResourceHandler.kt
@@ -13,11 +13,9 @@ import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileInputStream
-import java.nio.file.Paths
-import kotlin.io.path.pathString
private const val RANGE_HEADER = "Range"
-private const val MATHJAX_PATH_PREFIX = "/_anki/js/vendor/mathjax"
+private const val MATHJAX_PATH_PREFIX = "/_anki/js/vendor/mathjax/"
class ViewerResourceHandler(
context: Context,
@@ -37,13 +35,12 @@ class ViewerResourceHandler(
}
try {
+ if (path == "/_anki/js/mathjax.js") {
+ return WebResourceResponse(guessMimeType(path), null, assetManager.open("backend/js/mathjax.js"))
+ }
if (path.startsWith(MATHJAX_PATH_PREFIX)) {
- val mathjaxAssetPath =
- Paths
- .get(
- "backend/js/vendor/mathjax",
- path.removePrefix(MATHJAX_PATH_PREFIX),
- ).pathString
+ // Asset names use forward slashes, including when tests run on Windows.
+ val mathjaxAssetPath = "backend/js/vendor/mathjax/${path.removePrefix(MATHJAX_PATH_PREFIX)}"
val inputStream = assetManager.open(mathjaxAssetPath)
return WebResourceResponse(guessMimeType(path), null, inputStream)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginFragment.kt
index e335438adef3..1a6dd2b8711a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginFragment.kt
@@ -29,6 +29,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.account.AccountActivity.Companion.START_FROM_DECKPICKER
@@ -287,11 +288,11 @@ class LoginFragment : Fragment(R.layout.fragment_my_account) {
MaterialAlertDialogBuilder(requireContext()).show {
Timber.i("Showing dialog: 'Sync now?'")
- setTitle(R.string.login_successful)
+ setTitle(CommonString.login_successful)
setIcon(R.drawable.ic_sync)
- setMessage(R.string.sync_now)
- positiveButton(R.string.button_sync) { openDeckPickerAndSync() }
- negativeButton(R.string.dialog_continue) { showLoggedInView() }
+ setMessage(CommonString.sync_now)
+ positiveButton(CommonString.button_sync) { openDeckPickerAndSync() }
+ negativeButton(CommonString.dialog_continue) { showLoggedInView() }
setOnCancelListener { showLoggedInView() }
}
}
@@ -316,7 +317,7 @@ class LoginFragment : Fragment(R.layout.fragment_my_account) {
lifecycleScope.launch {
requireActivity().withProgress(
extractProgress = {
- text = getString(R.string.sign_in)
+ text = getString(CommonString.sign_in)
},
onCancel = { backend -> backend.setWantsAbort() },
) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginViewModel.kt
index 84fff1b55249..7db3b11b4ded 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginViewModel.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/account/LoginViewModel.kt
@@ -23,7 +23,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import anki.sync.SyncAuth
import com.ichi2.anki.CollectionManager.withCol
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.settings.Prefs
import kotlinx.coroutines.flow.MutableStateFlow
@@ -128,8 +128,8 @@ class LoginViewModel : ViewModel() {
enum class LoginError(
@StringRes private val messageResId: Int,
) {
- EMPTY_USERNAME(R.string.invalid_email),
- EMPTY_PASSWORD(R.string.password_empty),
+ EMPTY_USERNAME(CommonString.invalid_email),
+ EMPTY_PASSWORD(CommonString.password_empty),
;
fun toHumanReadableString(context: Context): String = context.getString(this.messageResId)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnkiDroidCrashReportDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnkiDroidCrashReportDialog.kt
index 9b1a21f9d05d..cd1729bcd8d0 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnkiDroidCrashReportDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnkiDroidCrashReportDialog.kt
@@ -10,6 +10,7 @@ import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import androidx.core.content.edit
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.crashreporting.CrashReporter
@@ -38,9 +39,9 @@ class AnkiDroidCrashReportDialog :
super.onCreate(savedInstanceState)
val dialogBuilder = AlertDialog.Builder(this)
dialogBuilder.setIcon(R.drawable.logo_star_144dp)
- dialogBuilder.setTitle(R.string.feedback_title)
- dialogBuilder.setPositiveButton(getString(R.string.feedback_report), this@AnkiDroidCrashReportDialog)
- dialogBuilder.setNegativeButton(R.string.dialog_cancel, this@AnkiDroidCrashReportDialog)
+ dialogBuilder.setTitle(CommonString.feedback_title)
+ dialogBuilder.setPositiveButton(getString(CommonString.feedback_report), this@AnkiDroidCrashReportDialog)
+ dialogBuilder.setNegativeButton(CommonString.dialog_cancel, this@AnkiDroidCrashReportDialog)
helper = CrashReportDialogHelper(this, intent)
dialogBuilder.setView(buildCustomView(savedInstanceState))
val dialog = dialogBuilder.create()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/android/back/ExitViaDoubleTapBackBackPressCallback.kt b/AnkiDroid/src/main/java/com/ichi2/anki/android/back/ExitViaDoubleTapBackBackPressCallback.kt
index 8771ce53bf67..c79ceb306046 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/android/back/ExitViaDoubleTapBackBackPressCallback.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/android/back/ExitViaDoubleTapBackBackPressCallback.kt
@@ -7,6 +7,7 @@ import androidx.activity.OnBackPressedCallback
import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.AnkiActivity
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.android.appContext
import com.ichi2.anki.common.utils.android.HandlerUtils
@@ -57,7 +58,7 @@ fun AnkiActivity.exitViaDoubleTapBackCallback(): OnBackPressedCallback =
lateinit var strongListenerReference: OnSharedPreferenceChangeListener
override fun handleOnBackPressed() {
- showSnackbar(R.string.back_pressed_once, Snackbar.LENGTH_SHORT)
+ showSnackbar(CommonString.back_pressed_once, Snackbar.LENGTH_SHORT)
this.isEnabled = false
HandlerUtils.executeFunctionWithDelay(2.seconds.inWholeMilliseconds) {
this.isEnabled = true
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionAdapter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionAdapter.kt
index 6dc3676e2dd3..75d9e53cf745 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionAdapter.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionAdapter.kt
@@ -8,6 +8,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.browser.BrowserColumnSelectionRecyclerItem.ColumnItem
import com.ichi2.anki.browser.BrowserColumnSelectionRecyclerItem.UsageItem
@@ -125,7 +126,7 @@ class BrowserColumnSelectionAdapter(
val isExclude = absoluteAdapterPosition < positionOfAvailableHeading
setImageResource(if (isExclude) R.drawable.ic_remove else R.drawable.ic_add)
- val label = context.getString(if (isExclude) R.string.exclude_column else R.string.include_column)
+ val label = context.getString(if (isExclude) CommonString.exclude_column else CommonString.include_column)
contentDescription = label
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
tooltipText = label
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionFragment.kt
index a71b197dac0e..6ac488863e6a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/BrowserColumnSelectionFragment.kt
@@ -21,6 +21,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ichi2.anki.CardBrowser
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.browser.BrowserColumnSelectionRecyclerItem.ColumnItem
import com.ichi2.anki.browser.BrowserColumnSelectionRecyclerItem.UsageItem
@@ -251,8 +252,8 @@ enum class ColumnUsage(
@StringRes val titleRes: Int,
) {
/** A column displayed in Browse */
- ACTIVE(R.string.user_active_columns),
+ ACTIVE(CommonString.user_active_columns),
/** A column which is not displayed in Browse */
- AVAILABLE(R.string.user_potential_columns),
+ AVAILABLE(CommonString.user_potential_columns),
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserColumn.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserColumn.kt
index 3451423b9826..c98e4526259d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserColumn.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserColumn.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.browser
import androidx.annotation.StringRes
import anki.search.BrowserColumns
import com.ichi2.anki.CardBrowser
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.model.CardsOrNotes
import com.ichi2.anki.model.CardsOrNotes.CARDS
@@ -196,16 +196,16 @@ fun BrowserColumns.Column.getTooltip(cardsOrNotes: CardsOrNotes): String? =
fun ColumnType.humanReadableExplanation(descending: Boolean): Int? =
if (descending) {
when (this) {
- ColumnType.TEXT -> R.string.card_browser_order_subtitle_text_descending
- ColumnType.NUMERIC -> R.string.card_browser_order_subtitle_numeric_descending
- ColumnType.DATE -> R.string.card_browser_order_subtitle_date_descending
+ ColumnType.TEXT -> CommonString.card_browser_order_subtitle_text_descending
+ ColumnType.NUMERIC -> CommonString.card_browser_order_subtitle_numeric_descending
+ ColumnType.DATE -> CommonString.card_browser_order_subtitle_date_descending
ColumnType.UNSPECIFIED -> null
}
} else {
when (this) {
- ColumnType.TEXT -> R.string.card_browser_order_subtitle_text_ascending
- ColumnType.NUMERIC -> R.string.card_browser_order_subtitle_numeric_ascending
- ColumnType.DATE -> R.string.card_browser_order_subtitle_date_ascending
+ ColumnType.TEXT -> CommonString.card_browser_order_subtitle_text_ascending
+ ColumnType.NUMERIC -> CommonString.card_browser_order_subtitle_numeric_ascending
+ ColumnType.DATE -> CommonString.card_browser_order_subtitle_date_ascending
ColumnType.UNSPECIFIED -> null
}
}
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 9c82be4302db..0da1f477aa7c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserFragment.kt
@@ -28,6 +28,7 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.CheckResult
import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.ThemeUtils
@@ -66,6 +67,8 @@ import com.ichi2.anki.CardBrowser
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.getColUnsafe
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.Flag
import com.ichi2.anki.R
import com.ichi2.anki.android.input.ShortcutGroup
@@ -127,8 +130,8 @@ import com.ichi2.anki.model.CardStateFilter
import com.ichi2.anki.model.CardsOrNotes.CARDS
import com.ichi2.anki.model.SelectableDeck
import com.ichi2.anki.observability.ChangeManager
-import com.ichi2.anki.observability.undoableOp
import com.ichi2.anki.previewer.PreviewerFragment
+import com.ichi2.anki.progress.observeProgress
import com.ichi2.anki.requireAnkiActivity
import com.ichi2.anki.requireNavigationDrawerActivity
import com.ichi2.anki.scheduling.ForgetCardsDialog
@@ -150,7 +153,6 @@ import com.ichi2.anki.utils.ext.visibleItemPositions
import com.ichi2.anki.utils.hideKeyboard
import com.ichi2.anki.withProgress
import com.ichi2.ui.CardBrowserSearchView
-import com.ichi2.utils.TagsUtil.getUpdatedTags
import com.ichi2.utils.increaseHorizontalPaddingOfOverflowMenuIcons
import com.ichi2.utils.moveCursorToEnd
import com.ichi2.utils.replaceText
@@ -320,6 +322,8 @@ class CardBrowserFragment :
(parentFragmentManager.findFragmentByTag(DeckSelectionDialog.TAG) as? DeckSelectionDialog)?.dismiss()
}
+ observeProgress(activityViewModel) { progress -> getString(progress.messageRes) }
+
// onSearchForDecks starts deck selection using childFragmentManager
childFragmentManager.setFragmentResultListener(DeckSelectionDialog.REQUEST_SELECT_DECK, this) { _, bundle ->
val selectedDeck = bundle.getParcelableCompat(ARG_SELECTED_DECK)
@@ -580,7 +584,7 @@ class CardBrowserFragment :
)
legacySearchView =
(searchItem!!.actionView as CardBrowserSearchView).apply {
- queryHint = resources.getString(R.string.card_browser_search_hint)
+ queryHint = resources.getString(CommonString.card_browser_search_hint)
setMaxWidth(Integer.MAX_VALUE)
setOnQueryTextListener(
object :
@@ -622,7 +626,7 @@ class CardBrowserFragment :
if (vm.isInMultiSelectMode) return
// qtMiscCreateFilteredDeck() contains QT Accelerators ('&') in Belarusian
- menu.findItem(R.id.action_create_filtered_deck).title = getString(R.string.new_dynamic_deck)
+ menu.findItem(R.id.action_create_filtered_deck).title = getString(CommonString.new_dynamic_deck)
saveSearchItem?.isVisible = legacySearchView?.query?.isNotEmpty() != false
@@ -775,12 +779,12 @@ class CardBrowserFragment :
this.title =
if (vm.cardsOrNotes == CARDS) {
resources.getQuantityString(
- R.plurals.card_browser_export_cards,
+ CommonPlurals.card_browser_export_cards,
vm.selectedRowCount(),
)
} else {
resources.getQuantityString(
- R.plurals.card_browser_export_notes,
+ CommonPlurals.card_browser_export_notes,
vm.selectedRowCount(),
)
}
@@ -799,7 +803,7 @@ class CardBrowserFragment :
deleteNoteItem.apply {
this.title =
resources.getQuantityString(
- R.plurals.card_browser_delete_notes,
+ CommonPlurals.card_browser_delete_notes,
vm.selectedNoteCount(),
)
}
@@ -1008,7 +1012,7 @@ class CardBrowserFragment :
showSnackbar(message, Snackbar.LENGTH_SHORT) {
if (!searchAllDecks) return@showSnackbar
- setAction(R.string.card_browser_search_all_decks) {
+ setAction(CommonString.card_browser_search_all_decks) {
activityViewModel.setSelectedDeck(SelectableDeck.AllDecks)
}
}
@@ -1024,7 +1028,7 @@ class CardBrowserFragment :
)
SearchResultMessage.NoCardsInSelectedDeck ->
showSnackbar(
- getString(R.string.card_browser_no_cards_in_deck, selectedDeckNameForUi),
+ getString(CommonString.card_browser_no_cards_in_deck, selectedDeckNameForUi),
searchAllDecks = true,
)
}
@@ -1100,14 +1104,14 @@ class CardBrowserFragment :
toggleRowSelections.contentDescription =
getString(
when (selectionState) {
- SELECT_ALL -> R.string.card_browser_select_all
- SELECT_NONE -> R.string.card_browser_select_none
+ SELECT_ALL -> CommonString.card_browser_select_all
+ SELECT_NONE -> CommonString.card_browser_select_none
},
)
}
fun onSearchForDecks(unit: Unit) {
- startDeckSelection(title = getString(R.string.search_deck), asChild = true, skipEmptyDefault = true)
+ startDeckSelection(title = getString(CommonString.search_deck), asChild = true, skipEmptyDefault = true)
}
fun advancedSearchChanged(inAdvancedSearch: Boolean) {
@@ -1151,22 +1155,22 @@ class CardBrowserFragment :
when (message) {
UserMessage.SEARCH_SAVED ->
showSnackbar(
- R.string.card_browser_list_my_searches_successful_save,
+ CommonString.card_browser_list_my_searches_successful_save,
Snackbar.LENGTH_SHORT,
)
UserMessage.SAVED_SEARCH_DUPLICATE_ADDED ->
showSnackbar(
- R.string.card_browser_list_my_searches_new_search_error_dup,
+ CommonString.card_browser_list_my_searches_new_search_error_dup,
Snackbar.LENGTH_SHORT,
)
UserMessage.SAVED_SEARCH_DELETED ->
showSnackbar(
- R.string.card_browser_list_my_searches_successful_deleted,
+ CommonString.card_browser_list_my_searches_successful_deleted,
Snackbar.LENGTH_SHORT,
)
UserMessage.SAVED_SEARCH_NAME_DOES_NOT_EXIST ->
showSnackbar(
- R.string.something_wrong,
+ CommonString.something_wrong,
Snackbar.LENGTH_SHORT,
)
}
@@ -1227,7 +1231,7 @@ class CardBrowserFragment :
fun onChangeNoteType(result: ChangeNoteTypeResponse) =
when (result) {
ChangeNoteTypeResponse.NoSelection -> Timber.w("change note type: no selection")
- ChangeNoteTypeResponse.MixedSelection -> showSnackbar(R.string.different_note_types_selected)
+ ChangeNoteTypeResponse.MixedSelection -> showSnackbar(CommonString.different_note_types_selected)
is ChangeNoteTypeResponse.ChangeNoteType -> showDialogFragment(ChangeNoteTypeDialog.newInstance(result.noteIds))
}
@@ -1242,10 +1246,10 @@ class CardBrowserFragment :
val (title, subtitle) =
when (notification) {
is SortChangeNotification.NoOrdering ->
- getString(R.string.card_browser_order_no_sorting_title) to null
+ getString(CommonString.card_browser_order_no_sorting_title) to null
is SortChangeNotification.CollectionOrdering -> {
val subtitleRes = notification.type.humanReadableExplanation(descending = notification.reverse)
- getString(R.string.card_browser_order_snackbar_sort_by, notification.columnLabel) to
+ getString(CommonString.card_browser_order_snackbar_sort_by, notification.columnLabel) to
subtitleRes?.let(::getString)
}
}
@@ -1517,7 +1521,7 @@ class CardBrowserFragment :
if (availableColumns.isEmpty()) {
Timber.w("No available columns to replace ${selectedColumn.label}")
- showSnackbar(R.string.no_columns_available)
+ showSnackbar(CommonString.no_columns_available)
return@launch
}
@@ -1533,7 +1537,7 @@ class CardBrowserFragment :
return
}
startDeckSelection(
- title = getString(R.string.move_all_to_deck),
+ title = getString(CommonString.move_all_to_deck),
allowAll = false,
allowFiltered = false,
requestKey = REQUEST_DECK_SELECTION_CHANGE_DECK,
@@ -1558,7 +1562,7 @@ class CardBrowserFragment :
*/
fun openNoteEditorForCurrentlySelectedRow() {
if (!activityViewModel.openNoteEditorForCurrentlySelectedRow()) {
- showSnackbar(R.string.no_note_to_edit)
+ showSnackbar(CommonString.no_note_to_edit)
}
}
@@ -1580,20 +1584,20 @@ class CardBrowserFragment :
@VisibleForTesting
fun toggleMark() =
launchCatchingTask {
- withProgress { activityViewModel.toggleMark() }
+ activityViewModel.toggleMark()
}
- fun toggleSuspendCards() = launchCatchingTask { withProgress { activityViewModel.toggleSuspendCards().join() } }
+ fun toggleSuspendCards() = launchCatchingTask { activityViewModel.toggleSuspendCards().join() }
/** @see CardBrowserViewModel.toggleBury */
fun toggleBury() =
launchCatchingTask {
- val result = withProgress { activityViewModel.toggleBury() } ?: return@launchCatchingTask
+ val result = activityViewModel.toggleBury() ?: return@launchCatchingTask
// show a snackbar as there's currently no colored background for buried cards
val message =
when (result.wasBuried) {
true -> TR.studyingCardsBuried(result.count)
- false -> resources.getQuantityString(R.plurals.unbury_cards_feedback, result.count, result.count)
+ false -> resources.getQuantityString(CommonPlurals.unbury_cards_feedback, result.count, result.count)
}
showUndoSnackbar(message)
}
@@ -1632,8 +1636,8 @@ class CardBrowserFragment :
// No selected cards can be repositioned
showDialogFragment(
SimpleMessageDialog.newInstance(
- title = getString(R.string.vague_error),
- message = getString(R.string.reposition_card_not_new_error),
+ title = getString(CommonString.vague_error),
+ message = getString(CommonString.reposition_card_not_new_error),
reload = false,
),
)
@@ -1643,7 +1647,7 @@ class CardBrowserFragment :
val top = repositionCardsResult.queueTop
val bottom = repositionCardsResult.queueBottom
if (top == null || bottom == null) {
- showSnackbar(R.string.something_wrong)
+ showSnackbar(CommonString.something_wrong)
return@launchCatchingTask
}
val repositionDialog =
@@ -1662,10 +1666,8 @@ class CardBrowserFragment :
fun deleteSelectedNotes() =
launchCatchingTask {
- withProgress(R.string.deleting_selected_notes) {
- activityViewModel.deleteSelectedNotes()
- }.ifNotZero { noteCount ->
- val deletedMessage = resources.getQuantityString(R.plurals.card_browser_cards_deleted, noteCount, noteCount)
+ activityViewModel.deleteSelectedNotes().ifNotZero { noteCount ->
+ val deletedMessage = resources.getQuantityString(CommonPlurals.card_browser_cards_deleted, noteCount, noteCount)
showUndoSnackbar(deletedMessage)
}
}
@@ -1760,7 +1762,7 @@ class CardBrowserFragment :
fun updateFlagForSelectedRows(flag: Flag) =
launchCatchingTask {
- withProgress { activityViewModel.updateSelectedCardsFlag(flag) }
+ activityViewModel.updateSelectedCardsFlag(flag)
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
@@ -1809,7 +1811,7 @@ class CardBrowserFragment :
TagsDialogListenerAction.FILTER -> filterByTags(selectedTags, stateFilter)
TagsDialogListenerAction.EDIT_TAGS ->
launchCatchingTask {
- editSelectedCardsTags(selectedTags, indeterminateTags)
+ activityViewModel.editSelectedCardsTags(selectedTags, indeterminateTags)
}
else -> {}
}
@@ -1833,7 +1835,7 @@ class CardBrowserFragment :
@VisibleForTesting
internal fun moveSelectedCardsToDeck(did: DeckId): Job =
launchCatchingTask {
- val changed = withProgress { activityViewModel.moveSelectedCardsToDeck(did).await() }
+ val changed = activityViewModel.moveSelectedCardsToDeck(did).await()
showUndoSnackbar(TR.browsingCardsUpdated(changed.count))
}
@@ -1845,14 +1847,12 @@ class CardBrowserFragment :
shift: Boolean,
) = launchCatchingTask {
val count =
- withProgress {
- activityViewModel.repositionSelectedRows(
- position = position,
- step = step,
- shuffle = shuffle,
- shift = shift,
- )
- }
+ activityViewModel.repositionSelectedRows(
+ position = position,
+ step = step,
+ shuffle = shuffle,
+ shift = shift,
+ )
showSnackbar(
TR.browsingChangedNewPosition(count),
Snackbar.LENGTH_SHORT,
@@ -1861,7 +1861,7 @@ class CardBrowserFragment :
private fun showUndoSnackbar(message: CharSequence) {
showSnackbar(message) {
- setAction(R.string.undo) { launchCatchingTask { undoAndShowSnackbar() } }
+ setAction(CommonString.undo) { launchCatchingTask { undoAndShowSnackbar() } }
undoSnackbar = this
}
}
@@ -1892,30 +1892,6 @@ class CardBrowserFragment :
onAddNoteActivityResult.navigate(addNoteDestination)
}
- /**
- * Updates the tags of selected/checked notes and saves them to the disk
- * @param selectedTags list of checked tags
- * @param indeterminateTags a list of tags which can checked or unchecked, should be ignored if not expected
- * For more info on [selectedTags] and [indeterminateTags] see [com.ichi2.anki.dialogs.tags.TagsDialogListener.onSelectedTags]
- */
- private suspend fun editSelectedCardsTags(
- selectedTags: List,
- indeterminateTags: List,
- ) = withProgress {
- val selectedNoteIds = activityViewModel.queryAllSelectedNoteIds().distinct()
- undoableOp {
- val selectedNotes =
- selectedNoteIds
- .map { noteId -> getNote(noteId) }
- .onEach { note ->
- val previousTags: List = note.tags
- val updatedTags = getUpdatedTags(previousTags, selectedTags, indeterminateTags)
- note.setTagsFromStr(this@undoableOp, tags.join(updatedTags))
- }
- updateNotes(selectedNotes)
- }
- }
-
private fun filterByTags(
selectedTags: List,
cardState: CardStateFilter,
@@ -1946,39 +1922,39 @@ class CardBrowserFragment :
val shortcuts get() =
ShortcutGroup(
listOf(
- shortcut("Ctrl+Shift+A", R.string.edit_tags_dialog),
- shortcut("Ctrl+A", R.string.card_browser_select_all),
+ shortcut("Ctrl+Shift+A", CommonString.edit_tags_dialog),
+ shortcut("Ctrl+A", CommonString.card_browser_select_all),
shortcut("Ctrl+Shift+E", Translations::exportingExport),
- shortcut("Ctrl+E", R.string.menu_add_note),
- shortcut("E", R.string.cardeditor_title_edit_card),
+ shortcut("Ctrl+E", CommonString.menu_add_note),
+ shortcut("E", CommonString.cardeditor_title_edit_card),
shortcut("Ctrl+D") { sentenceCase.changeDeck },
shortcut("Ctrl+K") { sentenceCase.toggleMark },
shortcut("Ctrl+Alt+R", Translations::browsingReschedule),
- shortcut("DEL", R.string.delete_card_title),
- shortcut("Ctrl+Alt+N", R.string.reset_card_dialog_title),
+ shortcut("DEL", CommonString.delete_card_title),
+ shortcut("Ctrl+Alt+N", CommonString.reset_card_dialog_title),
shortcut("Ctrl+Alt+T") { sentenceCase.toggleCardsNotes },
- shortcut("Ctrl+T", R.string.card_browser_search_by_tag),
+ shortcut("Ctrl+T", CommonString.card_browser_search_by_tag),
shortcut("Ctrl+Shift+S", Translations::actionsReposition),
- shortcut("Ctrl+Alt+S", R.string.card_browser_list_my_searches),
- shortcut("Ctrl+S", R.string.card_browser_list_my_searches_save),
- shortcut("Alt+S", R.string.card_browser_show_suspended),
+ shortcut("Ctrl+Alt+S", CommonString.card_browser_list_my_searches),
+ shortcut("Ctrl+S", CommonString.card_browser_list_my_searches_save),
+ shortcut("Alt+S", CommonString.card_browser_show_suspended),
shortcut("Ctrl+Shift+G") { sentenceCase.gradeNow },
shortcut("Ctrl+Shift+J") { sentenceCase.toggleBury },
shortcut("Ctrl+J") { sentenceCase.toggleSuspend },
shortcut("Ctrl+Shift+I") { sentenceCase.cardInfo },
- shortcut("Ctrl+O", R.string.show_order_dialog),
- shortcut("Ctrl+Shift+P", R.string.card_editor_preview_card),
- shortcut("Ctrl+M", R.string.card_browser_show_marked),
- shortcut("Esc", R.string.card_browser_select_none),
- shortcut("Ctrl+1", R.string.gesture_flag_red),
- shortcut("Ctrl+2", R.string.gesture_flag_orange),
- shortcut("Ctrl+3", R.string.gesture_flag_green),
- shortcut("Ctrl+4", R.string.gesture_flag_blue),
- shortcut("Ctrl+5", R.string.gesture_flag_pink),
- shortcut("Ctrl+6", R.string.gesture_flag_turquoise),
- shortcut("Ctrl+7", R.string.gesture_flag_purple),
+ shortcut("Ctrl+O", CommonString.show_order_dialog),
+ shortcut("Ctrl+Shift+P", CommonString.card_editor_preview_card),
+ shortcut("Ctrl+M", CommonString.card_browser_show_marked),
+ shortcut("Esc", CommonString.card_browser_select_none),
+ shortcut("Ctrl+1", CommonString.gesture_flag_red),
+ shortcut("Ctrl+2", CommonString.gesture_flag_orange),
+ shortcut("Ctrl+3", CommonString.gesture_flag_green),
+ shortcut("Ctrl+4", CommonString.gesture_flag_blue),
+ shortcut("Ctrl+5", CommonString.gesture_flag_pink),
+ shortcut("Ctrl+6", CommonString.gesture_flag_turquoise),
+ shortcut("Ctrl+7", CommonString.gesture_flag_purple),
),
- R.string.card_browser_context_menu,
+ CommonString.card_browser_context_menu,
)
private enum class TagsDialogListenerAction {
@@ -2054,3 +2030,10 @@ fun buildUserSpannable(
return spannable
}
+
+@get:StringRes
+private val CardBrowserProgress.messageRes: Int
+ get() =
+ when (this) {
+ CardBrowserProgress.DELETING_NOTES -> CommonString.deleting_selected_notes
+ }
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt
index c7233029667e..e76394367369 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/CardBrowserViewModel.kt
@@ -63,9 +63,12 @@ import com.ichi2.anki.model.SortType
import com.ichi2.anki.observability.ChangeManager
import com.ichi2.anki.observability.undoableOp
import com.ichi2.anki.preferences.SharedPreferencesProvider
+import com.ichi2.anki.progress.HasProgress
+import com.ichi2.anki.progress.ProgressManager
import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.utils.ext.getCardOrNull
import com.ichi2.anki.utils.ext.setUserFlagForCards
+import com.ichi2.utils.TagsUtil.getUpdatedTags
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
@@ -132,7 +135,10 @@ class CardBrowserViewModel(
BrowserOptionsRepository(preferences.sharedPrefs()),
private val manualInit: Boolean = false,
) : ViewModel(),
- SharedPreferencesProvider by preferences {
+ SharedPreferencesProvider by preferences,
+ HasProgress {
+ override val progressManager = ProgressManager()
+
// TODO: abstract so we can use a `Context` and `pref_display_filenames_in_browser_key`
val showMediaFilenames = sharedPrefs().getBoolean("card_browser_show_media_filenames", false)
@@ -775,25 +781,27 @@ class CardBrowserViewModel(
* otherwise, they will be unmarked
*/
suspend fun toggleMark() {
- val cardIds = queryAllSelectedCardIds()
- if (cardIds.isEmpty()) {
- Timber.i("Not marking cards - nothing selected")
- return
- }
- undoableOp(this) {
- val noteIds = notesOfCards(cardIds)
- // if all notes are marked, remove the mark
- // if no notes are marked, add the mark
- // if there is a mix, enable the mark on all
- val wantMark = !noteIds.all { getNote(it).hasTag(this@undoableOp, "marked") }
- Timber.i("setting mark = %b for %d notes", wantMark, noteIds.size)
- if (wantMark) {
- tags.bulkAdd(noteIds, "marked")
- } else {
- tags.bulkRemove(noteIds, "marked")
+ progressManager.withProgress {
+ val cardIds = queryAllSelectedCardIds()
+ if (cardIds.isEmpty()) {
+ Timber.i("Not marking cards - nothing selected")
+ return@withProgress
+ }
+ undoableOp(this@CardBrowserViewModel) {
+ val noteIds = notesOfCards(cardIds)
+ // if all notes are marked, remove the mark
+ // if no notes are marked, add the mark
+ // if there is a mix, enable the mark on all
+ val wantMark = !noteIds.all { getNote(it).hasTag(this@undoableOp, "marked") }
+ Timber.i("setting mark = %b for %d notes", wantMark, noteIds.size)
+ if (wantMark) {
+ tags.bulkAdd(noteIds, "marked")
+ } else {
+ tags.bulkRemove(noteIds, "marked")
+ }
}
+ flowOfCardStateChanged.emit(Unit)
}
- flowOfCardStateChanged.emit(Unit)
}
/**
@@ -801,19 +809,20 @@ class CardBrowserViewModel(
* @return the number of deleted notes
*/
@NeedsTest("Deleting the focused row is properly handled;#18639")
- suspend fun deleteSelectedNotes(): Int {
- val cardIds = queryAllSelectedCardIds()
- // reset the pane row if that row is about to be deleted
- if (paneRow?.cardOrNoteId in cardIds) {
- paneRow = null
- }
- return undoableOp(this@CardBrowserViewModel) { removeNotes(cardIds = cardIds) }
- .count
- .also {
- endMultiSelectMode(SingleSelectCause.Other)
- refreshSearch()
+ suspend fun deleteSelectedNotes(): Int =
+ progressManager.withProgress(message = CardBrowserProgress.DELETING_NOTES) {
+ val cardIds = queryAllSelectedCardIds()
+ // reset the pane row if that row is about to be deleted
+ if (paneRow?.cardOrNoteId in cardIds) {
+ paneRow = null
}
- }
+ undoableOp(this@CardBrowserViewModel) { removeNotes(cardIds = cardIds) }
+ .count
+ .also {
+ endMultiSelectMode(SingleSelectCause.Other)
+ refreshSearch()
+ }
+ }
fun setCardsOrNotes(newValue: CardsOrNotes) = viewModelScope.launch { browserOptionsRepository.setCardsOrNotes(newValue) }
@@ -1054,18 +1063,20 @@ class CardBrowserViewModel(
if (!hasSelectedAnyRows()) {
return@launch
}
- Timber.d("toggling selected cards suspend status")
- val cardIds = queryAllSelectedCardIds()
-
- undoableOp {
- val wantUnsuspend = cardIds.all { getCard(it).queue == QueueType.Suspended }
- if (wantUnsuspend) {
- sched.unsuspendCards(cardIds)
- } else {
- sched.suspendCards(cardIds).changes
+ progressManager.withProgress {
+ Timber.d("toggling selected cards suspend status")
+ val cardIds = queryAllSelectedCardIds()
+
+ undoableOp {
+ val wantUnsuspend = cardIds.all { getCard(it).queue == QueueType.Suspended }
+ if (wantUnsuspend) {
+ sched.unsuspendCards(cardIds)
+ } else {
+ sched.suspendCards(cardIds).changes
+ }
}
+ Timber.d("finished 'toggleSuspendCards'")
}
- Timber.d("finished 'toggleSuspendCards'")
}
/**
@@ -1087,25 +1098,27 @@ class CardBrowserViewModel(
// https://github.com/ankitects/anki/blob/074becc0cee1e9ae59be701ad6c26787f74b4594/qt/aqt/browser/browser.py#L896-L902
fun Card.isBuried(): Boolean = queue == ManuallyBuried || queue == SiblingBuried
- val cardIds = queryAllSelectedCardIds()
+ return progressManager.withProgress {
+ val cardIds = queryAllSelectedCardIds()
- // this variable exists as `undoableOp` needs an OpChanges as return value
- var wasBuried: Boolean? = null
- undoableOp {
- // this differs from Anki Desktop which uses the first selected card to determine the
- // 'checked' status
- val wantUnbury = cardIds.all { getCard(it).isBuried() }
-
- wasBuried = !wantUnbury
- if (wantUnbury) {
- Timber.i("unburying %d cards", cardIds.size)
- sched.unburyCards(cardIds)
- } else {
- Timber.i("burying %d cards", cardIds.size)
- sched.buryCards(cardIds).changes
+ // this variable exists as `undoableOp` needs an OpChanges as return value
+ var wasBuried: Boolean? = null
+ undoableOp {
+ // this differs from Anki Desktop which uses the first selected card to determine the
+ // 'checked' status
+ val wantUnbury = cardIds.all { getCard(it).isBuried() }
+
+ wasBuried = !wantUnbury
+ if (wantUnbury) {
+ Timber.i("unburying %d cards", cardIds.size)
+ sched.unburyCards(cardIds)
+ } else {
+ Timber.i("burying %d cards", cardIds.size)
+ sched.buryCards(cardIds).changes
+ }
}
+ BuryResult(wasBuried = wasBuried!!, count = cardIds.size)
}
- return BuryResult(wasBuried = wasBuried!!, count = cardIds.size)
}
fun querySelectionExportData(): Pair>? {
@@ -1181,14 +1194,15 @@ class CardBrowserViewModel(
step: Int,
shuffle: Boolean,
shift: Boolean,
- ): Int {
- val ids = queryAllSelectedCardIds()
-
- Timber.d("repositioning %d cards to %d", ids.size, position)
- return undoableOp {
- sched.sortCards(cids = ids, position, step = step, shuffle = shuffle, shift = shift)
- }.count
- }
+ ): Int =
+ progressManager.withProgress {
+ val ids = queryAllSelectedCardIds()
+
+ Timber.d("repositioning %d cards to %d", ids.size, position)
+ undoableOp {
+ sched.sortCards(cids = ids, position, step = step, shuffle = shuffle, shift = shift)
+ }.count
+ }
/** Returns the number of rows of the current result set */
val rowCount: Int
@@ -1267,6 +1281,30 @@ class CardBrowserViewModel(
setFilterQuery(searchTerms)
}
+ /**
+ * Updates the tags of selected/checked notes and saves them to the disk
+ * @param selectedTags list of checked tags
+ * @param indeterminateTags a list of tags which can checked or unchecked, should be ignored if not expected
+ * For more info on [selectedTags] and [indeterminateTags] see [com.ichi2.anki.dialogs.tags.TagsDialogListener.onSelectedTags]
+ */
+ suspend fun editSelectedCardsTags(
+ selectedTags: List,
+ indeterminateTags: List,
+ ) = progressManager.withProgress {
+ val selectedNoteIds = queryAllSelectedNoteIds().distinct()
+ undoableOp {
+ val selectedNotes =
+ selectedNoteIds
+ .map { noteId -> getNote(noteId) }
+ .onEach { note ->
+ val previousTags: List = note.tags
+ val updatedTags = getUpdatedTags(previousTags, selectedTags, indeterminateTags)
+ note.setTagsFromStr(this@undoableOp, tags.join(updatedTags))
+ }
+ updateNotes(selectedNotes)
+ }
+ }
+
suspend fun filterByTags(
selectedTags: List,
cardState: CardStateFilter,
@@ -1332,18 +1370,21 @@ class CardBrowserViewModel(
fun moveSelectedCardsToDeck(deckId: DeckId): Deferred =
viewModelScope.async {
- val selectedCardIds = queryAllSelectedCardIds()
- return@async undoableOp {
- setDeck(selectedCardIds, deckId)
+ progressManager.withProgress {
+ val selectedCardIds = queryAllSelectedCardIds()
+ undoableOp {
+ setDeck(selectedCardIds, deckId)
+ }
}
}
- suspend fun updateSelectedCardsFlag(flag: Flag): List {
- val idsToChange = queryAllSelectedCardIds()
- undoableOp(this) { setUserFlagForCards(cids = idsToChange, flag = flag) }
- flowOfCardStateChanged.emit(Unit)
- return idsToChange
- }
+ suspend fun updateSelectedCardsFlag(flag: Flag): List =
+ progressManager.withProgress {
+ val idsToChange = queryAllSelectedCardIds()
+ undoableOp(this@CardBrowserViewModel) { setUserFlagForCards(cids = idsToChange, flag = flag) }
+ flowOfCardStateChanged.emit(Unit)
+ idsToChange
+ }
/**
* Turn off [Multi-Select Mode][isInMultiSelectMode] and return to normal state
@@ -1483,20 +1524,22 @@ class CardBrowserViewModel(
*/
fun findAndReplace(result: FindReplaceResult) =
viewModelScope.async {
- // TODO pass the selection as the user saw it in the dialog to avoid running "find
- // and replace" on a different selection
- val noteIds = if (result.onlyOnSelectedNotes) queryAllSelectedNoteIds() else emptyList()
-
- if (result.field == TAGS_AS_FIELD) {
- undoableOp {
- tags.findAndReplace(noteIds, result.search, result.replacement, result.regex, result.matchCase)
- }.count
- } else {
- val field =
- if (result.field == ALL_FIELDS_AS_FIELD) null else result.field
- undoableOp {
- findAndReplace(noteIds, result.search, result.replacement, result.regex, field, result.matchCase)
- }.count
+ progressManager.withProgress {
+ // TODO pass the selection as the user saw it in the dialog to avoid running "find
+ // and replace" on a different selection
+ val noteIds = if (result.onlyOnSelectedNotes) queryAllSelectedNoteIds() else emptyList()
+
+ if (result.field == TAGS_AS_FIELD) {
+ undoableOp {
+ tags.findAndReplace(noteIds, result.search, result.replacement, result.regex, result.matchCase)
+ }.count
+ } else {
+ val field =
+ if (result.field == ALL_FIELDS_AS_FIELD) null else result.field
+ undoableOp {
+ findAndReplace(noteIds, result.search, result.replacement, result.regex, field, result.matchCase)
+ }.count
+ }
}
}
@@ -1696,6 +1739,11 @@ enum class SaveSearchResult {
SUCCESS,
}
+/** The operation [CardBrowserViewModel] is running, shown as a progress message by the UI. */
+enum class CardBrowserProgress {
+ DELETING_NOTES,
+}
+
/**
* Temporary file containing cards or note IDs to be passed in a Bundle.
*
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/ColumnSelectionDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/ColumnSelectionDialogFragment.kt
index c22ffdcdfebf..b029960412e2 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/ColumnSelectionDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/ColumnSelectionDialogFragment.kt
@@ -14,6 +14,7 @@ import androidx.core.os.BundleCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.ItemColumnSelectionBinding
import com.ichi2.anki.utils.ext.requireParcelable
@@ -69,7 +70,7 @@ class ColumnSelectionDialogFragment : DialogFragment() {
val column = getItem(position)
binding.columnTitle.text =
- column?.label ?: getString(R.string.no_columns_available)
+ column?.label ?: getString(CommonString.no_columns_available)
binding.columnExample.text =
if (column?.sampleValue.isNullOrBlank()) "-" else column.sampleValue
@@ -95,7 +96,7 @@ class ColumnSelectionDialogFragment : DialogFragment() {
listView.setOnItemClickListener { _, _, position, _ ->
val selected = adapter.getItem(position)
- if (selected == null || selected.label == getString(R.string.no_columns_available)) {
+ if (selected == null || selected.label == getString(CommonString.no_columns_available)) {
Timber.d("Ignoring click on 'No Columns Available'")
return@setOnItemClickListener
}
@@ -111,7 +112,7 @@ class ColumnSelectionDialogFragment : DialogFragment() {
return AlertDialog
.Builder(requireActivity())
- .setTitle(getString(R.string.manage_browser_column))
+ .setTitle(getString(CommonString.manage_browser_column))
.setView(container)
.setNegativeButton(android.R.string.cancel) { _, _ -> dismissAllowingStateLoss() }
.create()
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 f8980c9635e7..0de70c8827bf 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/FindAndReplaceDialogFragment.kt
@@ -18,6 +18,7 @@ import androidx.lifecycle.lifecycleScope
import com.ichi2.anki.CardBrowser
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.browser.FindAndReplaceDialogFragment.Companion.ARG_FIELD
@@ -71,9 +72,9 @@ class FindAndReplaceDialogFragment : AnalyticsDialogFragment() {
.show {
title(text = title)
customView(binding.root)
- neutralButton(R.string.help) { openUrl(R.string.link_manual_browser_find_replace) }
- negativeButton(R.string.dialog_cancel) { removeIdsFile() }
- positiveButton(R.string.dialog_positive_replace) { startFindReplace() }
+ neutralButton(CommonString.help) { openUrl(R.string.link_manual_browser_find_replace) }
+ negativeButton(CommonString.dialog_cancel) { removeIdsFile() }
+ positiveButton(CommonString.dialog_positive_replace) { startFindReplace() }
}.also { dialog ->
dialog.positiveButton.isEnabled = false
}
@@ -102,7 +103,7 @@ class FindAndReplaceDialogFragment : AnalyticsDialogFragment() {
val fetchNoteIdsResult = runCatching { idsFile.getIds() }
val noteIds = fetchNoteIdsResult.getOrNull()
if (fetchNoteIdsResult.isFailure || noteIds == null) {
- requireActivity().showSnackbar(R.string.something_wrong)
+ requireActivity().showSnackbar(CommonString.something_wrong)
dismiss()
return@launch
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/RepositionCardFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/RepositionCardFragment.kt
index 7d56308f8c55..428119f93821 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/RepositionCardFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/RepositionCardFragment.kt
@@ -14,7 +14,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult
import com.ichi2.anki.CardBrowser
import com.ichi2.anki.CollectionManager.TR
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.databinding.FragmentRepositionCardBinding
import com.ichi2.anki.ui.internationalization.sentenceCase
import com.ichi2.utils.create
@@ -67,7 +67,7 @@ class RepositionCardFragment : DialogFragment() {
AlertDialog.Builder(requireContext()).create {
title(text = title)
customView(binding.root)
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
positiveButton(text = TR.actionsReposition()) {
val position =
binding.startInputEditText.textAsIntOrNull() ?: return@positiveButton
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/AdvancedSearchFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/AdvancedSearchFragment.kt
index 85feda7b59d3..4f60339ff88c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/AdvancedSearchFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/AdvancedSearchFragment.kt
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.browser.search.AdvancedSearchFieldsTab.FieldSearch
import com.ichi2.anki.browser.search.AdvancedSearchFragment.OptionData
@@ -218,7 +219,7 @@ class AdvancedSearchFragment : Fragment(R.layout.fragment_advanced_search) {
val fieldSearch =
AdvancedSearchFieldsTab.options[resultType] ?: run {
Timber.w("resultType '%s' was unhandled", resultType)
- showSnackbar(R.string.something_wrong)
+ showSnackbar(CommonString.something_wrong)
return@registerFieldSelectionHandler
}
launchCatchingTask {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SortOrderBottomSheetFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SortOrderBottomSheetFragment.kt
index 7d892e75309a..25865d0406e8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SortOrderBottomSheetFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SortOrderBottomSheetFragment.kt
@@ -19,6 +19,7 @@ import androidx.recyclerview.widget.RecyclerView
import anki.search.BrowserColumns.Sorting
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.browser.BrowserColumnKey
import com.ichi2.anki.browser.CardBrowserColumn
@@ -67,7 +68,7 @@ class SortOrderBottomSheetFragment(
with(binding.title) {
isVisible = true
- text = getString(R.string.card_browser_change_display_order_title)
+ text = getString(CommonString.card_browser_change_display_order_title)
}
with(this.behavior) {
@@ -144,14 +145,14 @@ class SortOrderBottomSheetFragment(
// do for all cards in the collection. 'Sort Field' can be used instead
CardBrowserColumn.QUESTION.ankiColumnKey,
CardBrowserColumn.ANSWER.ankiColumnKey,
- -> R.string.card_browser_order_subtitle_use_sort_field
+ -> CommonString.card_browser_order_subtitle_use_sort_field
// FSRS data is stored in JSON in the card table, it's not yet feasible to
// sort on this data
CardBrowserColumn.FSRS_DIFFICULTY.ankiColumnKey,
CardBrowserColumn.FSRS_STABILITY.ankiColumnKey,
CardBrowserColumn.FSRS_RETRIEVABILITY.ankiColumnKey,
- -> R.string.card_browser_order_subtitle_cards_mode_only
+ -> CommonString.card_browser_order_subtitle_cards_mode_only
else -> null
}
@@ -179,7 +180,7 @@ class SortOrderBottomSheetFragment(
@CheckResult
fun getLabel(context: Context): String =
when (this) {
- NoOrdering -> context.getString(R.string.card_browser_order_no_sorting_title)
+ NoOrdering -> context.getString(CommonString.card_browser_order_no_sorting_title)
is AnkiColumn -> this.label
}
@@ -191,7 +192,7 @@ class SortOrderBottomSheetFragment(
val subtitleRes: Int? =
when (this) {
// No Ordering's subtitle is 'Faster'
- is NoOrdering -> R.string.card_browser_order_no_sorting_subtitle
+ is NoOrdering -> CommonString.card_browser_order_no_sorting_subtitle
// the selected column has an explanation of the sort: "Low to high"
is AnkiColumn if sort != null && sort.key == key ->
type.humanReadableExplanation(descending = sort.reverse)
@@ -260,15 +261,15 @@ class SortOrderBottomSheetFragment(
return buildList {
add(NoOrdering)
if (active.isNotEmpty()) {
- add(SectionHeader(R.string.user_active_columns))
+ add(SectionHeader(CommonString.user_active_columns))
addAll(active)
}
if (available.isNotEmpty()) {
- add(SectionHeader(R.string.user_potential_columns))
+ add(SectionHeader(CommonString.user_potential_columns))
addAll(available)
}
if (unavailable.isNotEmpty()) {
- add(SectionHeader(R.string.card_browser_order_section_unavailable))
+ add(SectionHeader(CommonString.card_browser_order_section_unavailable))
addAll(unavailable)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/StandardSearchFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/StandardSearchFragment.kt
index 6c7ac4ddf977..bbfbf70c7e57 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/StandardSearchFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/StandardSearchFragment.kt
@@ -17,6 +17,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.google.android.material.color.MaterialColors
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.Flag
import com.ichi2.anki.R
import com.ichi2.anki.browser.SearchHistory.SearchHistoryEntry
@@ -97,7 +98,7 @@ class StandardSearchFragment :
private fun setupChips() {
binding.decksChip.setOnClickListener {
// TODO: see onDeckSelected
- startDeckSelection(title = getString(R.string.search_deck), asChild = true, skipEmptyDefault = true)
+ startDeckSelection(title = getString(CommonString.search_deck), asChild = true, skipEmptyDefault = true)
}
binding.tagsChip.setOnClickListener {
@@ -244,7 +245,7 @@ class StandardSearchFragment :
}
viewModel.isHistoryExpandedFlow.launchCollectionInLifecycleScope {
- binding.toggleSearchHistory.setText(if (it) R.string.card_browser_see_less else R.string.card_browser_see_more)
+ binding.toggleSearchHistory.setText(if (it) CommonString.card_browser_see_less else CommonString.card_browser_see_more)
}
viewModel.searchHistoryAvailableFlow.launchCollectionInLifecycleScope {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/ui/SortPill.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/ui/SortPill.kt
index 2b6c9fee90d7..11011ac8e658 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/ui/SortPill.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/ui/SortPill.kt
@@ -20,6 +20,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.color.MaterialColors
import com.google.android.material.shape.AbsoluteCornerSize
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.browser.ColumnType
import com.ichi2.utils.dp
@@ -215,24 +216,24 @@ class SortPill
setIconAndLabel(
standardButton,
R.drawable.ic_sort_pill_arrow_down_label,
- context.getString(R.string.sort_pill_label_text_ascending),
+ context.getString(CommonString.sort_pill_label_text_ascending),
)
setIconAndLabel(
reverseButton,
R.drawable.ic_sort_pill_arrow_down_label,
- context.getString(R.string.sort_pill_label_text_descending),
+ context.getString(CommonString.sort_pill_label_text_descending),
)
}
ColumnType.NUMERIC -> {
setIconAndLabel(
standardButton,
R.drawable.ic_sort_pill_arrow_down_label,
- context.getString(R.string.sort_pill_label_numeric_ascending),
+ context.getString(CommonString.sort_pill_label_numeric_ascending),
)
setIconAndLabel(
reverseButton,
R.drawable.ic_sort_pill_arrow_down_label,
- context.getString(R.string.sort_pill_label_numeric_descending),
+ context.getString(CommonString.sort_pill_label_numeric_descending),
)
}
ColumnType.DATE, ColumnType.UNSPECIFIED -> {
@@ -244,8 +245,8 @@ class SortPill
private fun updateA11yDescriptions() {
// set contentDescription to ", ascending" / ", descending"
- val ascending = context.getString(R.string.sort_pill_a11y_ascending)
- val descending = context.getString(R.string.sort_pill_a11y_descending)
+ val ascending = context.getString(CommonString.sort_pill_a11y_ascending)
+ val descending = context.getString(CommonString.sort_pill_a11y_descending)
val prefix = columnContentDescription
standardButton.contentDescription = if (prefix != null) "$prefix, $ascending" else ascending
reverseButton.contentDescription = if (prefix != null) "$prefix, $descending" else descending
@@ -253,7 +254,7 @@ class SortPill
private fun updateA11yState() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return
- val selected = context.getString(R.string.sort_pill_a11y_selected)
+ val selected = context.getString(CommonString.sort_pill_a11y_selected)
standardButton.stateDescription = if (activeDirection == SortDirection.Ascending) selected else null
reverseButton.stateDescription = if (activeDirection == SortDirection.Descending) selected else null
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/Gesture.kt b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/Gesture.kt
index ed34d275547a..4049f8a8e039 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/Gesture.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/Gesture.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.cardviewer
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
/**
* https://www.fileformat.info/info/unicode/char/235d/index.htm (similar to a finger)
@@ -29,24 +29,24 @@ enum class Gesture(
@get:JvmName("getResourceId") val resourceId: Int,
private val displayPrefix: String = GESTURE_PREFIX,
) {
- SWIPE_UP(R.string.gestures_swipe_up),
- SWIPE_DOWN(R.string.gestures_swipe_down),
- SWIPE_LEFT(R.string.gestures_swipe_left),
- SWIPE_RIGHT(R.string.gestures_swipe_right),
- DOUBLE_TAP(R.string.gestures_double_tap),
- TAP_TOP_LEFT(R.string.gestures_corner_tap_top_left),
- TAP_TOP(R.string.gestures_tap_top),
- TAP_TOP_RIGHT(R.string.gestures_corner_tap_top_right),
- TAP_LEFT(R.string.gestures_tap_left),
- TAP_CENTER(R.string.gestures_corner_tap_middle_center),
- TAP_RIGHT(R.string.gestures_tap_right),
- TAP_BOTTOM_LEFT(R.string.gestures_corner_tap_bottom_left),
- TAP_BOTTOM(R.string.gestures_tap_bottom),
- TAP_BOTTOM_RIGHT(R.string.gestures_corner_tap_bottom_right),
- TWO_FINGER_TAP(R.string.gestures_two_finger_tap),
- THREE_FINGER_TAP(R.string.gestures_three_finger_tap),
- FOUR_FINGER_TAP(R.string.gestures_four_finger_tap),
- SHAKE(R.string.gestures_shake, SHAKE_GESTURE_PREFIX),
+ SWIPE_UP(CommonString.gestures_swipe_up),
+ SWIPE_DOWN(CommonString.gestures_swipe_down),
+ SWIPE_LEFT(CommonString.gestures_swipe_left),
+ SWIPE_RIGHT(CommonString.gestures_swipe_right),
+ DOUBLE_TAP(CommonString.gestures_double_tap),
+ TAP_TOP_LEFT(CommonString.gestures_corner_tap_top_left),
+ TAP_TOP(CommonString.gestures_tap_top),
+ TAP_TOP_RIGHT(CommonString.gestures_corner_tap_top_right),
+ TAP_LEFT(CommonString.gestures_tap_left),
+ TAP_CENTER(CommonString.gestures_corner_tap_middle_center),
+ TAP_RIGHT(CommonString.gestures_tap_right),
+ TAP_BOTTOM_LEFT(CommonString.gestures_corner_tap_bottom_left),
+ TAP_BOTTOM(CommonString.gestures_tap_bottom),
+ TAP_BOTTOM_RIGHT(CommonString.gestures_corner_tap_bottom_right),
+ TWO_FINGER_TAP(CommonString.gestures_two_finger_tap),
+ THREE_FINGER_TAP(CommonString.gestures_three_finger_tap),
+ FOUR_FINGER_TAP(CommonString.gestures_four_finger_tap),
+ SHAKE(CommonString.gestures_shake, SHAKE_GESTURE_PREFIX),
;
fun toDisplayString(context: Context): String =
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/OnRenderProcessGoneDelegate.kt b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/OnRenderProcessGoneDelegate.kt
index 5a1fb47a25d5..05fd97a54ad8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/OnRenderProcessGoneDelegate.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/OnRenderProcessGoneDelegate.kt
@@ -9,7 +9,7 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.Lifecycle
import com.ichi2.anki.AbstractFlashcardViewer
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.anki.libanki.CardId
import com.ichi2.utils.cancelable
@@ -99,7 +99,7 @@ open class OnRenderProcessGoneDelegate(
Timber.d("Not showing toast - screen isn't visible")
return
}
- val errorMessage = target.resources.getString(R.string.webview_crash_fatal, getErrorCause(detail))
+ val errorMessage = target.resources.getString(CommonString.webview_crash_fatal, getErrorCause(detail))
showThemedToast(target, errorMessage, false)
}
@@ -109,14 +109,14 @@ open class OnRenderProcessGoneDelegate(
Timber.d("Not showing toast - screen isn't visible")
return
}
- val nonFatalError = target.resources.getString(R.string.webview_crash_nonfatal, getErrorCause(detail))
+ val nonFatalError = target.resources.getString(CommonString.webview_crash_nonfatal, getErrorCause(detail))
showThemedToast(target, nonFatalError, false)
}
@RequiresApi(Build.VERSION_CODES.O)
protected fun getErrorCause(detail: RenderProcessGoneDetail): String {
// It's not necessarily an OOM crash, false implies a general code which is for "system terminated".
- val errorCauseId = if (detail.didCrash()) R.string.webview_crash_unknown else R.string.webview_crash_oom
+ val errorCauseId = if (detail.didCrash()) CommonString.webview_crash_unknown else CommonString.webview_crash_oom
return target.resources.getString(errorCauseId)
}
@@ -130,15 +130,15 @@ open class OnRenderProcessGoneDelegate(
val errorDetails =
if (detail.didCrash()) {
res.getString(
- R.string.webview_crash_unknwon_detailed,
+ CommonString.webview_crash_unknwon_detailed,
)
} else {
- res.getString(R.string.webview_crash_oom_details)
+ res.getString(CommonString.webview_crash_oom_details)
}
AlertDialog.Builder(target).show {
- title(R.string.webview_crash_loop_dialog_title)
- message(text = res.getString(R.string.webview_crash_loop_dialog_content, cardInformation, errorDetails))
- positiveButton(R.string.dialog_ok) {
+ title(CommonString.webview_crash_loop_dialog_title)
+ message(text = res.getString(CommonString.webview_crash_loop_dialog_content, cardInformation, errorDetails))
+ positiveButton(CommonString.dialog_ok) {
onCloseRenderLoopDialog()
}
cancelable(false)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypeAnswer.kt b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypeAnswer.kt
index 3d109ebdfbdd..bd7881aff023 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypeAnswer.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypeAnswer.kt
@@ -6,7 +6,7 @@ import android.content.SharedPreferences
import android.content.res.Resources
import androidx.annotation.VisibleForTesting
import com.ichi2.anki.CollectionManager
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.libanki.Card
import com.ichi2.anki.libanki.Collection
import com.ichi2.anki.servicelayer.LanguageHint
@@ -59,7 +59,7 @@ class TypeAnswer(
* Optional warning for when a typed answer can't be displayed
*
* * empty card
- * * unknown field specified [R.string.unknown_type_field_warning]
+ * * unknown field specified [CommonString.unknown_type_field_warning]
* */
var warning: String? = null
private set
@@ -115,7 +115,7 @@ class TypeAnswer(
if (clozeIdx != 0) {
CollectionManager.TR.cardTemplateRenderingEmptyFront()
} else {
- res.getString(R.string.unknown_type_field_warning, fldTag)
+ res.getString(CommonString.unknown_type_field_warning, fldTag)
}
}
"" -> {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/BackgroundImage.kt b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/BackgroundImage.kt
index 362aff8d89b8..eabeee760734 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/BackgroundImage.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/BackgroundImage.kt
@@ -7,8 +7,8 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.provider.MediaStore
import androidx.annotation.CheckResult
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
-import com.ichi2.anki.R
import com.ichi2.anki.common.storage.CollectionHelper
import com.ichi2.anki.preferences.AppearanceSettingsFragment
import com.ichi2.anki.settings.Prefs
@@ -60,14 +60,14 @@ object BackgroundImage {
/** The bitmap is too large to draw safely. */
data object TooLarge : Failure {
- override fun message(context: Context): String = context.getString(R.string.background_image_too_large)
+ override fun message(context: Context): String = context.getString(CommonString.background_image_too_large)
}
/** Decoding failed for some other reason. */
data class Failed(
val cause: String?,
) : Failure {
- override fun message(context: Context): String = context.getString(R.string.failed_to_apply_background_image, cause)
+ override fun message(context: Context): String = context.getString(CommonString.failed_to_apply_background_image, cause)
}
}
@@ -169,7 +169,7 @@ object BackgroundImage {
(target.requireContext().contentResolver.openInputStreamSafe(selectedImage) as FileInputStream).channel.use { sourceChannel ->
FileOutputStream(destFile).channel.use { destChannel ->
destChannel.transferFrom(sourceChannel, 0, sourceChannel.size())
- target.showSnackbar(R.string.background_image_applied)
+ target.showSnackbar(CommonString.background_image_applied)
}
}
Prefs.isBackgroundEnabled = true
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt
index d249a9900246..7537bfa40678 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt
@@ -123,6 +123,7 @@ class DeckPickerViewModel :
* @see DeckDeletionResult
*/
val deckDeletedNotification = MutableSharedFlow(extraBufferCapacity = 1)
+ val flowOfDeleteDeckConfirmation = MutableSharedFlow(extraBufferCapacity = 1)
val emptyCardsNotification = MutableSharedFlow(extraBufferCapacity = 1)
val flowOfDestination = MutableSharedFlow(extraBufferCapacity = 1)
val flowOfNavigate = MutableSharedFlow(extraBufferCapacity = 1)
@@ -188,6 +189,33 @@ class DeckPickerViewModel :
// TODO: Use a sensible default rather than null
val flowOfOptionsMenuState = MutableStateFlow(null)
+ data class DeleteDeckConfirmationRequest(
+ val deckId: DeckId,
+ val deckName: String,
+ val totalCards: Int,
+ val isFilteredDeck: Boolean,
+ )
+
+ /** Prepares a confirmation dialog for deleting the focused deck. */
+ fun requestDeleteDeckConfirmation() =
+ launchCatchingIO {
+ val deckId =
+ focusedDeck ?: run {
+ Timber.w("no focused deck")
+ return@launchCatchingIO
+ }
+ val request =
+ withCol {
+ DeleteDeckConfirmationRequest(
+ deckId = deckId,
+ deckName = decks.name(deckId),
+ totalCards = decks.cardCount(deckId, includeSubdecks = true),
+ isFilteredDeck = decks.isFiltered(deckId),
+ )
+ }
+ flowOfDeleteDeckConfirmation.emit(request)
+ }
+
/**
* Deletes the provided deck, child decks. and all cards inside.
*
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BackupPromptDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BackupPromptDialog.kt
index 0f4936121018..209fd02b2bd6 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BackupPromptDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BackupPromptDialog.kt
@@ -8,6 +8,7 @@ import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.common.crashreporting.CrashReportService
@@ -117,21 +118,21 @@ class BackupPromptDialog private constructor(
this.alertDialog =
AlertDialog.Builder(windowContext).create {
setIcon(if (isLoggedIn) R.drawable.ic_baseline_backup_24 else R.drawable.ic_backup_restore)
- title(R.string.backup_your_collection)
- message(R.string.backup_collection_message)
- positiveButton(if (isLoggedIn) R.string.button_sync else R.string.button_backup) {
+ title(CommonString.backup_your_collection)
+ message(CommonString.backup_collection_message)
+ positiveButton(if (isLoggedIn) CommonString.button_sync else CommonString.button_backup) {
Timber.i("User selected 'backup'")
onBackup()
performBackup()
}
if (allowUserToPermanentlyDismissDialog) {
- checkBoxPrompt(R.string.button_do_not_show_again, isCheckedDefault = false) { checked ->
+ checkBoxPrompt(CommonString.button_do_not_show_again, isCheckedDefault = false) { checked ->
Timber.d("Don't show again checked: %b", checked)
userCheckedDoNotShowAgain = checked
alertDialog.positiveButton.isEnabled = !checked
}
}
- negativeButton(R.string.button_backup_later) { onDismiss() }
+ negativeButton(CommonString.button_backup_later) { onDismiss() }
cancelable(false)
}
}
@@ -169,7 +170,7 @@ class BackupPromptDialog private constructor(
fun getPermanentlyDismissDialogMessageOrImmediatelyDismiss(context: Context): Int? {
if (userIsPromptedToDeleteCollectionOnUninstall(context)) {
Timber.d("User's collection may be deleted on uninstall")
- return R.string.dismiss_backup_warning_new_user // message stating collection will be deleted (new user/migrated)
+ return CommonString.dismiss_backup_warning_new_user // message stating collection will be deleted (new user/migrated)
}
// Full build users/users on old Androids will see this dialog, but only if they are syncing
@@ -192,7 +193,7 @@ class BackupPromptDialog private constructor(
Timber.d("User will lose access to their collection")
// message stating collection will be made inaccessible
// (existing user, not migrated)
- R.string.dismiss_backup_warning_upgrade
+ CommonString.dismiss_backup_warning_upgrade
} else {
// A user is on a Play Store Build. They are on a version of Android with storage restrictions
// Their collection is in a 'legacy' location but they are not going to lose access to their collection when they uninstall
@@ -200,7 +201,7 @@ class BackupPromptDialog private constructor(
Timber.w("getPermanentlyDismissDialogMessage: unexpected state")
CrashReportService.sendExceptionReport(IllegalStateException("unexpected state"), "getPermanentlyDismissDialogMessage")
// assume this is a mistake and show a scary confirmation prompt
- R.string.dismiss_backup_warning_new_user // message stating collection will be deleted
+ CommonString.dismiss_backup_warning_new_user // message stating collection will be deleted
}
}
@@ -218,11 +219,11 @@ class BackupPromptDialog private constructor(
}
AlertDialog.Builder(context).show {
- title(R.string.dismiss_backup_warning_title)
+ title(CommonString.dismiss_backup_warning_title)
message(message)
setIcon(R.drawable.ic_warning)
- positiveButton(R.string.dialog_cancel) { onCancel() }
- negativeButton(R.string.button_disable_reminder) { onDisableReminder() }
+ positiveButton(CommonString.dialog_cancel) { onCancel() }
+ negativeButton(CommonString.button_disable_reminder) { onDisableReminder() }
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BrowserOptionsDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BrowserOptionsDialog.kt
index 5bc27f6ead84..3c58d7e33298 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BrowserOptionsDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/BrowserOptionsDialog.kt
@@ -11,6 +11,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.browser.BrowserColumnSelectionFragment
import com.ichi2.anki.browser.CardBrowserViewModel
@@ -107,8 +108,8 @@ class BrowserOptionsDialog : AppCompatDialogFragment(R.layout.dialog_browser_opt
return MaterialAlertDialogBuilder(requireContext()).create {
setView(binding.root)
setTitle(TR.sentenceCase.browserOptions)
- positiveButton(R.string.save) { saveChanges() }
- negativeButton(R.string.dialog_cancel)
+ positiveButton(CommonString.save) { saveChanges() }
+ negativeButton(CommonString.dialog_cancel)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CardSideSelectionDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CardSideSelectionDialog.kt
index 67837ef2fbfc..c14e26869db8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CardSideSelectionDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CardSideSelectionDialog.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.dialogs
import android.annotation.SuppressLint
import android.content.Context
import androidx.appcompat.app.AlertDialog
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.reviewer.CardSide
import com.ichi2.utils.show
import com.ichi2.utils.title
@@ -20,18 +20,18 @@ class CardSideSelectionDialog {
) {
val items =
listOf(
- R.string.card_side_both,
- R.string.card_side_question,
- R.string.card_side_answer,
+ CommonString.card_side_both,
+ CommonString.card_side_question,
+ CommonString.card_side_answer,
)
AlertDialog.Builder(ctx).show {
- title(R.string.card_side_selection_title)
+ title(CommonString.card_side_selection_title)
setItems(items.map { ctx.getString(it) }.toTypedArray()) { _, index ->
when (items[index]) {
- R.string.card_side_both -> callback(CardSide.BOTH)
- R.string.card_side_question -> callback(CardSide.QUESTION)
- R.string.card_side_answer -> callback(CardSide.ANSWER)
+ CommonString.card_side_both -> callback(CardSide.BOTH)
+ CommonString.card_side_question -> callback(CardSide.QUESTION)
+ CommonString.card_side_answer -> callback(CardSide.ANSWER)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
index 798ce00bd53e..f039a9c6866d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialog.kt
@@ -6,6 +6,7 @@ package com.ichi2.anki.dialogs
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
+import android.os.Build
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.LayoutInflater
@@ -21,6 +22,10 @@ import androidx.annotation.CheckResult
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.core.text.BidiFormatter
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat.Type.displayCutout
+import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
@@ -35,6 +40,7 @@ import com.google.android.material.tabs.TabLayoutMediator
import com.google.android.material.textview.MaterialTextView
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.CrashReportData.Companion.toCrashReportData
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
@@ -59,6 +65,7 @@ import com.ichi2.anki.ui.internationalization.sentenceCase
import com.ichi2.anki.utils.InitStatus
import com.ichi2.anki.utils.ext.launchCollectionInLifecycleScope
import com.ichi2.anki.withProgress
+import com.ichi2.themes.Themes
import com.ichi2.utils.LanguageUtil
import com.ichi2.utils.boldList
import kotlinx.coroutines.flow.filterNotNull
@@ -99,6 +106,7 @@ class ChangeNoteTypeDialog : AnalyticsDialogFragment(R.layout.dialog_change_note
) {
super.onViewCreated(view, savedInstanceState)
val binding = DialogChangeNoteTypeBinding.bind(view)
+ setupEdgeToEdge(binding)
binding.toolbar.title = TR.sentenceCase.changeNoteType
binding.toolbar.setNavigationOnClickListener { dismiss() }
@@ -131,6 +139,18 @@ class ChangeNoteTypeDialog : AnalyticsDialogFragment(R.layout.dialog_change_note
}
}
+ private fun setupEdgeToEdge(binding: DialogChangeNoteTypeBinding) {
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
+ val bars = insets.getInsets(systemBars() or displayCutout())
+ view.setPadding(bars.left, bars.top, bars.right, bars.bottom)
+ insets
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ val window = dialog?.window ?: return
+ WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = !Themes.isNightTheme
+ }
+ }
+
private fun setupFlows() {
launchCatchingTask {
viewModel.closeDialogFlow.filterNotNull().collect {
@@ -567,12 +587,12 @@ class ChangeNoteTypeDialog : AnalyticsDialogFragment(R.layout.dialog_change_note
REGULAR_TO_REGULAR -> Standard
CLOZE_TO_CLOZE, REGULAR_TO_CLOZE ->
WithWarning(
- warningRes = R.string.card_numbers_unchanged,
+ warningRes = CommonString.card_numbers_unchanged,
)
// Improvement: we could detect this using the max ord of provided notes
CLOZE_TO_REGULAR ->
WithWarning(
- warningRes = R.string.extra_cloze_deletions_removed,
+ warningRes = CommonString.extra_cloze_deletions_removed,
)
}
}
@@ -692,5 +712,5 @@ private fun AnkiActivity.changeNoteType(viewModel: ChangeNoteTypeViewModel) =
@VisibleForTesting
fun ChangeNoteTypeException.Kind.toString(context: Context): String =
when (this) {
- ChangeNoteTypeException.Kind.NO_CHANGES -> context.getString(R.string.error_no_changes_to_save)
+ ChangeNoteTypeException.Kind.NO_CHANGES -> context.getString(CommonString.error_no_changes_to_save)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt
index 520ff3338191..44dbe51bf7f4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ConfirmationDialog.kt
@@ -6,6 +6,7 @@ package com.ichi2.anki.dialogs
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.utils.ext.ifNullOrEmpty
import com.ichi2.anki.utils.ext.requireString
@@ -34,7 +35,7 @@ class ConfirmationDialog : DialogFragment() {
get() =
requireArguments()
.getString(ARG_POSITIVE_BUTTON_TEXT)
- .ifNullOrEmpty { getString(R.string.dialog_ok) }
+ .ifNullOrEmpty { getString(CommonString.dialog_ok) }
private var confirm = Runnable {} // Do nothing by default
private var cancel = Runnable {} // Do nothing by default
@@ -76,7 +77,7 @@ class ConfirmationDialog : DialogFragment() {
title(text = title)
message(text = message)
positiveButton(text = positiveButtonText) { confirm.run() }
- negativeButton(R.string.dialog_cancel) { cancel.run() }
+ negativeButton(CommonString.dialog_cancel) { cancel.run() }
}
companion object {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt
index 7ba592b17cb0..c230871175b8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CreateDeckDialog.kt
@@ -12,6 +12,7 @@ import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.anki.libanki.DeckId
@@ -86,16 +87,16 @@ class CreateDeckDialog(
// Uses "Rename" for rename deck dialogs and "Create" for all other deck-related dialogs.
val positiveButtonTextRes =
when (deckDialogType) {
- DeckDialogType.RENAME_DECK -> R.string.rename
+ DeckDialogType.RENAME_DECK -> CommonString.rename
DeckDialogType.DECK,
DeckDialogType.SUB_DECK,
- -> R.string.dialog_positive_create
+ -> CommonString.dialog_positive_create
}
positiveButton(positiveButtonTextRes) {
onPositiveButtonClicked()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
setView(R.layout.dialog_generic_text_input)
}.input(
hint = textInputHint,
@@ -125,7 +126,7 @@ class CreateDeckDialog(
}
val existingDeckId = getColUnsafe.decks.idForName(maybeDeckName)
if (existingDeckId != null && existingDeckId != renamedDeckId) {
- dialog.getInputTextLayout().error = context.getString(R.string.error_name_exists)
+ dialog.getInputTextLayout().error = context.getString(CommonString.error_name_exists)
dialog.positiveButton.isEnabled = false
return@input
}
@@ -136,7 +137,7 @@ class CreateDeckDialog(
// To fix: they need [01, 02, 10]. Show a hint to help them
dialog.getInputTextLayout().helperText =
if (text.containsNumberLargerThanNine()) {
- context.getString(R.string.create_deck_numeric_hint)
+ context.getString(CommonString.create_deck_numeric_hint)
} else {
null
}
@@ -168,11 +169,11 @@ class CreateDeckDialog(
if (Decks.isValidDeckName(deckName)) {
if (createNewDeck(deckName)) {
// 11668: Display feedback if a deck is created
- displayFeedback(context.getString(R.string.deck_created))
+ displayFeedback(context.getString(CommonString.deck_created))
}
} else {
Timber.d("CreateDeckDialog::createDeck - Not creating invalid deck name '%s'", deckName)
- displayFeedback(context.getString(R.string.invalid_deck_name), Snackbar.LENGTH_LONG)
+ displayFeedback(context.getString(CommonString.invalid_deck_name), Snackbar.LENGTH_LONG)
}
// AlertDialog should be dismissed after the Keyboard 'Done' or Deck 'Ok' button is pressed
shownDialog?.dismiss()
@@ -215,7 +216,7 @@ class CreateDeckDialog(
if (!Decks.isValidDeckName(newDeckName)) {
Timber.w("CreateDeckDialog::renameDeck not renaming deck to invalid name")
Timber.d("invalid deck name: %s", newDeckName)
- displayFeedback(context.getString(R.string.invalid_deck_name), Snackbar.LENGTH_LONG)
+ displayFeedback(context.getString(CommonString.invalid_deck_name), Snackbar.LENGTH_LONG)
} else {
val deckId = renamedDeckId!!
try {
@@ -223,7 +224,7 @@ class CreateDeckDialog(
if (getColUnsafe.decks.rename(deckId, newDeckName).deck) {
onNewDeckCreated(deckId)
// 11668: Display feedback if a deck is renamed
- displayFeedback(context.getString(R.string.deck_renamed))
+ displayFeedback(context.getString(CommonString.deck_renamed))
}
} catch (e: BackendDeckIsFilteredException) {
Timber.w(e)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt
index 92a8ae0c222c..1d8934aaa624 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.kt
@@ -19,6 +19,7 @@ import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.BackupManager
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.ConflictResolution
import com.ichi2.anki.DatabaseRestorationListener
import com.ichi2.anki.DeckPicker
@@ -105,14 +106,14 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
// Collection failed to load; give user the option of either choosing from repair options, or closing
// the activity
alertDialog.show {
- title(R.string.open_collection_failed_title)
+ title(CommonString.open_collection_failed_title)
cancelable(false)
message(text = message)
setIcon(R.drawable.ic_warning)
- positiveButton(R.string.error_handling_options) {
+ positiveButton(CommonString.error_handling_options) {
showDatabaseErrorDialog(DIALOG_ERROR_HANDLING)
}
- negativeButton(R.string.close) {
+ negativeButton(CommonString.close) {
closeCollectionAndFinish()
}
}
@@ -123,17 +124,17 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
alertDialog
.create {
cancelable(false)
- title(R.string.answering_error_title)
+ title(CommonString.answering_error_title)
message(text = message)
setIcon(R.drawable.ic_warning)
- positiveButton(R.string.error_handling_options) {
+ positiveButton(CommonString.error_handling_options) {
showDatabaseErrorDialog(DIALOG_ERROR_HANDLING)
}
- negativeButton(R.string.answering_error_report) {
+ negativeButton(CommonString.answering_error_report) {
requireDeckPicker().sendErrorReport()
requireActivity().dismissAllDialogFragments()
}
- neutralButton(R.string.close) {
+ neutralButton(CommonString.close) {
closeCollectionAndFinish()
}
}.apply {
@@ -148,20 +149,20 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
val values = ArrayList(8)
if (!requireAnkiActivity().colIsOpenUnsafe()) {
// retry
- options.add(res.getString(R.string.backup_retry_opening))
+ options.add(res.getString(CommonString.backup_retry_opening))
values.add(ErrorHandlingEntries.RETRY)
}
// repair db with sqlite
if (sqliteInstalled) {
- options.add(res.getString(R.string.backup_error_menu_repair))
+ options.add(res.getString(CommonString.backup_error_menu_repair))
values.add(ErrorHandlingEntries.REPAIR)
}
// // restore from backup
- options.add(res.getString(R.string.backup_restore))
+ options.add(res.getString(CommonString.backup_restore))
values.add(ErrorHandlingEntries.RESTORE)
// one-way sync from server
if (isLoggedIn) {
- options.add(res.getString(R.string.backup_one_way_sync_from_server))
+ options.add(res.getString(CommonString.backup_one_way_sync_from_server))
values.add(ErrorHandlingEntries.ONE_WAY)
}
@@ -177,21 +178,21 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
}
if (shouldOfferResetToDefaultDirectory) {
- options.add(res.getString(R.string.backup_use_default_folder))
+ options.add(res.getString(CommonString.backup_use_default_folder))
values.add(ErrorHandlingEntries.RESET_TO_DEFAULT_DIRECTORY)
}
// delete old collection and build new one
- options.add(res.getString(R.string.backup_del_collection))
+ options.add(res.getString(CommonString.backup_del_collection))
values.add(ErrorHandlingEntries.NEW)
// copy stack trace and debug info
options.add(TR.sentenceCase.copyDebugInfo)
values.add(ErrorHandlingEntries.DEBUG_INFO)
alertDialog.show {
- title(R.string.error_handling_title)
+ title(CommonString.error_handling_title)
setIcon(R.drawable.ic_warning)
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
listItems(items = options) { _, index ->
when (values[index]) {
ErrorHandlingEntries.RETRY -> {
@@ -243,14 +244,14 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
DIALOG_REPAIR_COLLECTION -> {
// Allow user to run BackupManager.repairCollection()
alertDialog.show {
- title(R.string.dialog_positive_repair)
+ title(CommonString.dialog_positive_repair)
message(text = message)
setIcon(R.drawable.ic_warning)
- positiveButton(R.string.dialog_positive_repair) {
+ positiveButton(CommonString.dialog_positive_repair) {
requireDeckPicker().repairCollection()
requireActivity().dismissAllDialogFragments()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
DIALOG_RESTORE_BACKUP -> {
@@ -258,9 +259,9 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
backups = BackupManager.getBackups(CollectionHelper.getCollectionPath(requireContext()))
if (backups.isEmpty()) {
alertDialog
- .title(R.string.backup_restore)
+ .title(CommonString.backup_restore)
.title(text = message)
- .positiveButton(R.string.dialog_ok) {
+ .positiveButton(CommonString.dialog_ok) {
showDatabaseErrorDialog(DIALOG_ERROR_HANDLING)
}
} else {
@@ -270,12 +271,12 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
val dates = backups.map { formatter.getTimeOfBackupAsText(it) }
alertDialog
- .title(R.string.backup_restore_select_title)
- .positiveButton(R.string.restore_backup_choose_another) {
+ .title(CommonString.backup_restore_select_title)
+ .positiveButton(CommonString.restore_backup_choose_another) {
ankiActivity?.let {
ImportFileSelectionFragment.openImportFilePicker(it, ImportFileSelectionFragment.ImportFileType.APKG)
}
- }.negativeButton(R.string.dialog_cancel)
+ }.negativeButton(CommonString.dialog_cancel)
.setSingleChoiceItems(dates.toTypedArray(), -1) { _, index: Int ->
if (backups[index].length() > 0) {
// restore the backup if it's valid
@@ -286,9 +287,9 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
} else {
// otherwise show an error dialog
AlertDialog.Builder(requireActivity()).show {
- title(R.string.vague_error)
- message(R.string.backup_invalid_file_error)
- positiveButton(R.string.dialog_ok)
+ title(CommonString.vague_error)
+ message(CommonString.backup_invalid_file_error)
+ positiveButton(CommonString.dialog_ok)
}
}
}
@@ -298,9 +299,9 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
DIALOG_NEW_COLLECTION -> {
// Allow user to create a new empty collection
alertDialog.show {
- title(R.string.backup_new_collection)
+ title(CommonString.backup_new_collection)
message(text = message)
- positiveButton(R.string.dialog_positive_create) {
+ positiveButton(CommonString.dialog_positive_create) {
val time = TimeManager.time
Timber.i(
"closeCollection: %s",
@@ -314,7 +315,7 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
showDatabaseErrorDialog(DIALOG_LOAD_FAILED)
}
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
DIALOG_CONFIRM_DATABASE_CHECK -> {
@@ -322,42 +323,42 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
alertDialog.show {
title(text = TR.sentenceCase.checkDatabase)
message(text = message)
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
requireDeckPicker().integrityCheck()
requireActivity().dismissAllDialogFragments()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
DIALOG_CONFIRM_RESTORE_BACKUP -> {
// Confirmation dialog for backup restore
alertDialog.show {
- title(R.string.restore_backup_title)
+ title(CommonString.restore_backup_title)
message(text = message)
- positiveButton(R.string.dialog_continue) {
+ positiveButton(CommonString.dialog_continue) {
showDatabaseErrorDialog(DIALOG_RESTORE_BACKUP)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
DIALOG_ONE_WAY_SYNC_FROM_SERVER -> {
// Allow user to do a full-sync from the server
alertDialog.show {
- title(R.string.backup_one_way_sync_from_server)
+ title(CommonString.backup_one_way_sync_from_server)
message(text = message)
- positiveButton(R.string.dialog_positive_overwrite) {
+ positiveButton(CommonString.dialog_positive_overwrite) {
requireDeckPicker().sync(ConflictResolution.FULL_DOWNLOAD)
requireActivity().dismissAllDialogFragments()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
DIALOG_DB_LOCKED -> {
// If the database is locked, all we can do is ask the user to exit.
alertDialog.show {
- title(R.string.database_locked_title)
+ title(CommonString.database_locked_title)
message(text = message)
- positiveButton(R.string.close) {
+ positiveButton(CommonString.close) {
closeCollectionAndFinish()
}
cancelable(false)
@@ -366,17 +367,17 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
INCOMPATIBLE_DB_VERSION -> {
val values: MutableList = ArrayList(2)
val options = mutableListOf()
- options.add(makeBold(res.getString(R.string.backup_restore)))
+ options.add(makeBold(res.getString(CommonString.backup_restore)))
values.add(IncompatibleDbVersionEntries.RESTORE)
if (isLoggedIn) {
- options.add(makeBold(res.getString(R.string.backup_one_way_sync_from_server)))
+ options.add(makeBold(res.getString(CommonString.backup_one_way_sync_from_server)))
values.add(IncompatibleDbVersionEntries.ONE_WAY)
}
alertDialog.show {
cancelable(false)
- title(R.string.incompatible_database_version_title)
+ title(CommonString.incompatible_database_version_title)
setIcon(R.drawable.ic_warning)
- positiveButton(R.string.close) {
+ positiveButton(CommonString.close) {
closeCollectionAndFinish()
}
listItemsAndMessage(message = message, items = options) { _, index: Int ->
@@ -395,9 +396,9 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
}
DIALOG_DISK_FULL -> {
alertDialog.show {
- title(R.string.storage_full_title)
+ title(CommonString.storage_full_title)
message(text = message)
- positiveButton(R.string.close) {
+ positiveButton(CommonString.close) {
closeCollectionAndFinish()
}
}
@@ -405,7 +406,7 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
DIALOG_STORAGE_UNAVAILABLE_AFTER_UNINSTALL -> {
val listItems = UninstallListItem.createList()
alertDialog.show {
- title(R.string.directory_inaccessible_after_uninstall)
+ title(CommonString.directory_inaccessible_after_uninstall)
listItemsAndMessage(message = message, items = listItems.map { getString(it.stringRes) }) { _, index: Int ->
val listItem = listItems[index]
listItem.onClick(requireAnkiActivity())
@@ -454,7 +455,7 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
context.copyToClipboard(
TruncatedString.from(combinedInfo),
- failureMessageId = R.string.about_ankidroid_error_copy_debug_info,
+ failureMessageId = CommonString.about_ankidroid_error_copy_debug_info,
)
}
@@ -465,28 +466,28 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
val onClick: (AnkiActivity) -> Unit,
) {
RESTORE_FROM_ANKIWEB(
- R.string.restore_data_from_ankiweb,
+ CommonString.restore_data_from_ankiweb,
dismissesDialog = true,
{
this.displayCreateNewCollectionDialog(it)
},
),
INSTALL_NON_PLAY_APP_RECOMMENDED(
- R.string.install_non_play_store_ankidroid_recommended,
+ CommonString.install_non_play_store_ankidroid_recommended,
dismissesDialog = false,
{
it.openUrl(R.string.link_install_non_play_store_install)
},
),
INSTALL_NON_PLAY_APP_NORMAL(
- R.string.install_non_play_store_ankidroid,
+ CommonString.install_non_play_store_ankidroid,
dismissesDialog = false,
{
it.openUrl(R.string.link_install_non_play_store_install)
},
),
RESTORE_FROM_BACKUP(
- R.string.restore_data_from_backup,
+ CommonString.restore_data_from_backup,
dismissesDialog = false,
{ activity ->
Timber.i("Restoring from colpkg")
@@ -509,14 +510,14 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
},
),
GET_HELP(
- R.string.help_title_get_help,
+ CommonString.help_title_get_help,
dismissesDialog = false,
{
it.openUrl(R.string.link_forum)
},
),
RECREATE_COLLECTION(
- R.string.create_new_collection,
+ CommonString.create_new_collection,
dismissesDialog = false,
{
this.displayCreateNewCollectionDialog(it)
@@ -536,10 +537,10 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
return
}
AlertDialog.Builder(context).show {
- title(R.string.backup_new_collection)
+ title(CommonString.backup_new_collection)
setIcon(R.drawable.ic_warning)
- message(R.string.new_unsafe_collection)
- positiveButton(R.string.dialog_positive_create) {
+ message(CommonString.new_unsafe_collection)
+ positiveButton(CommonString.dialog_positive_create) {
Timber.w("Creating new collection")
Timber.i(
"closeCollection: %s",
@@ -549,7 +550,7 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
resetAnkiDroidDirectory(context, directory)
context.closeCollectionAndFinish()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
cancelable(false)
}
}
@@ -586,20 +587,20 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
if (DatabaseCorruption.isDetected) {
// The sqlite database has been corrupted (DatabaseErrorHandler.onCorrupt() was called)
// Show a specific message appropriate for the situation
- res().getString(R.string.corrupt_db_message, res().getString(R.string.repair_deck))
+ res().getString(CommonString.corrupt_db_message, res().getString(R.string.repair_deck))
} else {
// Generic message shown when a libanki task failed
- res().getString(R.string.access_collection_failed_message, res().getString(R.string.link_help))
+ res().getString(CommonString.access_collection_failed_message, res().getString(R.string.link_help))
}
- DIALOG_DB_ERROR -> res().getString(R.string.answering_error_message)
- DIALOG_DISK_FULL -> res().getString(R.string.storage_full_message)
- DIALOG_REPAIR_COLLECTION -> res().getString(R.string.repair_deck_dialog, BackupManager.BROKEN_COLLECTIONS_SUFFIX)
- DIALOG_RESTORE_BACKUP -> res().getString(R.string.backup_restore_no_backups)
- DIALOG_NEW_COLLECTION -> res().getString(R.string.backup_del_collection_question)
- DIALOG_CONFIRM_DATABASE_CHECK -> res().getString(R.string.check_db_warning)
- DIALOG_CONFIRM_RESTORE_BACKUP -> res().getString(R.string.restore_backup)
- DIALOG_ONE_WAY_SYNC_FROM_SERVER -> res().getString(R.string.backup_full_sync_from_server_question)
- DIALOG_DB_LOCKED -> res().getString(R.string.database_locked_summary)
+ DIALOG_DB_ERROR -> res().getString(CommonString.answering_error_message)
+ DIALOG_DISK_FULL -> res().getString(CommonString.storage_full_message)
+ DIALOG_REPAIR_COLLECTION -> res().getString(CommonString.repair_deck_dialog, BackupManager.BROKEN_COLLECTIONS_SUFFIX)
+ DIALOG_RESTORE_BACKUP -> res().getString(CommonString.backup_restore_no_backups)
+ DIALOG_NEW_COLLECTION -> res().getString(CommonString.backup_del_collection_question)
+ DIALOG_CONFIRM_DATABASE_CHECK -> res().getString(CommonString.check_db_warning)
+ DIALOG_CONFIRM_RESTORE_BACKUP -> res().getString(CommonString.restore_backup)
+ DIALOG_ONE_WAY_SYNC_FROM_SERVER -> res().getString(CommonString.backup_full_sync_from_server_question)
+ DIALOG_DB_LOCKED -> res().getString(CommonString.database_locked_summary)
INCOMPATIBLE_DB_VERSION -> {
var databaseVersion = -1
try {
@@ -609,7 +610,7 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
}
val schemaVersion = Consts.BACKEND_SCHEMA_VERSION
res().getString(
- R.string.incompatible_database_version_summary,
+ CommonString.incompatible_database_version_summary,
schemaVersion,
databaseVersion,
)
@@ -617,31 +618,31 @@ class DatabaseErrorDialog : AsyncDialogFragment() {
DIALOG_STORAGE_UNAVAILABLE_AFTER_UNINSTALL -> {
val directory =
context?.let { CollectionHelper.getCurrentAnkiDroidDirectory(it) }
- ?: res().getString(R.string.card_browser_unknown_deck_name)
- res().getString(R.string.directory_inaccessible_after_uninstall_summary, directory)
+ ?: res().getString(CommonString.card_browser_unknown_deck_name)
+ res().getString(CommonString.directory_inaccessible_after_uninstall_summary, directory)
}
DIALOG_ERROR_HANDLING -> requireArguments().getString("dialogMessage")
}
private val title: String
get() =
when (requireDialogType()) {
- DIALOG_LOAD_FAILED -> res().getString(R.string.open_collection_failed_title)
- DIALOG_ERROR_HANDLING -> res().getString(R.string.error_handling_title)
- DIALOG_REPAIR_COLLECTION -> res().getString(R.string.dialog_positive_repair)
- DIALOG_RESTORE_BACKUP -> res().getString(R.string.backup_restore)
- DIALOG_NEW_COLLECTION -> res().getString(R.string.backup_new_collection)
+ DIALOG_LOAD_FAILED -> res().getString(CommonString.open_collection_failed_title)
+ DIALOG_ERROR_HANDLING -> res().getString(CommonString.error_handling_title)
+ DIALOG_REPAIR_COLLECTION -> res().getString(CommonString.dialog_positive_repair)
+ DIALOG_RESTORE_BACKUP -> res().getString(CommonString.backup_restore)
+ DIALOG_NEW_COLLECTION -> res().getString(CommonString.backup_new_collection)
DIALOG_CONFIRM_DATABASE_CHECK -> TR.sentenceCase.checkDatabase
- DIALOG_CONFIRM_RESTORE_BACKUP -> res().getString(R.string.restore_backup_title)
- DIALOG_ONE_WAY_SYNC_FROM_SERVER -> res().getString(R.string.backup_one_way_sync_from_server)
- DIALOG_DB_LOCKED -> res().getString(R.string.database_locked_title)
- INCOMPATIBLE_DB_VERSION -> res().getString(R.string.incompatible_database_version_title)
- DIALOG_DB_ERROR -> res().getString(R.string.answering_error_title)
- DIALOG_DISK_FULL -> res().getString(R.string.storage_full_title)
- DIALOG_STORAGE_UNAVAILABLE_AFTER_UNINSTALL -> res().getString(R.string.directory_inaccessible_after_uninstall)
+ DIALOG_CONFIRM_RESTORE_BACKUP -> res().getString(CommonString.restore_backup_title)
+ DIALOG_ONE_WAY_SYNC_FROM_SERVER -> res().getString(CommonString.backup_one_way_sync_from_server)
+ DIALOG_DB_LOCKED -> res().getString(CommonString.database_locked_title)
+ INCOMPATIBLE_DB_VERSION -> res().getString(CommonString.incompatible_database_version_title)
+ DIALOG_DB_ERROR -> res().getString(CommonString.answering_error_title)
+ DIALOG_DISK_FULL -> res().getString(CommonString.storage_full_title)
+ DIALOG_STORAGE_UNAVAILABLE_AFTER_UNINSTALL -> res().getString(CommonString.directory_inaccessible_after_uninstall)
}
override val notificationMessage: String? get() = message
- override val notificationTitle: String get() = res().getString(R.string.answering_error_title)
+ override val notificationTitle: String get() = res().getString(CommonString.answering_error_title)
override val dialogHandlerMessage
get() = ShowDatabaseErrorDialog(requireDialogType())
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerAnalyticsOptInDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerAnalyticsOptInDialog.kt
index 76ff5dd66b26..9b3af3006cbd 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerAnalyticsOptInDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerAnalyticsOptInDialog.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.dialogs
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.analytics.AnkiDroidUsageAnalytics
import com.ichi2.anki.utils.ext.dismissAllDialogFragments
@@ -21,10 +21,10 @@ class DeckPickerAnalyticsOptInDialog : AnalyticsDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
super.onCreateDialog(savedInstanceState)
return AlertDialog.Builder(requireActivity()).create {
- title(R.string.analytics_dialog_title)
- message(R.string.analytics_summ)
- checkBoxPrompt(R.string.analytics_title, isCheckedDefault = false) {}
- positiveButton(R.string.dialog_continue) {
+ title(CommonString.analytics_dialog_title)
+ message(CommonString.analytics_summ)
+ checkBoxPrompt(CommonString.analytics_title, isCheckedDefault = false) {}
+ positiveButton(CommonString.dialog_continue) {
AnkiDroidUsageAnalytics.isEnabled = (it as AlertDialog).getCheckBoxPrompt().isChecked
activity?.dismissAllDialogFragments()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerBackupNoSpaceLeftDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerBackupNoSpaceLeftDialog.kt
index 22c98b32901e..dd532a4f359c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerBackupNoSpaceLeftDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerBackupNoSpaceLeftDialog.kt
@@ -6,8 +6,8 @@ package com.ichi2.anki.dialogs
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.BackupManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
-import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.common.storage.CollectionHelper
import com.ichi2.utils.create
@@ -22,9 +22,9 @@ class DeckPickerBackupNoSpaceLeftDialog : AnalyticsDialogFragment() {
return AlertDialog
.Builder(requireContext())
.create {
- title(R.string.storage_almost_full_title)
- message(text = res.getString(R.string.storage_warning, space / 1024 / 1024))
- positiveButton(R.string.dialog_ok) {
+ title(CommonString.storage_almost_full_title)
+ message(text = res.getString(CommonString.storage_warning, space / 1024 / 1024))
+ positiveButton(CommonString.dialog_ok) {
(activity as DeckPicker).finish()
}
}.apply {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerConfirmDeleteDeckDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerConfirmDeleteDeckDialog.kt
index 6711134f3ea2..732a40cdc630 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerConfirmDeleteDeckDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerConfirmDeleteDeckDialog.kt
@@ -20,6 +20,8 @@ import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.core.text.HtmlCompat
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
@@ -36,10 +38,10 @@ class DeckPickerConfirmDeleteDeckDialog : AnalyticsDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val message =
if (isFilteredDeck) {
- resources.getString(R.string.delete_cram_deck_message, "$deckName")
+ resources.getString(CommonString.delete_cram_deck_message, "$deckName")
} else {
resources.getQuantityString(
- R.plurals.delete_deck_message,
+ CommonPlurals.delete_deck_message,
totalCards,
"$deckName",
totalCards,
@@ -48,17 +50,17 @@ class DeckPickerConfirmDeleteDeckDialog : AnalyticsDialogFragment() {
super.onCreate(savedInstanceState)
return AlertDialog
.Builder(requireActivity())
- .setTitle(R.string.delete_deck_title)
+ .setTitle(CommonString.delete_deck_title)
.setMessage(
HtmlCompat.fromHtml(
message,
HtmlCompat.FROM_HTML_MODE_LEGACY,
),
).setIcon(R.drawable.ic_warning)
- .setPositiveButton(R.string.dialog_positive_delete) { _, _ ->
+ .setPositiveButton(CommonString.dialog_positive_delete) { _, _ ->
(activity as DeckPicker).deleteDeck(deckId)
activity?.dismissAllDialogFragments()
- }.setNegativeButton(R.string.dialog_cancel) { _, _ ->
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ ->
activity?.dismissAllDialogFragments()
}.create()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.kt
index fe50daa91872..1ebd8fb14e8b 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.kt
@@ -24,7 +24,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.compat.requireSerializableCompat
import com.ichi2.anki.contextmenu.DeckPickerMenuContentProvider
@@ -87,16 +87,16 @@ class DeckPickerContextMenu : AnalyticsDialogFragment() {
DECK_OPTIONS -> TR.sentenceCase.deckOptions
CUSTOM_STUDY -> TR.sentenceCase.customStudy
DELETE_DECK -> TR.sentenceCase.deleteDeck
- EXPORT_DECK -> getString(R.string.export_deck)
+ EXPORT_DECK -> getString(CommonString.export_deck)
UNBURY -> TR.studyingUnbury()
CUSTOM_STUDY_REBUILD -> TR.actionsRebuild()
- CUSTOM_STUDY_EMPTY -> getString(R.string.empty_cram_label)
- CREATE_SUBDECK -> getString(R.string.create_subdeck)
- CREATE_SHORTCUT -> getString(R.string.create_shortcut)
- BROWSE_CARDS -> getString(R.string.browse_cards)
- EDIT_DESCRIPTION -> getString(R.string.edit_deck_description)
+ CUSTOM_STUDY_EMPTY -> getString(CommonString.empty_cram_label)
+ CREATE_SUBDECK -> getString(CommonString.create_subdeck)
+ CREATE_SHORTCUT -> getString(CommonString.create_shortcut)
+ BROWSE_CARDS -> getString(CommonString.browse_cards)
+ EDIT_DESCRIPTION -> getString(CommonString.edit_deck_description)
ADD_CARD -> TR.actionsAdd()
- SCHEDULE_REMINDERS -> getString(R.string.schedule_reminders_title)
+ SCHEDULE_REMINDERS -> getString(CommonString.schedule_reminders_title)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerNoSpaceLeftDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerNoSpaceLeftDialog.kt
index b499f37f1b39..c9dfbb57f794 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerNoSpaceLeftDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerNoSpaceLeftDialog.kt
@@ -18,7 +18,7 @@ package com.ichi2.anki.dialogs
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.utils.cancelable
import com.ichi2.utils.create
@@ -29,10 +29,10 @@ import com.ichi2.utils.title
class DeckPickerNoSpaceLeftDialog : AnalyticsDialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?) =
AlertDialog.Builder(requireActivity()).create {
- title(R.string.storage_full_title)
- message(R.string.backup_deck_no_storage_left)
+ title(CommonString.storage_full_title)
+ message(CommonString.backup_deck_no_storage_left)
cancelable(true)
- positiveButton(R.string.dialog_ok) {}
+ positiveButton(CommonString.dialog_ok) {}
setOnCancelListener {}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.kt
index f8f976137c5c..a7827b7b2b69 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.kt
@@ -42,6 +42,7 @@ import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CardTemplateEditor
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.OnContextAndLongClickListener.Companion.setOnContextAndLongClickListener
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
@@ -86,7 +87,7 @@ class DeckSelectionDialog : AnalyticsDialogFragment() {
private val title: String
get() =
requireArguments().getString(ARG_TITLE, null)
- ?: getString(R.string.select_deck_title)
+ ?: getString(CommonString.select_deck_title)
private val templateEditorMessage: String?
get() = requireArguments().getString(ARG_TEMPLATE_EDITOR_MESSAGE, null)
private val allowMultipleSelection: Boolean
@@ -106,10 +107,10 @@ class DeckSelectionDialog : AnalyticsDialogFragment() {
binding.decks.adapter = decksAdapter
setupMenu()
return AlertDialog.Builder(requireActivity()).create {
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
customView(view = binding.root)
if (templateEditorMessage != null) {
- positiveButton(R.string.restore_default) {
+ positiveButton(CommonString.restore_default) {
onDeckSelected(null)
}
}
@@ -133,7 +134,7 @@ class DeckSelectionDialog : AnalyticsDialogFragment() {
toolbar.inflateMenu(R.menu.deck_picker_dialog_menu)
val searchItem = toolbar.menu.findItem(R.id.action_filter)
val searchView = searchItem.actionView as AccessibleSearchView
- searchView.queryHint = getString(R.string.deck_picker_dialog_filter_decks)
+ searchView.queryHint = getString(CommonString.deck_picker_dialog_filter_decks)
searchView.setOnQueryTextListener(
object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
@@ -164,7 +165,7 @@ class DeckSelectionDialog : AnalyticsDialogFragment() {
val createDeckDialog =
CreateDeckDialog(
context = requireActivity(),
- title = getString(R.string.create_subdeck),
+ title = getString(CommonString.create_subdeck),
deckDialogType = CreateDeckDialog.DeckDialogType.SUB_DECK,
parentId = parentDeck.deckId,
)
@@ -339,7 +340,7 @@ class DeckSelectionDialog : AnalyticsDialogFragment() {
expander.apply {
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
setImageDrawable(if (node.collapsed) expandImage else collapseImage)
- contentDescription = context.getString(if (node.collapsed) R.string.expand else R.string.collapse)
+ contentDescription = context.getString(if (node.collapsed) CommonString.expand else CommonString.collapse)
visibility = View.VISIBLE
}
} else {
@@ -500,7 +501,7 @@ fun Fragment.registerDeckSelectedHandler(
* Displays a [DeckSelectionDialog] for the user to select a deck. Using the default parameters will
* result in all possible decks('All Decks', normal, filtered and the 'Default' deck) being shown.
*
- * @param title the title for the selection dialog, uses [R.string.select_decks_title] if not provided
+ * @param title the title for the selection dialog, uses [CommonString.select_decks_title] if not provided
* @param asChild if true [DeckSelectionDialog] will start as a child of [Fragment]([Fragment.getChildFragmentManager]
* will be used to start it). If false, the [FragmentActivity.getSupportFragmentManager] will be used
* instead to start [DeckSelectionDialog]
@@ -561,7 +562,7 @@ fun Fragment.startDeckSelection(
* Displays a [DeckSelectionDialog] for the user to select a deck. Using the default parameters will
* result in all possible decks('All Decks', normal, filtered and the 'Default' deck) being shown.
*
- * @param title the title for the selection dialog, uses [R.string.select_decks_title] if not provided
+ * @param title the title for the selection dialog, uses [CommonString.select_decks_title] if not provided
* @param templateEditorMessage an optional message shown only when used by [CardTemplateEditor]
* @param requestKey specifies the request key to be used for this request
* @param allowMultipleSelection if selection of multiple decks is permitted
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt
index 84d03cd1571c..ba9ed6c0d34a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.kt
@@ -8,10 +8,10 @@ import android.os.Message
import androidx.annotation.VisibleForTesting
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionLoadingErrorDialog
+import com.ichi2.anki.CommonString
import com.ichi2.anki.CrashReportData.Companion.toCrashReportData
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.IntentHandler
-import com.ichi2.anki.R
import com.ichi2.anki.common.analytics.Analytics
import com.ichi2.anki.common.utils.android.HandlerUtils.getDefaultLooper
import com.ichi2.anki.dialogs.DialogHandler.Companion.storeMessage
@@ -141,7 +141,7 @@ fun AnkiActivity.requireDeckPickerOrShowError(): DeckPicker? {
if (this is DeckPicker) return this
showError(
- message = getString(R.string.something_wrong),
+ message = getString(CommonString.something_wrong),
crashReportData =
ClassCastException(
this.javaClass.simpleName + " is not " + DeckPicker::class.java.simpleName,
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DiscardChangesDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DiscardChangesDialog.kt
index 8326b1e84fe7..ced7e459c95b 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DiscardChangesDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DiscardChangesDialog.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.CollectionManager.TR
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.ui.internationalization.sentenceCase
import com.ichi2.utils.message
import com.ichi2.utils.negativeButton
@@ -18,7 +18,7 @@ import timber.log.Timber
object DiscardChangesDialog {
fun showDialog(
context: Context,
- positiveButtonText: String = context.getString(R.string.discard),
+ positiveButtonText: String = context.getString(CommonString.discard),
negativeButtonText: String = with(context) { TR.sentenceCase.keepEditing },
neutralButtonText: String? = null,
message: String = TR.cardTemplatesDiscardChanges(),
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EditDeckDescriptionDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EditDeckDescriptionDialog.kt
index 10447c6c3807..302e0d45568f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EditDeckDescriptionDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EditDeckDescriptionDialog.kt
@@ -16,6 +16,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.StudyOptionsFragment
import com.ichi2.anki.databinding.DialogDeckDescriptionBinding
@@ -65,8 +66,8 @@ class EditDeckDescriptionDialog : DialogFragment() {
return MaterialAlertDialogBuilder(requireContext())
.create {
setView(binding.root)
- positiveButton(R.string.save)
- negativeButton(R.string.close)
+ positiveButton(CommonString.save)
+ negativeButton(CommonString.close)
}.apply {
alertDialog = this
setOnShowListener {
@@ -109,7 +110,7 @@ class EditDeckDescriptionDialog : DialogFragment() {
// setup 'Format as Markdown' help
binding.markdownFormattingHelp.apply {
contentDescription =
- getString(R.string.help_button_content_description, getString(R.string.format_deck_description_as_markdown))
+ getString(CommonString.help_button_content_description, getString(CommonString.format_deck_description_as_markdown))
setOnClickListener {
MaterialAlertDialogBuilder(requireContext()).show {
setTitle(binding.formatAsMarkdownInput.text)
@@ -140,7 +141,7 @@ class EditDeckDescriptionDialog : DialogFragment() {
DismissType.ClosedWithoutSaving -> requireActivity().dismissAllDialogFragments()
DismissType.Saved -> {
requireActivity().dismissAllDialogFragments()
- showSnackbar(R.string.deck_description_saved)
+ showSnackbar(CommonString.deck_description_saved)
// notify DeckPicker to invalidate its toolbar menu, otherwise the undo
// action to revert the description change is not going to be visible
// when there are no other undo actions
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EmptyCardsDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EmptyCardsDialogFragment.kt
index a6f60f2599ba..eef818459856 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EmptyCardsDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/EmptyCardsDialogFragment.kt
@@ -31,8 +31,8 @@ import androidx.lifecycle.repeatOnLifecycle
import anki.card_rendering.EmptyCardsReport
import com.ichi2.anki.CardBrowser
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
-import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.common.destinations.BrowserDestination
import com.ichi2.anki.common.destinations.navigate
@@ -73,7 +73,7 @@ class EmptyCardsDialogFragment : AnalyticsDialogFragment() {
.Builder(requireContext())
.show {
setTitle(TR.sentenceCase.emptyCardsTitle)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
val state = viewModel.uiState.value
if (state is EmptyCardsSearchResult) {
// this dialog is only shown from DeckPicker so we use it directly to avoid
@@ -86,7 +86,7 @@ class EmptyCardsDialogFragment : AnalyticsDialogFragment() {
)
}
}
- setNegativeButton(R.string.dialog_cancel) { _, _ ->
+ setNegativeButton(CommonString.dialog_cancel) { _, _ ->
Timber.i("Empty cards dialog cancelled")
}
setView(binding.root)
@@ -103,12 +103,12 @@ class EmptyCardsDialogFragment : AnalyticsDialogFragment() {
viewModel.uiState.collect { state ->
when (state) {
is SearchingForEmptyCards -> {
- binding.loadingMessage.text = getString(R.string.emtpy_cards_finding)
+ binding.loadingMessage.text = getString(CommonString.emtpy_cards_finding)
binding.loadingContainer.isVisible = true
binding.emptyCardsResultsContainer.isVisible = false
(dialog as? AlertDialog)?.positiveButton?.apply {
isEnabled = false
- text = getString(R.string.dialog_ok)
+ text = getString(CommonString.dialog_ok)
}
}
@@ -121,7 +121,7 @@ class EmptyCardsDialogFragment : AnalyticsDialogFragment() {
// nothing to delete so also hide the preserve notes check box
binding.keepNotesWithNoValidCards.isVisible = false
(dialog as? AlertDialog)?.positiveButton?.text =
- getString(R.string.dialog_ok)
+ getString(CommonString.dialog_ok)
(dialog as? AlertDialog)?.negativeButton?.visibility = View.GONE
} else {
binding.reportScrollView.updateViewHeight()
@@ -132,7 +132,7 @@ class EmptyCardsDialogFragment : AnalyticsDialogFragment() {
binding.keepNotesWithNoValidCards.isVisible = true
binding.emptyReportMessage.isVisible = false
(dialog as? AlertDialog)?.positiveButton?.text =
- getString(R.string.dialog_positive_delete)
+ getString(CommonString.dialog_positive_delete)
binding.emptyCardsResultsContainer.isVisible = true
}
(dialog as? AlertDialog)?.positiveButton?.isEnabled = true
@@ -142,9 +142,9 @@ class EmptyCardsDialogFragment : AnalyticsDialogFragment() {
// the dialog is informational so there's nothing to do but show the
// error and exit
AlertDialog.Builder(requireActivity()).show {
- title(R.string.vague_error)
+ title(CommonString.vague_error)
message(text = state.throwable.toString())
- positiveButton(R.string.dialog_ok) { }
+ positiveButton(CommonString.dialog_ok) { }
}
dismissNow()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt
index 2712f515634a..ea40a215ccee 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportReadyDialog.kt
@@ -7,7 +7,7 @@ import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.ichi2.anki.AnkiActivity
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.dialogs.viewmodel.ExportReadyViewModel.ExportReadyParams
import com.ichi2.anki.utils.ext.requireString
import com.ichi2.utils.negativeButton
@@ -24,13 +24,13 @@ class ExportReadyDialog : DialogFragment() {
val dialog = AlertDialog.Builder(requireActivity())
dialog
- .setTitle(getString(R.string.export_ready_title))
- .positiveButton(R.string.export_choice_save_to) {
+ .setTitle(getString(CommonString.export_ready_title))
+ .positiveButton(CommonString.export_choice_save_to) {
parentFragmentManager.setFragmentResult(
REQUEST_EXPORT_SAVE,
Bundle().apply { putString(KEY_EXPORT_PATH, exportPath) },
)
- }.negativeButton(R.string.export_choice_share) {
+ }.negativeButton(CommonString.export_choice_share) {
parentFragmentManager.setFragmentResult(
REQUEST_EXPORT_SHARE,
Bundle().apply {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FatalErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FatalErrorDialog.kt
index 15b5f5f04adb..a862564ca7a9 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FatalErrorDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FatalErrorDialog.kt
@@ -5,8 +5,8 @@ package com.ichi2.anki.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.AnkiActivity
+import com.ichi2.anki.CommonString
import com.ichi2.anki.InitialActivity.StartupFailure.InitializationError
-import com.ichi2.anki.R
import com.ichi2.utils.cancelable
import com.ichi2.utils.create
import com.ichi2.utils.message
@@ -23,13 +23,13 @@ object FatalErrorDialog {
val context: Context = activity
Timber.i("Displaying 'Fatal error'")
return AlertDialog.Builder(context).create {
- title(R.string.ankidroid_init_failed_webview_title)
+ title(CommonString.ankidroid_init_failed_webview_title)
message(text = failure.toHumanReadableString(context))
- positiveButton(R.string.close) {
+ positiveButton(CommonString.close) {
activity.closeCollectionAndFinish()
}
failure.infoLink?.let { url ->
- neutralButton(R.string.help) {
+ neutralButton(CommonString.help) {
activity.openUrl(url)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FieldSelectionDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FieldSelectionDialog.kt
index 4f7535ac86b2..b68c78ac1c46 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FieldSelectionDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FieldSelectionDialog.kt
@@ -9,7 +9,7 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.ichi2.anki.CollectionManager.withCol
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.dialogs.FieldSelectionDialog.Companion.RESULT_TYPE
import com.ichi2.anki.dialogs.FieldSelectionDialog.Companion.registerFieldSelectionHandler
import com.ichi2.anki.libanki.Field
@@ -41,7 +41,7 @@ class FieldSelectionDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
MaterialAlertDialogBuilder(requireContext()).create {
- setTitle(R.string.card_template_editor_select_field)
+ setTitle(CommonString.card_template_editor_select_field)
setItems(fieldNames.toTypedArray()) { _, which ->
onFieldSelected(fieldNames[which])
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FlagRenameDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FlagRenameDialog.kt
index 5f25bcd0e6e8..fcb66070a744 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FlagRenameDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/FlagRenameDialog.kt
@@ -11,8 +11,8 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.Flag
-import com.ichi2.anki.R
import com.ichi2.anki.databinding.DialogRenameFlagBinding
import com.ichi2.utils.customView
import com.ichi2.utils.title
@@ -28,7 +28,7 @@ class FlagRenameDialog : DialogFragment() {
val builder =
AlertDialog.Builder(requireContext()).apply {
customView(view = binding.root, 4, 4, 4, 4)
- title(R.string.rename_flag)
+ title(CommonString.rename_flag)
}
val dialog = builder.create()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/GradeNowDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/GradeNowDialog.kt
index 06392301cead..91cd2fb5494a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/GradeNowDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/GradeNowDialog.kt
@@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
import anki.scheduler.CardAnswer.Rating
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.databinding.ItemGradeNowBinding
@@ -55,7 +56,7 @@ object GradeNowDialog {
MaterialAlertDialogBuilder(context).show {
title(text = with(context) { TR.sentenceCase.gradeNow })
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
setAdapter(adapter) { dialog, which ->
val selectedGrade = adapter.getItem(which)!!
Timber.i("selected '%s'", selectedGrade.name)
@@ -75,7 +76,7 @@ object GradeNowDialog {
undoableOp { this.backend.gradeNow(ids, grade.rating) }
}
showSnackbar(TR.schedulingGradedCardsDone(ids.size)) {
- setAction(R.string.undo) { launchCatchingTask { undoAndShowSnackbar() } }
+ setAction(CommonString.undo) { launchCatchingTask { undoAndShowSnackbar() } }
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt
index ef1abbc4ee99..1eed98a0ecb7 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportDialog.kt
@@ -7,7 +7,7 @@ import android.os.Bundle
import androidx.annotation.CheckResult
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.dialogs.ImportDialog.Type.DIALOG_IMPORT_ADD_CONFIRM
import com.ichi2.anki.dialogs.ImportDialog.Type.DIALOG_IMPORT_REPLACE_CONFIRM
@@ -35,22 +35,22 @@ class ImportDialog : AsyncDialogFragment() {
return when (dialogType) {
DIALOG_IMPORT_ADD_CONFIRM -> {
dialog
- .setTitle(R.string.import_title)
- .setMessage(res().getString(R.string.import_dialog_message_add, displayFileName))
- .positiveButton(R.string.import_message_add) {
+ .setTitle(CommonString.import_title)
+ .setMessage(res().getString(CommonString.import_dialog_message_add, displayFileName))
+ .positiveButton(CommonString.import_message_add) {
importViewModel.triggerImportAdd(packagePath)
activity?.dismissAllDialogFragments()
- }.negativeButton(R.string.dialog_cancel)
+ }.negativeButton(CommonString.dialog_cancel)
.create()
}
DIALOG_IMPORT_REPLACE_CONFIRM -> {
dialog
- .setTitle(R.string.import_title)
- .setMessage(res().getString(R.string.import_message_replace_confirm, displayFileName))
- .positiveButton(R.string.dialog_positive_replace) {
+ .setTitle(CommonString.import_title)
+ .setMessage(res().getString(CommonString.import_message_replace_confirm, displayFileName))
+ .positiveButton(CommonString.dialog_positive_replace) {
importViewModel.triggerImportReplace(packagePath)
activity?.dismissAllDialogFragments()
- }.negativeButton(R.string.dialog_cancel)
+ }.negativeButton(CommonString.dialog_cancel)
.create()
}
}
@@ -69,12 +69,12 @@ class ImportDialog : AsyncDialogFragment() {
override val notificationMessage: String
get() {
- return res().getString(R.string.import_interrupted)
+ return res().getString(CommonString.import_interrupted)
}
override val notificationTitle: String
get() {
- return res().getString(R.string.import_title)
+ return res().getString(CommonString.import_title)
}
enum class Type(
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportFileSelectionFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportFileSelectionFragment.kt
index 29553578c023..62f321c68f68 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportFileSelectionFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ImportFileSelectionFragment.kt
@@ -15,7 +15,7 @@ import androidx.core.os.BundleCompat
import androidx.fragment.app.DialogFragment
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.analytics.Analytics
import com.ichi2.anki.common.analytics.AnalyticsEvent.LinkClicked
import com.ichi2.anki.common.analytics.LinkAction
@@ -57,7 +57,7 @@ class ImportFileSelectionFragment : DialogFragment() {
if (options.importApkg) {
add(
ImportEntry(
- R.string.import_deck_package,
+ CommonString.import_deck_package,
LinkAction.IMPORT_APKG_FILE,
ImportFileType.APKG,
),
@@ -66,7 +66,7 @@ class ImportFileSelectionFragment : DialogFragment() {
if (options.importColpkg) {
add(
ImportEntry(
- R.string.import_collection_package,
+ CommonString.import_collection_package,
LinkAction.IMPORT_COLPKG_FILE,
ImportFileType.COLPKG,
),
@@ -75,7 +75,7 @@ class ImportFileSelectionFragment : DialogFragment() {
if (options.importTextFile) {
add(
ImportEntry(
- R.string.import_csv,
+ CommonString.import_csv,
LinkAction.IMPORT_CSV_FILE,
ImportFileType.CSV,
multiple = false,
@@ -171,7 +171,7 @@ class ImportFileSelectionFragment : DialogFragment() {
}
} catch (ex: ActivityNotFoundException) {
Timber.w("No activity to handle openImportFilePicker request")
- activity.showSnackbar(R.string.activity_start_failed)
+ activity.showSnackbar(CommonString.activity_start_failed)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/InsertFieldDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/InsertFieldDialog.kt
index 8f4ecb06c6d4..d0710c2dd91f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/InsertFieldDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/InsertFieldDialog.kt
@@ -4,6 +4,7 @@
package com.ichi2.anki.dialogs
import android.content.Context
+import android.os.Build
import android.os.Bundle
import android.text.Spanned
import android.view.LayoutInflater
@@ -14,6 +15,10 @@ import androidx.annotation.CheckResult
import androidx.annotation.StringRes
import androidx.core.text.HtmlCompat
import androidx.core.text.parseAsHtml
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat.Type.displayCutout
+import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -24,6 +29,7 @@ import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.ichi2.anki.CardTemplateEditor
+import com.ichi2.anki.CommonString
import com.ichi2.anki.Flag
import com.ichi2.anki.R
import com.ichi2.anki.databinding.DialogGenericRecyclerViewBinding
@@ -37,6 +43,7 @@ import com.ichi2.anki.launchCatchingTask
import com.ichi2.anki.model.SpecialField
import com.ichi2.anki.model.SpecialFields
import com.ichi2.anki.utils.ext.requireString
+import com.ichi2.themes.Themes
import dev.androidbroadcast.vbpd.viewBinding
import org.jetbrains.annotations.VisibleForTesting
@@ -63,8 +70,9 @@ class InsertFieldDialog : DialogFragment(R.layout.dialog_insert_field) {
) {
super.onViewCreated(view, savedInstanceState)
val binding = DialogInsertFieldBinding.bind(view)
+ setupEdgeToEdge(binding)
- binding.toolbar.title = getString(R.string.card_template_editor_select_field)
+ binding.toolbar.title = getString(CommonString.card_template_editor_select_field)
binding.toolbar.setNavigationOnClickListener { dismiss() }
binding.viewPager.adapter = InsertFieldDialogAdapter(this)
@@ -106,6 +114,18 @@ class InsertFieldDialog : DialogFragment(R.layout.dialog_insert_field) {
}
}
+ private fun setupEdgeToEdge(binding: DialogInsertFieldBinding) {
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
+ val bars = insets.getInsets(systemBars() or displayCutout())
+ view.setPadding(bars.left, bars.top, bars.right, bars.bottom)
+ insets
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ val window = dialog?.window ?: return
+ WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = !Themes.isNightTheme
+ }
+ }
+
companion object {
/**
* A key in the extras of the Fragment Result
@@ -242,19 +262,19 @@ fun SpecialField.buildDescription(
): Spanned {
fun buildSuffix(value: String?): String {
if (value == null) return ""
- return context.getString(R.string.special_field_example_suffix, value)
+ return context.getString(CommonString.special_field_example_suffix, value)
}
return when (this) {
- SpecialFields.FrontSide -> context.getString(R.string.special_field_front_side_help)
+ SpecialFields.FrontSide -> context.getString(CommonString.special_field_front_side_help)
SpecialFields.Deck ->
- context.getString(R.string.special_field_deck_help, buildSuffix(metadata.deck))
+ context.getString(CommonString.special_field_deck_help, buildSuffix(metadata.deck))
SpecialFields.Subdeck ->
- context.getString(R.string.special_field_subdeck_help, buildSuffix(metadata.subdeck))
+ context.getString(CommonString.special_field_subdeck_help, buildSuffix(metadata.subdeck))
SpecialFields.Flag -> {
val code = metadata.flag ?: "N"
context.getString(
- R.string.special_field_flag_help,
+ CommonString.special_field_flag_help,
if (code == "N") "flag$code" else "flag$code",
"$code",
Flag.entries.minOf { it.code }.toString(),
@@ -263,20 +283,20 @@ fun SpecialField.buildDescription(
}
SpecialFields.Tags -> {
val tags = if (metadata.tags.isNullOrBlank()) null else metadata.tags
- context.getString(R.string.special_field_tags_help, buildSuffix(tags))
+ context.getString(CommonString.special_field_tags_help, buildSuffix(tags))
}
SpecialFields.CardId ->
- context.getString(R.string.special_field_card_id_help, buildSuffix(metadata.cardId?.toString()))
+ context.getString(CommonString.special_field_card_id_help, buildSuffix(metadata.cardId?.toString()))
SpecialFields.CardTemplate ->
context.getString(
- R.string.special_field_card_help,
+ CommonString.special_field_card_help,
buildSuffix(metadata.cardTemplateName),
)
SpecialFields.NoteType ->
context.getString(
- R.string.special_field_type_help,
+ CommonString.special_field_type_help,
buildSuffix(metadata.noteTypeName),
)
// this shouldn't happen
@@ -290,7 +310,7 @@ private val Tab.title: String
get() =
dialog.requireContext().getString(
when (this) {
- Tab.FIELDS -> R.string.standard_fields_tab_header
- Tab.SPECIAL -> R.string.special_fields_tab_header
+ Tab.FIELDS -> CommonString.standard_fields_tab_header
+ Tab.SPECIAL -> CommonString.special_fields_tab_header
},
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt
index decf6a405433..56789b6d7a84 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.kt
@@ -19,6 +19,7 @@ package com.ichi2.anki.dialogs
import android.os.Bundle
import android.text.InputType
import androidx.appcompat.app.AlertDialog
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.snackbar.showSnackbar
@@ -61,8 +62,8 @@ open class IntegerDialog : AnalyticsDialogFragment() {
.Builder(requireActivity())
.show {
title(text = requireArguments().getString("title"))
- positiveButton(R.string.dialog_ok)
- negativeButton(R.string.dialog_cancel)
+ positiveButton(CommonString.dialog_ok)
+ negativeButton(CommonString.dialog_cancel)
setMessage(requireArguments().getString("content"))
setView(R.layout.dialog_generic_text_input)
}.input(
@@ -79,7 +80,7 @@ open class IntegerDialog : AnalyticsDialogFragment() {
} catch (e: Exception) {
Timber.w(e)
// TODO: find a good place in the foreground to show snackbar
- showSnackbar(R.string.something_wrong)
+ showSnackbar(CommonString.something_wrong)
return@input
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LoadingDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LoadingDialogFragment.kt
index 53251532c99b..2e101a36fb34 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LoadingDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LoadingDialogFragment.kt
@@ -12,6 +12,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.commitNow
import com.google.android.material.loadingindicator.LoadingIndicator
import com.ichi2.anki.AnkiActivity
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.utils.cancelable
import com.ichi2.utils.create
@@ -26,7 +27,7 @@ class LoadingDialogFragment : DialogFragment() {
val canBeCancelled = arguments?.getBoolean(KEY_CANCELLABLE) ?: true
isCancelable = canBeCancelled
dialogView.findViewById(R.id.text).text =
- arguments?.getString(KEY_MESSAGE) ?: getString(R.string.dialog_processing)
+ arguments?.getString(KEY_MESSAGE) ?: getString(CommonString.dialog_processing)
return AlertDialog
.Builder(requireContext())
.create {
@@ -43,7 +44,7 @@ class LoadingDialogFragment : DialogFragment() {
/**
* Creates an instance of [LoadingDialogFragment].
* @param message optional message for the loading operation, will default to
- * [R.string.dialog_processing] if not provided
+ * [CommonString.dialog_processing] if not provided
* @param cancellable if the dialog should be cancellable or not (also affects cancel when
* touching outside the dialog window)
*/
@@ -90,7 +91,7 @@ fun AnkiActivity.showLoadingDialog(
val fragmentView = fragment.view // sanity check
if (fragmentView != null && cancellable == fragment.isCancelable) {
fragmentView.findViewById(R.id.text)?.text =
- message ?: getString(R.string.dialog_processing)
+ message ?: getString(CommonString.dialog_processing)
return
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenu.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenu.kt
index 3c4fde31e60e..d52a35009ba7 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenu.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/NoteTypeFieldEditorContextMenu.kt
@@ -8,8 +8,8 @@ import android.os.Bundle
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
+import com.ichi2.anki.CommonString
import com.ichi2.anki.NoteTypeFieldEditor
-import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.utils.create
import timber.log.Timber
@@ -35,11 +35,11 @@ class NoteTypeFieldEditorContextMenu : AnalyticsDialogFragment() {
val order: Int,
@StringRes val actionTextId: Int,
) {
- Reposition(0, R.string.model_field_editor_reposition_menu),
- Sort(1, R.string.model_field_editor_sort_field),
- Rename(2, R.string.model_field_editor_rename),
- Delete(3, R.string.model_field_editor_delete),
- AddLanguageHint(4, R.string.model_field_editor_language_hint),
+ Reposition(0, CommonString.model_field_editor_reposition_menu),
+ Sort(1, CommonString.model_field_editor_sort_field),
+ Rename(2, CommonString.model_field_editor_rename),
+ Delete(3, CommonString.model_field_editor_delete),
+ AddLanguageHint(4, CommonString.model_field_editor_language_hint),
}
companion object {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SaveBrowserSearchDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SaveBrowserSearchDialogFragment.kt
index 7bffb06cb0e1..1253d00e174d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SaveBrowserSearchDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SaveBrowserSearchDialogFragment.kt
@@ -11,6 +11,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.CardBrowser
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.browser.search.SavedSearch
import com.ichi2.anki.dialogs.SaveBrowserSearchDialogFragment.Companion.ARG_SEARCH_QUERY
@@ -39,12 +40,12 @@ class SaveBrowserSearchDialogFragment : DialogFragment() {
return AlertDialog
.Builder(requireContext())
.show {
- title(text = getString(R.string.card_browser_list_my_searches_save))
- positiveButton(R.string.dialog_ok)
- negativeButton(R.string.dialog_cancel)
+ title(text = getString(CommonString.card_browser_list_my_searches_save))
+ positiveButton(CommonString.dialog_ok)
+ negativeButton(CommonString.dialog_cancel)
setView(R.layout.dialog_generic_text_input)
}.input(
- hint = getString(R.string.card_browser_list_my_searches_new_name),
+ hint = getString(CommonString.card_browser_list_my_searches_new_name),
allowEmpty = false,
displayKeyboard = true,
waitForPositiveButton = true,
@@ -97,7 +98,7 @@ fun CardBrowser.registerSaveSearchHandler() {
bundle.getString(ARG_SEARCH_QUERY_NAME)
if (savedSearchName.isNullOrEmpty()) {
showSnackbar(
- R.string.card_browser_list_my_searches_new_search_error_empty_name,
+ CommonString.card_browser_list_my_searches_new_search_error_empty_name,
Snackbar.LENGTH_SHORT,
)
return@setFragmentResultListener
@@ -128,7 +129,7 @@ fun Fragment.registerSaveSearchHandler(onSaveSearch: (SavedSearch) -> Unit) {
bundle.getString(ARG_SEARCH_QUERY_NAME)
if (savedSearchName.isNullOrEmpty()) {
showSnackbar(
- R.string.card_browser_list_my_searches_new_search_error_empty_name,
+ CommonString.card_browser_list_my_searches_new_search_error_empty_name,
Snackbar.LENGTH_SHORT,
)
return@setFragmentResultListener
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SavedBrowserSearchesDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SavedBrowserSearchesDialogFragment.kt
index 3797c1bd24f7..174e2d1167d1 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SavedBrowserSearchesDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SavedBrowserSearchesDialogFragment.kt
@@ -10,7 +10,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.ichi2.anki.CardBrowser
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.browser.search.SavedSearch
import com.ichi2.anki.browser.search.SavedSearches
@@ -65,15 +65,15 @@ class SavedBrowserSearchesDialogFragment : AnalyticsDialogFragment() {
)
return AlertDialog
.Builder(requireContext())
- .title(text = resources.getString(R.string.card_browser_list_my_searches_title))
- .negativeButton(R.string.dialog_cancel)
+ .title(text = resources.getString(CommonString.card_browser_list_my_searches_title))
+ .negativeButton(CommonString.dialog_cancel)
.apply { customListAdapter(adapter) }
.create()
}
private fun removeSearch(searchName: String) {
AlertDialog.Builder(requireActivity()).show {
- message(text = resources.getString(R.string.card_browser_list_my_searches_remove_content, searchName))
+ message(text = resources.getString(CommonString.card_browser_list_my_searches_remove_content, searchName))
positiveButton(android.R.string.ok) {
parentFragmentManager.setFragmentResult(
REQUEST_SAVED_SEARCH_ACTION,
@@ -84,7 +84,7 @@ class SavedBrowserSearchesDialogFragment : AnalyticsDialogFragment() {
)
dialog?.dismiss() // Dismiss the root dialog
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SchedulerUpgradeDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SchedulerUpgradeDialog.kt
index 2016b306b183..69761a278b69 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SchedulerUpgradeDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SchedulerUpgradeDialog.kt
@@ -8,6 +8,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.android.AdaptionUtil
import com.ichi2.anki.utils.openUrl
@@ -31,10 +32,10 @@ class SchedulerUpgradeDialog(
fun showDialog() {
val dialog =
MaterialAlertDialogBuilder(activity)
- .setTitle(R.string.scheduler_upgrade_required_dialog_title)
+ .setTitle(CommonString.scheduler_upgrade_required_dialog_title)
.setMessage(TR.schedulingUpdateRequired())
- .setPositiveButton(R.string.dialog_ok, null)
- .setNegativeButton(R.string.dialog_cancel, null)
+ .setPositiveButton(CommonString.dialog_ok, null)
+ .setNegativeButton(CommonString.dialog_cancel, null)
.apply {
if (AdaptionUtil.hasWebBrowser(activity)) {
setNeutralButton(TR.schedulingUpdateMoreInfoButton(), null)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SimpleMessageDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SimpleMessageDialog.kt
index 2c72ce2cbe34..c073e3fa8c04 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SimpleMessageDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SimpleMessageDialog.kt
@@ -8,6 +8,7 @@ import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentActivity
import com.ichi2.anki.AnkiDroidApp
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.R
import com.ichi2.anki.utils.ext.dismissAllDialogFragments
@@ -19,7 +20,7 @@ class SimpleMessageDialog : AsyncDialogFragment() {
return AlertDialog.Builder(requireContext()).create {
setTitle(notificationTitle)
setMessage(notificationMessage)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
activity?.dismissSimpleMessageDialog(requireArguments().getBoolean(ARG_RELOAD))
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt
index dd03a819a4d0..741b8cc98193 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/SyncErrorDialog.kt
@@ -4,17 +4,23 @@
package com.ichi2.anki.dialogs
import android.app.Dialog
+import android.content.ActivityNotFoundException
+import android.content.Intent
import android.os.Bundle
import android.os.Message
+import android.provider.Settings
import androidx.annotation.CheckResult
import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.ConflictResolution
import com.ichi2.anki.R
+import com.ichi2.anki.common.time.TimeManager
import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_CONNECTION_ERROR
import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_MEDIA_SYNC_ERROR
import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_BASIC_CHECK_ERROR
+import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CLOCK_OFF
import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL
import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE
import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_SYNC_CONFLICT_RESOLUTION
@@ -26,7 +32,11 @@ import com.ichi2.anki.dialogs.SyncErrorDialog.Type.DIALOG_USER_NOT_LOGGED_IN_SYN
import com.ichi2.anki.ui.internationalization.sentenceCase
import com.ichi2.anki.utils.ext.dismissAllDialogFragments
import com.ichi2.anki.utils.openUrl
+import com.ichi2.utils.positiveButton
+import com.ichi2.utils.setupEnterKeyHandler
import com.ichi2.utils.titleWithHelpIcon
+import timber.log.Timber
+import java.text.DateFormat
class SyncErrorDialog : AsyncDialogFragment() {
interface SyncErrorDialogListener {
@@ -65,16 +75,16 @@ class SyncErrorDialog : AsyncDialogFragment() {
.setIcon(R.drawable.ic_sync_problem)
.setPositiveButton(TR.sentenceCase.logIn) { _, _ ->
requireSyncErrorDialogListener().loginToSyncServer()
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
DIALOG_CONNECTION_ERROR -> {
// Connection error; allow user to retry or cancel
dialog
.setIcon(R.drawable.ic_sync_problem)
- .setPositiveButton(R.string.retry) { _, _ ->
+ .setPositiveButton(CommonString.retry) { _, _ ->
syncAndDismissAllDialogFragments()
- }.setNegativeButton(R.string.dialog_cancel) { _, _ ->
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ ->
activity?.dismissAllDialogFragments()
}.create()
}
@@ -82,15 +92,15 @@ class SyncErrorDialog : AsyncDialogFragment() {
// Sync conflict; allow user to cancel, or choose between local and remote versions
dialog
.titleWithHelpIcon(
- text = getString(R.string.sync_conflict_title_new),
+ text = getString(CommonString.sync_conflict_title_new),
startIcon = R.drawable.ic_sync_problem,
) {
requireContext().openUrl(getString(R.string.link_sync_conflict_help))
- }.setPositiveButton(R.string.sync_conflict_keep_local_new) { _, _ ->
+ }.setPositiveButton(CommonString.sync_conflict_keep_local_new) { _, _ ->
requireSyncErrorDialogListener().showSyncErrorDialog(DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL)
- }.setNegativeButton(R.string.sync_conflict_keep_remote_new) { _, _ ->
+ }.setNegativeButton(CommonString.sync_conflict_keep_remote_new) { _, _ ->
requireSyncErrorDialogListener().showSyncErrorDialog(DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE)
- }.setNeutralButton(R.string.dialog_cancel) { _, _ ->
+ }.setNeutralButton(CommonString.dialog_cancel) { _, _ ->
activity?.dismissAllDialogFragments()
}.create()
}
@@ -98,44 +108,44 @@ class SyncErrorDialog : AsyncDialogFragment() {
// Confirmation before pushing local collection to server after sync conflict
dialog
.setIcon(R.drawable.ic_sync_problem)
- .setPositiveButton(R.string.dialog_positive_replace) { _, _ ->
+ .setPositiveButton(CommonString.dialog_positive_replace) { _, _ ->
syncAndDismissAllDialogFragments(ConflictResolution.FULL_UPLOAD)
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE -> {
// Confirmation before overwriting local collection with server collection after sync conflict
dialog
.setIcon(R.drawable.ic_sync_problem)
- .setPositiveButton(R.string.dialog_positive_replace) { _, _ ->
+ .setPositiveButton(CommonString.dialog_positive_replace) { _, _ ->
syncAndDismissAllDialogFragments(ConflictResolution.FULL_DOWNLOAD)
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
DIALOG_SYNC_SANITY_ERROR -> {
// Sync sanity check error; allow user to cancel, or choose between local and remote versions
dialog
- .setPositiveButton(R.string.sync_sanity_local) { _, _ ->
+ .setPositiveButton(CommonString.sync_sanity_local) { _, _ ->
requireSyncErrorDialogListener().showSyncErrorDialog(DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL)
- }.setNeutralButton(R.string.sync_sanity_remote) { _, _ ->
+ }.setNeutralButton(CommonString.sync_sanity_remote) { _, _ ->
requireSyncErrorDialogListener().showSyncErrorDialog(DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE)
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL -> {
// Confirmation before pushing local collection to server after sanity check error
dialog
- .setPositiveButton(R.string.dialog_positive_replace) { _, _ ->
+ .setPositiveButton(CommonString.dialog_positive_replace) { _, _ ->
syncAndDismissAllDialogFragments(ConflictResolution.FULL_UPLOAD)
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE -> {
// Confirmation before overwriting local collection with server collection after sanity check error
dialog
- .setPositiveButton(R.string.dialog_positive_replace) { _, _ ->
+ .setPositiveButton(CommonString.dialog_positive_replace) { _, _ ->
syncAndDismissAllDialogFragments(ConflictResolution.FULL_DOWNLOAD)
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
DIALOG_MEDIA_SYNC_ERROR -> {
@@ -143,13 +153,13 @@ class SyncErrorDialog : AsyncDialogFragment() {
.setPositiveButton(TR.sentenceCase.checkMediaAction) { _, _ ->
requireSyncErrorDialogListener().mediaCheck()
activity?.dismissAllDialogFragments()
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
DIALOG_SYNC_CORRUPT_COLLECTION -> {
dialog
- .setPositiveButton(R.string.dialog_ok) { _, _ -> }
- .setNegativeButton(R.string.help) { _, _ ->
+ .setPositiveButton(CommonString.dialog_ok) { _, _ -> }
+ .setNegativeButton(CommonString.help) { _, _ ->
requireContext().openUrl(R.string.repair_deck)
}.setCancelable(false)
.create()
@@ -159,21 +169,41 @@ class SyncErrorDialog : AsyncDialogFragment() {
.setPositiveButton(TR.sentenceCase.checkDatabase) { _, _ ->
requireSyncErrorDialogListener().integrityCheck()
activity?.dismissAllDialogFragments()
- }.setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ }.setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
.create()
}
+ DIALOG_SYNC_CLOCK_OFF -> {
+ dialog
+ .setPositiveButton(CommonString.open_settings, null)
+ .setNegativeButton(CommonString.dialog_cancel, null)
+ .create()
+ .apply {
+ // Override the default handler so the dialog remains open if no date-settings activity exists.
+ setOnShowListener {
+ positiveButton.setOnClickListener {
+ try {
+ startActivity(Intent(Settings.ACTION_DATE_SETTINGS))
+ this@SyncErrorDialog.dismiss()
+ } catch (e: ActivityNotFoundException) {
+ Timber.w(e, "No app can show date and time settings")
+ }
+ }
+ }
+ setupEnterKeyHandler()
+ }
+ }
}
}
private val title: String
get() =
when (dialogType) {
- DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(R.string.not_logged_in_title)
+ DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(CommonString.not_logged_in_title)
DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL, DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE ->
res().getString(
- R.string.sync_conflict_replace_title,
+ CommonString.sync_conflict_replace_title,
)
- DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(R.string.sync_conflict_title_new)
+ DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(CommonString.sync_conflict_title_new)
DIALOG_CONNECTION_ERROR,
DIALOG_SYNC_SANITY_ERROR,
DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL,
@@ -181,7 +211,8 @@ class SyncErrorDialog : AsyncDialogFragment() {
DIALOG_MEDIA_SYNC_ERROR,
DIALOG_SYNC_CORRUPT_COLLECTION,
DIALOG_SYNC_BASIC_CHECK_ERROR,
- -> res().getString(R.string.sync_error)
+ -> res().getString(CommonString.sync_error)
+ DIALOG_SYNC_CLOCK_OFF -> res().getString(CommonString.vague_error)
}
/**
@@ -192,7 +223,7 @@ class SyncErrorDialog : AsyncDialogFragment() {
override val notificationTitle: String
get() {
return if (dialogType == DIALOG_USER_NOT_LOGGED_IN_SYNC) {
- res().getString(R.string.sync_error)
+ res().getString(CommonString.sync_error)
} else {
title
}
@@ -201,23 +232,29 @@ class SyncErrorDialog : AsyncDialogFragment() {
private val message: String?
get() =
when (dialogType) {
- DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(R.string.login_create_account_message)
- DIALOG_CONNECTION_ERROR -> res().getString(R.string.connection_error_message)
- DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(R.string.sync_conflict_message_new)
+ DIALOG_USER_NOT_LOGGED_IN_SYNC -> res().getString(CommonString.login_create_account_message)
+ DIALOG_CONNECTION_ERROR -> res().getString(CommonString.connection_error_message)
+ DIALOG_SYNC_CONFLICT_RESOLUTION -> res().getString(CommonString.sync_conflict_message_new)
DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_LOCAL, DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_LOCAL ->
res().getString(
- R.string.sync_conflict_local_confirm_new,
+ CommonString.sync_conflict_local_confirm_new,
)
DIALOG_SYNC_CONFLICT_CONFIRM_KEEP_REMOTE, DIALOG_SYNC_SANITY_ERROR_CONFIRM_KEEP_REMOTE ->
res().getString(
- R.string.sync_conflict_remote_confirm_new,
+ CommonString.sync_conflict_remote_confirm_new,
)
DIALOG_SYNC_CORRUPT_COLLECTION -> {
val syncMessage = requireArguments().getString(DIALOG_MESSAGE_KEY)
val repairUrl = res().getString(R.string.repair_deck)
- val dialogMessage = res().getString(R.string.sync_corrupt_database, repairUrl)
+ val dialogMessage = res().getString(CommonString.sync_corrupt_database, repairUrl)
joinSyncMessages(dialogMessage, syncMessage)
}
+ DIALOG_SYNC_CLOCK_OFF ->
+ res().getString(
+ CommonString.sync_clock_off_with_current_time,
+ requireArguments().getString(DIALOG_MESSAGE_KEY).orEmpty(),
+ DateFormat.getDateTimeInstance().format(TimeManager.time.currentDate),
+ )
else -> requireArguments().getString(DIALOG_MESSAGE_KEY)
}
@@ -247,7 +284,7 @@ class SyncErrorDialog : AsyncDialogFragment() {
override val notificationMessage: String?
get() {
return if (dialogType == DIALOG_USER_NOT_LOGGED_IN_SYNC) {
- res().getString(R.string.not_logged_in_title)
+ res().getString(CommonString.not_logged_in_title)
} else {
message
}
@@ -281,6 +318,7 @@ class SyncErrorDialog : AsyncDialogFragment() {
DIALOG_MEDIA_SYNC_ERROR(8),
DIALOG_SYNC_CORRUPT_COLLECTION(9),
DIALOG_SYNC_BASIC_CHECK_ERROR(10),
+ DIALOG_SYNC_CLOCK_OFF(11),
;
companion object {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsPlaybackErrorDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsPlaybackErrorDialog.kt
index 00146fa4c9a4..00bc25dba0a8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsPlaybackErrorDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsPlaybackErrorDialog.kt
@@ -20,6 +20,7 @@ import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.FragmentManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.TtsVoices
import com.ichi2.anki.common.crashreporting.CrashReportService
@@ -37,13 +38,13 @@ object TtsPlaybackErrorDialog {
Timber.i("Dialog is shown to guide users correctly to troubleshoot the Tts error: Missing voice error")
activity.runOnUiThread {
AlertDialog.Builder(activity).show {
- setTitle(activity.getString(R.string.tts_error_dialog_title))
- setMessage(activity.getString(R.string.tts_error_dialog_reason_text, TtsVoices.ttsEngine, ttsTag?.lang))
- setNegativeButton(context.getString(R.string.tts_error_dialog_change_button_text)) { _, _ -> openSettings(activity) }
+ setTitle(activity.getString(CommonString.tts_error_dialog_title))
+ setMessage(activity.getString(CommonString.tts_error_dialog_reason_text, TtsVoices.ttsEngine, ttsTag?.lang))
+ setNegativeButton(context.getString(CommonString.tts_error_dialog_change_button_text)) { _, _ -> openSettings(activity) }
setPositiveButton(
- activity.getString(R.string.tts_error_dialog_supported_voices_button_text),
+ activity.getString(CommonString.tts_error_dialog_supported_voices_button_text),
) { _, _ -> showVoicesDialog(fragmentManager) }
- setNeutralButton(context.getString(R.string.help)) { _, _ ->
+ setNeutralButton(context.getString(CommonString.help)) { _, _ ->
activity.openUrl(R.string.link_faq_tts)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsVoicesDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsVoicesDialogFragment.kt
index a4db1ed21b85..dbd3354e8ec4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsVoicesDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TtsVoicesDialogFragment.kt
@@ -21,6 +21,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.ichi2.anki.AndroidTtsVoice
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.anki.databinding.DialogTtsVoicesBinding
@@ -114,7 +115,7 @@ class TtsVoicesDialogFragment : DialogFragment(R.layout.dialog_tts_voices) {
)
} catch (e: ActivityNotFoundException) {
Timber.w(e)
- showThemedToast(requireContext(), R.string.tts_voices_failed_opening_tts_system_settings, shortLength = true)
+ showThemedToast(requireContext(), CommonString.tts_voices_failed_opening_tts_system_settings, shortLength = true)
}
}
@@ -146,15 +147,15 @@ class TtsVoicesDialogFragment : DialogFragment(R.layout.dialog_tts_voices) {
}
viewModel.uninstalledVoicePlayed.observe { voice ->
- dialog?.window?.decorView?.showSnackbar(R.string.tts_voices_selected_voice_should_be_installed) {
- setAction(R.string.tts_voices_use_selected_voice_without_install) { viewModel.copyToClipboard(voice) }
+ dialog?.window?.decorView?.showSnackbar(CommonString.tts_voices_selected_voice_should_be_installed) {
+ setAction(CommonString.tts_voices_use_selected_voice_without_install) { viewModel.copyToClipboard(voice) }
}
}
viewModel.ttsPlaybackErrorFlow.observe {
val string = it.localizedErrorMessage(requireContext())
dialog?.window?.decorView?.showSnackbar(string) {
- setAction(R.string.help) {
+ setAction(CommonString.help) {
// TODO: Should do this in ViewModel, but we need an Activity
requireContext().openUrl(R.string.link_faq_tts)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/WhiteBoardWidthDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/WhiteBoardWidthDialog.kt
index 6ec7e8580845..412beb1c7bf4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/WhiteBoardWidthDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/WhiteBoardWidthDialog.kt
@@ -22,7 +22,7 @@ import android.widget.LinearLayout
import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.appcompat.app.AlertDialog
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.ui.FixedTextView
import com.ichi2.utils.negativeButton
import com.ichi2.utils.positiveButton
@@ -77,11 +77,11 @@ class WhiteBoardWidthDialog(
),
)
AlertDialog.Builder(context).show {
- title(R.string.whiteboard_stroke_width)
- positiveButton(R.string.save) {
+ title(CommonString.whiteboard_stroke_width)
+ positiveButton(CommonString.save) {
onStrokeWidthChanged?.accept(wbStrokeWidth)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
setView(layout)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt
index fa5d6b7be798..b8a2216b11c3 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.kt
@@ -33,11 +33,15 @@ import anki.scheduler.CustomStudyRequest.Cram.CramKind
import anki.scheduler.copy
import anki.scheduler.customStudyRequest
import anki.search.SearchNode
+import anki.search.searchNode
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.asyncIO
+import com.ichi2.anki.browser.search.CardState
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.common.preferences.sharedPrefs
import com.ichi2.anki.common.utils.annotation.KotlinCleanup
@@ -191,7 +195,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
if (item.checkAvailability != null) {
val defaults = withProgress { deferredDefaults.await() }
if (!item.checkAvailability(defaults)) {
- showSnackbar(getString((R.string.studyoptions_no_cards_due)))
+ showSnackbar(getString((CommonString.studyoptions_no_cards_due)))
return
}
}
@@ -286,7 +290,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
binding.detailsText1.text = text1
// 'review ahead' has a dialog title, so the empty label would only add a blank line
- binding.detailsText1.isVisible = contextMenuOption != STUDY_AHEAD
+ binding.detailsText1.isVisible = contextMenuOption != STUDY_AHEAD && contextMenuOption != STUDY_PREVIEW
binding.detailsText2.text = text2
binding.cardsStateSelectorLayout.isVisible = contextMenuOption == STUDY_TAGS
@@ -326,7 +330,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
if (contextMenuOption == EXTEND_NEW || contextMenuOption == EXTEND_REV) {
inputType = EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_FLAG_SIGNED
}
- if (contextMenuOption == STUDY_AHEAD) {
+ if (contextMenuOption == STUDY_AHEAD || contextMenuOption == STUDY_PREVIEW) {
inputType = EditorInfo.TYPE_CLASS_NUMBER
filters += arrayOf(InputFilter.LengthFilter(5), NonLeadingZeroInputFilter)
setSuffixText(defaultValue.toInt())
@@ -335,10 +339,10 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
val positiveBtnLabel =
if (contextMenuOption == STUDY_TAGS) {
TR.sentenceCase.chooseTags
- } else if (contextMenuOption == STUDY_AHEAD) {
- getString(R.string.dialog_positive_create)
+ } else if (contextMenuOption == STUDY_AHEAD || contextMenuOption == STUDY_PREVIEW) {
+ getString(CommonString.dialog_positive_create)
} else {
- getString(R.string.dialog_ok)
+ getString(CommonString.dialog_ok)
}
// Set material dialog parameters
@@ -349,7 +353,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
AlertDialog
.Builder(requireActivity())
.apply {
- if (contextMenuOption == STUDY_AHEAD) {
+ if (contextMenuOption == STUDY_AHEAD || contextMenuOption == STUDY_PREVIEW) {
title(text = contextMenuOption.getTitle(resources))
}
}.customView(
@@ -359,7 +363,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
paddingTop = verticalPadding,
paddingBottom = verticalPadding,
).positiveButton(text = positiveBtnLabel, click = null)
- .negativeButton(R.string.dialog_cancel) {
+ .negativeButton(CommonString.dialog_cancel) {
requireActivity().dismissAllDialogFragments()
}.create()
@@ -421,7 +425,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
}
launchCustomStudy(contextMenuOption, n)
}
- if (contextMenuOption == STUDY_AHEAD) {
+ if (contextMenuOption == STUDY_AHEAD || contextMenuOption == STUDY_PREVIEW) {
// the stored default may match no cards
searchJob = launchCatchingTask { updateCreateButtonState(dialog, userInputValue) }
}
@@ -429,7 +433,7 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
binding.detailsEditText2.doAfterTextChanged {
val value = userInputValue
- if (contextMenuOption != STUDY_AHEAD) {
+ if (contextMenuOption != STUDY_AHEAD && contextMenuOption != STUDY_PREVIEW) {
dialog.positiveButton.isEnabled = value != null && value != 0
return@doAfterTextChanged
}
@@ -445,20 +449,40 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
/** Sets the suffix of the days input: `[1] day`, `[3] days` */
private fun setSuffixText(days: Int) {
- binding.detailsEditText2Layout.suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, days)
+ binding.detailsEditText2Layout.suffixText = resources.getQuantityString(CommonPlurals.set_due_date_label_suffix, days)
}
+ /**
+ * Whether the deck has new cards added in the last [days] days.
+ *
+ * Upstream: https://github.com/ankitects/anki/blob/618c1787c83ac9baace0527a2f572631b86b7aee/qt/aqt/customstudy.py
+ */
+ private suspend fun hasPreviewCards(days: Int): Boolean =
+ withCol {
+ val search =
+ listOf(
+ searchNode { deck = decks.name(viewModel.deckId) },
+ CardState.New.toSearchNode(),
+ searchNode { addedInDays = days },
+ )
+ findCards(buildSearchString(search)).isNotEmpty()
+ }
+
/** Enables 'Create' only if some cards would be reviewed ahead by [days] */
private suspend fun updateCreateButtonState(
dialog: AlertDialog,
days: Int?,
) {
if (days == null || days == 0) {
- binding.detailsEditText2Layout.error = if (days == 0) getString(R.string.minimum_value_is, 1) else null
+ binding.detailsEditText2Layout.error = if (days == 0) getString(CommonString.minimum_value_is, 1) else null
dialog.positiveButton.isEnabled = false
return
}
- val hasCards = hasCardsDueWithin(days)
+ val hasCards =
+ when (selectedSubDialog) {
+ STUDY_PREVIEW -> hasPreviewCards(days)
+ else -> hasCardsDueWithin(days)
+ }
binding.detailsEditText2Layout.error = if (hasCards) null else TR.customStudyNoCardsMatchedTheCriteriaYou()
dialog.positiveButton.isEnabled = hasCards
}
@@ -566,12 +590,12 @@ class CustomStudyDialog : AnalyticsDialogFragment() {
get() {
val res = resources
return when (selectedSubDialog) {
- EXTEND_NEW -> res.getString(R.string.custom_study_new_extend)
- EXTEND_REV -> res.getString(R.string.custom_study_rev_extend)
- STUDY_FORGOT -> res.getString(R.string.custom_study_forgotten)
- STUDY_AHEAD -> res.getString(R.string.custom_study_ahead_description)
- STUDY_PREVIEW -> res.getString(R.string.custom_study_preview)
- STUDY_TAGS -> res.getString(R.string.custom_study_tags)
+ EXTEND_NEW -> res.getString(CommonString.custom_study_new_extend)
+ EXTEND_REV -> res.getString(CommonString.custom_study_rev_extend)
+ STUDY_FORGOT -> res.getString(CommonString.custom_study_forgotten)
+ STUDY_AHEAD -> res.getString(CommonString.custom_study_ahead_description)
+ STUDY_PREVIEW -> res.getString(CommonString.custom_study_preview)
+ STUDY_TAGS -> res.getString(CommonString.custom_study_tags)
null -> ""
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/TagLimitFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/TagLimitFragment.kt
index 1fa0519bf8f4..e33deea64214 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/TagLimitFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/TagLimitFragment.kt
@@ -13,7 +13,7 @@ import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.lifecycleScope
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.databinding.FragmentTagLimitBinding
import com.ichi2.anki.dialogs.customstudy.IncludedExcludedTagsAdapter.TagsSelectionMode.Exclude
import com.ichi2.anki.dialogs.customstudy.IncludedExcludedTagsAdapter.TagsSelectionMode.Include
@@ -69,8 +69,8 @@ class TagLimitFragment : DialogFragment() {
.Builder(requireContext())
.title(text = TR.sentenceCase.selectiveStudy)
.customView(binding.root)
- .negativeButton(R.string.dialog_cancel)
- .positiveButton(R.string.dialog_ok, null)
+ .negativeButton(CommonString.dialog_cancel)
+ .positiveButton(CommonString.dialog_ok, null)
.create()
var allowSubmit = true
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpDialog.kt
index ff5fc454161c..8e0ad1126150 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpDialog.kt
@@ -14,6 +14,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.BuildConfig
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.ankiActivity
@@ -119,7 +120,7 @@ class HelpDialog : AnalyticsDialogFragment() {
return HelpDialog().apply {
arguments =
Bundle().apply {
- putInt(ARG_MENU_TITLE, R.string.help)
+ putInt(ARG_MENU_TITLE, CommonString.help)
putParcelableArray(ARG_MENU_ITEMS, mainHelpMenuItems)
}
}
@@ -132,7 +133,7 @@ class HelpDialog : AnalyticsDialogFragment() {
return HelpDialog().apply {
arguments =
Bundle().apply {
- putInt(ARG_MENU_TITLE, R.string.help_title_privacy)
+ putInt(ARG_MENU_TITLE, CommonString.help_title_privacy)
putParcelableArray(ARG_MENU_ITEMS, privacyItems.toTypedArray())
}
}
@@ -150,7 +151,7 @@ class HelpDialog : AnalyticsDialogFragment() {
return HelpDialog().apply {
arguments =
Bundle().apply {
- putInt(ARG_MENU_TITLE, R.string.help_title_support_ankidroid)
+ putInt(ARG_MENU_TITLE, CommonString.help_title_support_ankidroid)
putParcelableArray(ARG_MENU_ITEMS, actualMenuItems.toTypedArray())
}
}
@@ -214,25 +215,25 @@ class HelpPageFragment : Fragment(R.layout.fragment_help_page) {
internal val mainHelpMenuItems =
arrayOf(
HelpItem(
- titleResId = R.string.help_title_using_ankidroid,
+ titleResId = CommonString.help_title_using_ankidroid,
iconResId = R.drawable.ic_manual_black_24dp,
analyticsId = LinkAction.OPENED_USING_ANKIDROID,
id = 1,
),
HelpItem(
- titleResId = R.string.help_title_get_help,
+ titleResId = CommonString.help_title_get_help,
iconResId = R.drawable.ic_help_black_24dp,
analyticsId = LinkAction.OPENED_GET_HELP,
id = 2,
),
HelpItem(
- titleResId = R.string.help_title_community,
+ titleResId = CommonString.help_title_community,
iconResId = R.drawable.ic_people_black_24dp,
analyticsId = LinkAction.OPENED_COMMUNITY,
id = 3,
),
HelpItem(
- titleResId = R.string.help_title_privacy,
+ titleResId = CommonString.help_title_privacy,
iconResId = R.drawable.ic_baseline_privacy_tip_24,
analyticsId = LinkAction.OPENED_PRIVACY,
id = 4,
@@ -244,42 +245,42 @@ internal val mainHelpMenuItems =
internal val supportMenuItems =
arrayOf(
HelpItem(
- titleResId = R.string.help_item_support_opencollective_donate,
+ titleResId = CommonString.help_item_support_opencollective_donate,
iconResId = R.drawable.ic_round_favorite_24,
analyticsId = LinkAction.OPENED_DONATE,
id = 5,
action = OpenUrlResource(R.string.link_opencollective_donate),
),
HelpItem(
- titleResId = R.string.multimedia_editor_trans_translate,
+ titleResId = CommonString.multimedia_editor_trans_translate,
iconResId = R.drawable.ic_language_black_24dp,
analyticsId = LinkAction.OPENED_TRANSLATE,
id = 6,
action = OpenUrlResource(R.string.link_translation),
),
HelpItem(
- titleResId = R.string.help_item_support_develop_ankidroid,
+ titleResId = CommonString.help_item_support_develop_ankidroid,
iconResId = R.drawable.ic_build_black_24,
analyticsId = LinkAction.OPENED_DEVELOP,
id = 7,
action = OpenUrlResource(R.string.link_ankidroid_development_guide),
),
HelpItem(
- titleResId = R.string.help_item_support_rate_ankidroid,
+ titleResId = CommonString.help_item_support_rate_ankidroid,
iconResId = R.drawable.ic_star_black_24,
analyticsId = LinkAction.OPENED_RATE,
id = 8,
action = Rate,
),
HelpItem(
- titleResId = R.string.help_item_support_other_ankidroid,
+ titleResId = CommonString.help_item_support_other_ankidroid,
iconResId = R.drawable.ic_help_black_24dp,
analyticsId = LinkAction.OPENED_OTHER,
id = 9,
action = OpenUrlResource(R.string.link_contribution),
),
HelpItem(
- titleResId = R.string.send_feedback,
+ titleResId = CommonString.send_feedback,
iconResId = R.drawable.ic_email_black_24dp,
analyticsId = LinkAction.OPENED_SEND_FEEDBACK,
id = 10,
@@ -292,7 +293,7 @@ internal val supportMenuItems =
internal val childHelpMenuItems =
arrayOf(
HelpItem(
- titleResId = R.string.help_item_ankidroid_manual,
+ titleResId = CommonString.help_item_ankidroid_manual,
iconResId = R.drawable.ic_manual_black_24dp,
analyticsId = LinkAction.OPENED_ANKIDROID_MANUAL,
id = 100,
@@ -300,7 +301,7 @@ internal val childHelpMenuItems =
action = OpenUrl(AnkiDroidApp.manualUrl),
),
HelpItem(
- titleResId = R.string.help_item_anki_manual,
+ titleResId = CommonString.help_item_anki_manual,
iconResId = R.drawable.ic_manual_black_24dp,
analyticsId = LinkAction.OPENED_ANKI_MANUAL,
id = 101,
@@ -308,7 +309,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_anki_manual),
),
HelpItem(
- titleResId = R.string.help_item_ankidroid_faq,
+ titleResId = CommonString.help_item_ankidroid_faq,
iconResId = R.drawable.ic_help_black_24dp,
analyticsId = LinkAction.OPENED_ANKIDROID_FAQ,
id = 102,
@@ -316,7 +317,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_ankidroid_faq),
),
HelpItem(
- titleResId = R.string.help_item_mailing_list,
+ titleResId = CommonString.help_item_mailing_list,
iconResId = R.drawable.ic_email_black_24dp,
analyticsId = LinkAction.OPENED_MAILING_LIST,
id = 200,
@@ -324,7 +325,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_forum),
),
HelpItem(
- titleResId = R.string.help_item_report_bug,
+ titleResId = CommonString.help_item_report_bug,
iconResId = R.drawable.ic_bug_report_black_24dp,
analyticsId = LinkAction.OPENED_REPORT_BUG,
id = 201,
@@ -332,7 +333,7 @@ internal val childHelpMenuItems =
action = OpenUrl(AnkiDroidApp.feedbackUrl),
),
HelpItem(
- titleResId = R.string.help_title_send_exception,
+ titleResId = CommonString.help_title_send_exception,
iconResId = R.drawable.ic_round_assignment_24,
analyticsId = LinkAction.EXCEPTION_REPORT,
id = 202,
@@ -340,7 +341,7 @@ internal val childHelpMenuItems =
action = SendReport,
),
HelpItem(
- titleResId = R.string.help_item_anki_forums,
+ titleResId = CommonString.help_item_anki_forums,
iconResId = R.drawable.ic_forum_black_24dp,
analyticsId = LinkAction.OPENED_ANKI_FORUMS,
id = 300,
@@ -348,7 +349,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_anki_forum),
),
HelpItem(
- titleResId = R.string.help_item_mailing_list,
+ titleResId = CommonString.help_item_mailing_list,
iconResId = R.drawable.ic_email_black_24dp,
analyticsId = LinkAction.OPENED_MAILING_LIST,
id = 301,
@@ -356,7 +357,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_forum),
),
HelpItem(
- titleResId = R.string.help_item_reddit,
+ titleResId = CommonString.help_item_reddit,
iconResId = R.drawable.ic_link,
analyticsId = LinkAction.OPENED_REDDIT,
id = 302,
@@ -364,7 +365,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_reddit),
),
HelpItem(
- titleResId = R.string.help_item_discord,
+ titleResId = CommonString.help_item_discord,
iconResId = R.drawable.ic_link,
analyticsId = LinkAction.OPENED_DISCORD,
id = 303,
@@ -372,7 +373,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_discord),
),
HelpItem(
- titleResId = R.string.help_item_facebook,
+ titleResId = CommonString.help_item_facebook,
iconResId = R.drawable.ic_link,
analyticsId = LinkAction.OPENED_FACEBOOK,
id = 304,
@@ -380,7 +381,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_facebook),
),
HelpItem(
- titleResId = R.string.help_item_twitter,
+ titleResId = CommonString.help_item_twitter,
iconResId = R.drawable.ic_link,
analyticsId = LinkAction.OPENED_TWITTER,
id = 305,
@@ -388,7 +389,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_twitter),
),
HelpItem(
- titleResId = R.string.help_item_ankidroid_privacy_policy,
+ titleResId = CommonString.help_item_ankidroid_privacy_policy,
iconResId = R.drawable.ic_baseline_policy_24,
analyticsId = LinkAction.OPENED_ANKIDROID_PRIVACY_POLICY,
id = 400,
@@ -396,7 +397,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_ankidroid_privacy_policy),
),
HelpItem(
- titleResId = R.string.help_item_ankiweb_privacy_policy,
+ titleResId = CommonString.help_item_ankiweb_privacy_policy,
iconResId = R.drawable.ic_baseline_policy_24,
analyticsId = LinkAction.OPENED_ANKIWEB_PRIVACY_POLICY,
id = 401,
@@ -404,7 +405,7 @@ internal val childHelpMenuItems =
action = OpenUrlResource(R.string.link_ankiweb_privacy_policy),
),
HelpItem(
- titleResId = R.string.help_item_ankiweb_terms_and_conditions,
+ titleResId = CommonString.help_item_ankiweb_terms_and_conditions,
iconResId = R.drawable.ic_baseline_description_24,
analyticsId = LinkAction.OPENED_ANKIWEB_TERMS_AND_CONDITIONS,
id = 402,
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpItemActionsDispatcher.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpItemActionsDispatcher.kt
index 9d363c5a8740..f637e6c35db8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpItemActionsDispatcher.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/help/HelpItemActionsDispatcher.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.dialogs.help
import androidx.annotation.StringRes
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.AnkiDroidApp
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.android.AdaptionUtil
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.snackbar.showSnackbar
@@ -49,14 +49,14 @@ class AnkiActivityHelpActionsDispatcher(
override fun onSendReport() {
if (AdaptionUtil.isUserATestClient) {
- ankiActivity.showSnackbar(ankiActivity.getString(R.string.user_is_a_robot))
+ ankiActivity.showSnackbar(ankiActivity.getString(CommonString.user_is_a_robot))
} else {
val wasReportSent = CrashReportService.sendReport(ankiActivity)
if (!wasReportSent) {
- ankiActivity.showSnackbar(ankiActivity.getString(R.string.help_dialog_exception_report_debounce))
+ ankiActivity.showSnackbar(ankiActivity.getString(CommonString.help_dialog_exception_report_debounce))
return
}
- ankiActivity.showSnackbar(ankiActivity.getString(R.string.help_dialog_exception_report_sent))
+ ankiActivity.showSnackbar(ankiActivity.getString(CommonString.help_dialog_exception_report_sent))
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsArrayAdapter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsArrayAdapter.kt
index 7ee6148ea54e..7efa15552d94 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsArrayAdapter.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsArrayAdapter.kt
@@ -11,6 +11,7 @@ import android.widget.ImageButton
import android.widget.LinearLayout
import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.RecyclerView
+import com.ichi2.anki.CommonString
import com.ichi2.anki.OnContextAndLongClickListener
import com.ichi2.anki.OnContextAndLongClickListener.Companion.setOnContextAndLongClickListener
import com.ichi2.anki.R
@@ -284,7 +285,8 @@ class TagsArrayAdapter(
// do not add padding if there is no visible nested tag
binding.expandButton.visibility = View.GONE
}
- binding.expandButton.contentDescription = holder.itemView.context.getString(R.string.expand_tag, holder.node.tag.replace("::", " "))
+ binding.expandButton.contentDescription =
+ holder.itemView.context.getString(CommonString.expand_tag, holder.node.tag.replace("::", " "))
binding.textView.text = TagsUtil.getTagParts(holder.node.tag).last()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsDialog.kt
index 62627511d88c..24bab21cc278 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/tags/TagsDialog.kt
@@ -31,6 +31,7 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.OnContextAndLongClickListener
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
@@ -173,9 +174,9 @@ class TagsDialog : AnalyticsDialogFragment {
val positiveText =
if (type == DialogType.EDIT_TAGS) {
- getString(R.string.dialog_confirm)
+ getString(CommonString.dialog_confirm)
} else {
- getString(R.string.select)
+ getString(CommonString.select)
}
val tagsListLayout: RecyclerView.LayoutManager = LinearLayoutManager(requireContext())
@@ -201,7 +202,7 @@ class TagsDialog : AnalyticsDialogFragment {
AlertDialog
.Builder(requireActivity())
.positiveButton(text = positiveText) { onPositiveButton() }
- .negativeButton(R.string.dialog_cancel)
+ .negativeButton(CommonString.dialog_cancel)
.customView(view = binding.root)
.create()
@@ -217,7 +218,7 @@ class TagsDialog : AnalyticsDialogFragment {
binding.progressText.text =
when (progress) {
TagsDialogViewModel.InitProgress.Processing ->
- getString(R.string.dialog_processing)
+ getString(CommonString.dialog_processing)
is TagsDialogViewModel.InitProgress.FetchingNoteTags ->
"${progress.noteNumber}/${progress.noteCount}"
TagsDialogViewModel.InitProgress.Finished -> null
@@ -303,7 +304,7 @@ class TagsDialog : AnalyticsDialogFragment {
private fun adjustToolbar(tagsDialogView: View) {
val toolbar: Toolbar = binding.toolbar.root
- val titleRes = if (type == DialogType.EDIT_TAGS) R.string.card_details_tags else R.string.studyoptions_limit_select_tags
+ val titleRes = if (type == DialogType.EDIT_TAGS) CommonString.card_details_tags else CommonString.studyoptions_limit_select_tags
toolbar.setTitle(titleRes)
val toolbarAddItem = toolbar.menu.findItem(R.id.tags_dialog_action_add)
@@ -326,7 +327,7 @@ class TagsDialog : AnalyticsDialogFragment {
toolbarSearchView = toolbarSearchItem?.actionView as AccessibleSearchView
val queryET = toolbarSearchView!!.findViewById(androidx.appcompat.R.id.search_src_text)
queryET.filters = arrayOf(addTagFilter)
- toolbarSearchView!!.queryHint = getString(R.string.filter_tags)
+ toolbarSearchView!!.queryHint = getString(CommonString.filter_tags)
toolbarSearchView!!.setOnQueryTextListener(
object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
@@ -353,7 +354,7 @@ class TagsDialog : AnalyticsDialogFragment {
true
}
if (type == DialogType.EDIT_TAGS) {
- toolbarSearchView!!.queryHint = getString(R.string.add_new_filter_tags)
+ toolbarSearchView!!.queryHint = getString(CommonString.add_new_filter_tags)
} else {
toolbarAddItem.isVisible = false
}
@@ -372,9 +373,9 @@ class TagsDialog : AnalyticsDialogFragment {
AlertDialog
.Builder(requireActivity())
.show {
- title(text = getString(R.string.add_tag))
- positiveButton(R.string.menu_add)
- negativeButton(R.string.dialog_cancel)
+ title(text = getString(CommonString.add_tag))
+ positiveButton(CommonString.menu_add)
+ negativeButton(CommonString.dialog_cancel)
setView(R.layout.dialog_generic_text_input)
}.input(
hint = TR.actionsName().dropLastWhile { it == ':' },
@@ -421,7 +422,7 @@ class TagsDialog : AnalyticsDialogFragment {
if (exists) {
textInputLayout?.error =
- getString(R.string.tag_already_exists)
+ getString(CommonString.tag_already_exists)
positiveButton.isEnabled = false
} else {
@@ -444,10 +445,10 @@ class TagsDialog : AnalyticsDialogFragment {
if (tags.add(tag)) {
binding.noTagsTextView.isVisible = false
tags.add(tag)
- val positiveText = (dialog as? AlertDialog)?.positiveButton?.text ?: getString(R.string.dialog_ok)
- feedbackText = getString(R.string.tag_editor_add_feedback, tag, positiveText)
+ val positiveText = (dialog as? AlertDialog)?.positiveButton?.text ?: getString(CommonString.dialog_ok)
+ feedbackText = getString(CommonString.tag_editor_add_feedback, tag, positiveText)
} else {
- feedbackText = getString(R.string.tag_editor_add_feedback_existing, tag)
+ feedbackText = getString(CommonString.tag_editor_add_feedback_existing, tag)
}
tags.check(tag)
tagsArrayAdapter?.sortData()
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/export/ExportDialogFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/export/ExportDialogFragment.kt
index 5e1bf59db56d..89148ffe841d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/export/ExportDialogFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/export/ExportDialogFragment.kt
@@ -25,6 +25,7 @@ import anki.notes.noteIds
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.browser.IdsFile
@@ -93,7 +94,7 @@ class ExportDialogFragment : AnalyticsDialogFragment() {
.Builder(requireActivity())
.setTitle(TR.actionsExport())
.setView(binding.root)
- .negativeButton(R.string.dialog_cancel)
+ .negativeButton(CommonString.dialog_cancel)
.positiveButton(text = TR.actionsExport()) {
val selectedIndex = binding.exportTypeSelector.selectedItemPosition
// just to be safe, if not exporting a collection and the decks spinner is not
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/filtered/FilteredDeckOptionsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/filtered/FilteredDeckOptionsFragment.kt
index c8551ff70dc4..462aac38ae88 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/filtered/FilteredDeckOptionsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/filtered/FilteredDeckOptionsFragment.kt
@@ -33,6 +33,7 @@ import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.destinations.BrowserDestination
import com.ichi2.anki.common.destinations.navigate
@@ -126,10 +127,10 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
if (state.throwable != null) {
// we can't recover from an initialization error so just exit
AlertDialog.Builder(requireActivity()).show {
- setTitle(R.string.import_title_error)
+ setTitle(CommonString.import_title_error)
message(text = state.throwable.toString())
cancelable(false)
- positiveButton(R.string.dialog_exit) { requireActivity().finish() }
+ positiveButton(CommonString.dialog_exit) { requireActivity().finish() }
}
} else {
binding.loadingIndicator.isVisible = true
@@ -151,9 +152,9 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
// show any errors we might have
if (state.throwable != null) {
AlertDialog.Builder(requireActivity()).show {
- title(R.string.import_title_error)
+ title(CommonString.import_title_error)
message(text = state.throwable.message)
- positiveButton(R.string.dialog_ok)
+ positiveButton(CommonString.dialog_ok)
setOnDismissListener { viewModel.clearError() }
}
return@collect
@@ -187,8 +188,8 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
binding.deckNameInput.setTextIfChanged(state.name)
binding.deckNameInputLayout.error =
when (state.nameInputError) {
- FilteredNameInputError.Empty -> getString(R.string.empty_cram_label)
- FilteredNameInputError.AlreadyExists -> getString(R.string.error_name_exists)
+ FilteredNameInputError.Empty -> getString(CommonString.empty_cram_label)
+ FilteredNameInputError.AlreadyExists -> getString(CommonString.error_name_exists)
null -> null
}
binding.checkBoxAllowEmpty.setCheckedIfChanged(state.allowEmpty)
@@ -201,7 +202,7 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
binding.filterLimitInput.setTextIfChanged(filter1State.limit)
binding.filterLimitInputLayout.error =
when (state.filter1State.error) {
- SearchInputError.Empty -> getString(R.string.empty_cram_label)
+ SearchInputError.Empty -> getString(CommonString.empty_cram_label)
SearchInputError.NotANumber -> TR.errorsInvalidInputEmpty()
null -> null
}
@@ -229,7 +230,7 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
binding.secondFilterLimitInput.setTextIfChanged(filter2State.limit)
binding.secondFilterLimitInputLayout.error =
when (state.filter2State.error) {
- SearchInputError.Empty -> getString(R.string.empty_cram_label)
+ SearchInputError.Empty -> getString(CommonString.empty_cram_label)
SearchInputError.NotANumber -> TR.errorsInvalidInputEmpty()
null -> null
}
@@ -333,9 +334,9 @@ class FilteredDeckOptionsFragment : Fragment(R.layout.fragment_filtered_deck_opt
binding.checkBoxAllowEmpty.text = TR.decksCreateEvenIfEmpty()
// reschedule options
binding.rescheduleInfoLabel.text = TR.decksZeroMinutesHint()
- binding.rescheduleDelayAgainLayout.hint = getString(R.string.filtered_option_delay_again)
- binding.rescheduleDelayHardLayout.hint = getString(R.string.filtered_option_delay_hard)
- binding.rescheduleDelayGoodLayout.hint = getString(R.string.filtered_option_delay_good)
+ binding.rescheduleDelayAgainLayout.hint = getString(CommonString.filtered_option_delay_again)
+ binding.rescheduleDelayHardLayout.hint = getString(CommonString.filtered_option_delay_hard)
+ binding.rescheduleDelayGoodLayout.hint = getString(CommonString.filtered_option_delay_good)
}
/** Sets text of [TextInputEditText] to [newText] only if it's different from its current text */
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/EditorChips.kt b/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/EditorChips.kt
index f02fa2e0b4ef..f5481bea1dbf 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/EditorChips.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/EditorChips.kt
@@ -26,7 +26,7 @@ import com.google.android.material.badge.ExperimentalBadgeUtils
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.ichi2.anki.AnkiActivity
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.dialogs.IntegerDialog
import com.ichi2.anki.utils.ext.showDialogFragment
@@ -134,8 +134,8 @@ private fun AnkiActivity.displayUpdateClozeNumberDialog(
val repositionDialog =
IntegerDialog().apply {
setArgs(
- title = this@displayUpdateClozeNumberDialog.getString(R.string.change_cloze_number),
- prompt = this@displayUpdateClozeNumberDialog.getString(R.string.cloze_number),
+ title = this@displayUpdateClozeNumberDialog.getString(CommonString.change_cloze_number),
+ prompt = this@displayUpdateClozeNumberDialog.getString(CommonString.cloze_number),
digits = 2,
defaultValue = clozeNumber.toString(),
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantNoteEditorActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantNoteEditorActivity.kt
index c03e6a187c5c..71970f6b1ca6 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantNoteEditorActivity.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/instantnoteeditor/InstantNoteEditorActivity.kt
@@ -27,6 +27,7 @@ import com.google.android.material.textfield.TextInputLayout
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.CustomActionModeCallback
import com.ichi2.anki.R
import com.ichi2.anki.common.destinations.NoteEditorDestination
@@ -385,7 +386,7 @@ class InstantNoteEditorActivity : AnkiActivity(R.layout.activity_instant_note_ed
when (result) {
is SaveNoteResult.Failure -> {
Timber.d("Failed to save note")
- savingErrorDialog(result.message ?: getString(R.string.something_wrong))
+ savingErrorDialog(result.message ?: getString(CommonString.something_wrong))
}
SaveNoteResult.Success -> {
@@ -396,7 +397,7 @@ class InstantNoteEditorActivity : AnkiActivity(R.layout.activity_instant_note_ed
is SaveNoteResult.Warning -> {
Timber.d("Showing warning to the user")
- viewModel.setWarningMessage(result.message ?: getString(R.string.something_wrong))
+ viewModel.setWarningMessage(result.message ?: getString(CommonString.something_wrong))
}
}
}
@@ -451,12 +452,12 @@ class InstantNoteEditorActivity : AnkiActivity(R.layout.activity_instant_note_ed
* AnkiDroid Note Editor **/
private fun noClozeNoteTypesFoundDialog() {
AlertDialog.Builder(this).show {
- title(R.string.cloze_note_required)
- message(R.string.cloze_not_found_message)
- positiveButton(R.string.open) {
+ title(CommonString.cloze_note_required)
+ message(CommonString.cloze_not_found_message)
+ positiveButton(CommonString.open) {
openNoteEditor()
}
- negativeButton(R.string.dialog_cancel) {
+ negativeButton(CommonString.dialog_cancel) {
finish()
}
}
@@ -469,7 +470,7 @@ class InstantNoteEditorActivity : AnkiActivity(R.layout.activity_instant_note_ed
.onEach { errorMessage ->
AlertDialog
.Builder(this)
- .setTitle(R.string.vague_error)
+ .setTitle(CommonString.vague_error)
.setMessage(errorMessage)
.show()
}.launchIn(lifecycleScope)
@@ -497,10 +498,10 @@ class InstantNoteEditorActivity : AnkiActivity(R.layout.activity_instant_note_ed
private fun savingErrorDialog(message: String) {
AlertDialog.Builder(this).show {
message(text = message)
- positiveButton(R.string.try_again) {
+ positiveButton(CommonString.try_again) {
checkAndSave()
}
- negativeButton(R.string.dialog_cancel) {
+ negativeButton(CommonString.dialog_cancel) {
instantAlertDialog.dismiss()
}
}
@@ -514,14 +515,14 @@ class InstantNoteEditorActivity : AnkiActivity(R.layout.activity_instant_note_ed
positiveButton(text = TR.actionsSave()) {
saveNoteWithProgress(skipClozeCheck = true)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
private fun saveNoteWithProgress(skipClozeCheck: Boolean) {
lifecycleScope.launch {
val result =
- withProgress(resources.getString(R.string.saving_facts)) {
+ withProgress(resources.getString(CommonString.saving_facts)) {
viewModel.checkAndSaveNote(skipClozeCheck = skipClozeCheck)
}
handleSaveNoteResult(result)
@@ -550,7 +551,7 @@ class InstantNoteEditorActivity : AnkiActivity(R.layout.activity_instant_note_ed
CustomActionModeCallback(
// we always have cloze type notes here
isClozeType = true,
- getString(R.string.multimedia_editor_popup_cloze),
+ getString(CommonString.multimedia_editor_popup_cloze),
clozeMenuId,
onActionItemSelected = { mode, item ->
val itemId = item.itemId
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/TgzPackageExtract.kt b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/TgzPackageExtract.kt
index b657dabef50e..5f0f14e4535b 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/TgzPackageExtract.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/jsaddons/TgzPackageExtract.kt
@@ -35,7 +35,7 @@ package com.ichi2.anki.jsaddons
import android.content.Context
import android.text.format.Formatter
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.compat.CompatHelper.Companion.compat
import com.ichi2.utils.FileUtil
import org.apache.commons.compress.archivers.ArchiveException
@@ -130,13 +130,13 @@ class TgzPackageExtract(
tarballFile: File,
addonsPackageDir: AddonsPackageDir,
) {
- require(isGzip(tarballFile)) { context.getString(R.string.not_valid_js_addon_package, tarballFile.absolutePath) }
+ require(isGzip(tarballFile)) { context.getString(CommonString.not_valid_js_addon_package, tarballFile.absolutePath) }
try {
compat.createDirectories(addonsPackageDir)
} catch (e: IOException) {
Timber.w(e)
- throw IOException(context.getString(R.string.could_not_create_dir, addonsPackageDir.absolutePath))
+ throw IOException(context.getString(CommonString.could_not_create_dir, addonsPackageDir.absolutePath))
}
// Make sure we have 2x the tar file size in free space (1x for tar file, 1x for unarchived tar file contents
@@ -259,7 +259,7 @@ class TgzPackageExtract(
outputDir.deleteRecursively()
throw ArchiveException(
context.getString(
- R.string.malicious_archive_exceeds_limit,
+ CommonString.malicious_archive_exceeds_limit,
Formatter.formatFileSize(context, TOO_BIG_SIZE),
TOO_MANY_FILES,
),
@@ -293,7 +293,7 @@ class TgzPackageExtract(
} catch (e: IOException) {
// clean up
Timber.w(e)
- throw IOException(context.getString(R.string.could_not_create_dir, parent.absolutePath))
+ throw IOException(context.getString(CommonString.could_not_create_dir, parent.absolutePath))
}
FileOutputStream(outputFile).use { outputFileStream ->
@@ -338,7 +338,7 @@ class TgzPackageExtract(
compat.createDirectories(outputFile)
} catch (e: IOException) {
Timber.w(e)
- throw IOException(context.getString(R.string.could_not_create_dir, outputFile.absolutePath))
+ throw IOException(context.getString(CommonString.could_not_create_dir, outputFile.absolutePath))
}
}
@@ -359,19 +359,19 @@ class TgzPackageExtract(
destDirectory.canonicalPath
} catch (_: IOException) {
// Path may contain PII; removing the %s param would churn translations — use a placeholder.
- throw ArchiveException(context.getString(R.string.malicious_archive_entry_outside, ARCHIVE_ENTRY_PATH_OMITTED))
+ throw ArchiveException(context.getString(CommonString.malicious_archive_entry_outside, ARCHIVE_ENTRY_PATH_OMITTED))
}
val childCanonical =
try {
outputFile.canonicalPath
} catch (_: IOException) {
- throw ArchiveException(context.getString(R.string.malicious_archive_entry_outside, ARCHIVE_ENTRY_PATH_OMITTED))
+ throw ArchiveException(context.getString(CommonString.malicious_archive_entry_outside, ARCHIVE_ENTRY_PATH_OMITTED))
}
val destPrefix = destCanonical + File.separator
// Allow the destination directory itself (e.g. `./` entries). Reject anything else
// that is not dest and not under dest + separator (sibling-prefix safe).
if (childCanonical != destCanonical && !childCanonical.startsWith(destPrefix)) {
- throw ArchiveException(context.getString(R.string.malicious_archive_entry_outside, ARCHIVE_ENTRY_PATH_OMITTED))
+ throw ArchiveException(context.getString(CommonString.malicious_archive_entry_outside, ARCHIVE_ENTRY_PATH_OMITTED))
}
}
@@ -409,7 +409,7 @@ class TgzPackageExtract(
private fun enforceSpaceUsedLessThanHalfAvailable(outputDir: File) {
val newAvailableSpace: Long = FileUtil.determineBytesAvailable(outputDir.canonicalPath)
if (newAvailableSpace <= availableSpace / 2) {
- throw ArchiveException(context.getString(R.string.file_extract_exceeds_storage_space))
+ throw ArchiveException(context.getString(CommonString.file_extract_exceeds_storage_space))
}
}
@@ -452,7 +452,7 @@ class TgzPackageExtract(
private const val TOO_BIG_SIZE: Long = 0x6400000 // max size of unzipped data, 100MB
private const val TOO_MANY_FILES = 1024 // max number of files
- /** Placeholder for [R.string.malicious_archive_entry_outside]; do not pass real archive paths. */
+ /** Placeholder for [CommonString.malicious_archive_entry_outside]; do not pass real archive paths. */
private const val ARCHIVE_ENTRY_PATH_OMITTED = "archive entry"
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/mediacheck/MediaCheckFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/mediacheck/MediaCheckFragment.kt
index 3186c1294eef..f7aa110c484d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/mediacheck/MediaCheckFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/mediacheck/MediaCheckFragment.kt
@@ -21,6 +21,8 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.SingleFragmentActivity
import com.ichi2.anki.common.utils.android.getColorFromAttr
@@ -149,7 +151,7 @@ class MediaCheckFragment : Fragment(R.layout.fragment_media_check) {
launchCatchingTask {
viewModel.tagMissing(TR.mediaCheckMissingMediaTag()).join()
showResultDialog(
- R.string.check_media_tags_added,
+ CommonString.check_media_tags_added,
TR.browsingNotesUpdated(viewModel.taggedFiles),
)
}
@@ -182,8 +184,8 @@ class MediaCheckFragment : Fragment(R.layout.fragment_media_check) {
private fun deleteConfirmationDialog() {
AlertDialog.Builder(requireContext()).show {
message(text = TR.mediaCheckDeleteUnusedConfirm())
- positiveButton(R.string.dialog_positive_delete) { handleDeleteConfirmation() }
- negativeButton(R.string.dialog_cancel)
+ positiveButton(CommonString.dialog_positive_delete) { handleDeleteConfirmation() }
+ negativeButton(CommonString.dialog_cancel)
}
}
@@ -203,9 +205,9 @@ class MediaCheckFragment : Fragment(R.layout.fragment_media_check) {
*/
private fun showDeletionResult() {
showResultDialog(
- R.string.delete_media_result_title,
+ CommonString.delete_media_result_title,
resources.getQuantityString(
- R.plurals.delete_media_result_message,
+ CommonPlurals.delete_media_result_message,
viewModel.deletedFiles,
viewModel.deletedFiles,
),
@@ -215,7 +217,7 @@ class MediaCheckFragment : Fragment(R.layout.fragment_media_check) {
private fun showTrashRestoredDialog() {
AlertDialog.Builder(requireContext()).show {
message(text = TR.mediaCheckTrashRestored())
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
requireActivity().finish()
}
cancelable(false)
@@ -225,7 +227,7 @@ class MediaCheckFragment : Fragment(R.layout.fragment_media_check) {
private fun showTrashDeletedDialog() {
AlertDialog.Builder(requireContext()).show {
message(text = TR.mediaCheckTrashEmptied())
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
requireActivity().finish()
}
cancelable(false)
@@ -239,7 +241,7 @@ class MediaCheckFragment : Fragment(R.layout.fragment_media_check) {
AlertDialog.Builder(requireContext()).show {
title(titleRes)
message(text = message)
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
requireActivity().finish()
}
cancelable(false)
@@ -255,7 +257,7 @@ class MediaCheckFragment : Fragment(R.layout.fragment_media_check) {
private val MediaCheckProgress.messageRes: Int
get() =
when (this) {
- MediaCheckProgress.CHECKING_MEDIA -> R.string.check_media_message
- MediaCheckProgress.ADDING_TAGS -> R.string.check_media_adding_missing_tag
- MediaCheckProgress.DELETING_MEDIA -> R.string.delete_media_message
+ MediaCheckProgress.CHECKING_MEDIA -> CommonString.check_media_message
+ MediaCheckProgress.ADDING_TAGS -> CommonString.check_media_adding_missing_tag
+ MediaCheckProgress.DELETING_MEDIA -> CommonString.delete_media_message
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioRecordingFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioRecordingFragment.kt
index b794c4a76dd3..ce01fd42e5ee 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioRecordingFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioRecordingFragment.kt
@@ -25,6 +25,7 @@ import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.lifecycleScope
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.databinding.FragmentAudioRecordingBinding
@@ -40,7 +41,7 @@ class AudioRecordingFragment : MultimediaFragment(R.layout.fragment_audio_record
internal val binding by viewBinding(FragmentAudioRecordingBinding::bind)
override val title: String
- get() = resources.getString(R.string.multimedia_editor_field_editing_audio)
+ get() = resources.getString(CommonString.multimedia_editor_field_editing_audio)
private var audioRecordingController: AudioRecordingController? = null
@@ -49,7 +50,7 @@ class AudioRecordingFragment : MultimediaFragment(R.layout.fragment_audio_record
ankiCacheDirectory = FileUtil.getAnkiCacheDirectory(requireContext(), "temp-media")
if (ankiCacheDirectory == null) {
Timber.e("createUI() failed to get cache directory")
- showErrorDialog(errorMessage = resources.getString(R.string.multimedia_editor_failed))
+ showErrorDialog(errorMessage = resources.getString(CommonString.multimedia_editor_failed))
return
}
}
@@ -65,7 +66,7 @@ class AudioRecordingFragment : MultimediaFragment(R.layout.fragment_audio_record
setupDoneButton()
} else {
Timber.d("Audio permission denied")
- showErrorDialog(resources.getString(R.string.multimedia_editor_audio_permission_refused))
+ showErrorDialog(resources.getString(CommonString.multimedia_editor_audio_permission_refused))
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioVideoFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioVideoFragment.kt
index 6d012c69ecca..2a1ef4a55d46 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioVideoFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/AudioVideoFragment.kt
@@ -33,6 +33,7 @@ import androidx.media3.common.C
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.common.crashreporting.CrashReportService
@@ -144,7 +145,7 @@ class AudioVideoFragment : MultimediaFragment(R.layout.fragment_audio_video) {
"audio/*",
// #9226: allows ogg on Android 8
arrayOf("audio/*", "application/ogg"),
- R.string.multimedia_editor_popup_audio_clip,
+ CommonString.multimedia_editor_popup_audio_clip,
)
}
@@ -153,7 +154,7 @@ class AudioVideoFragment : MultimediaFragment(R.layout.fragment_audio_video) {
openMediaChooser(
"video/*",
emptyArray(),
- R.string.multimedia_editor_popup_video_clip,
+ CommonString.multimedia_editor_popup_video_clip,
)
}
}
@@ -408,7 +409,7 @@ class AudioVideoFragment : MultimediaFragment(R.layout.fragment_audio_video) {
context: Context,
): String =
when (mediaOption) {
- AUDIO_CLIP -> context.getString(R.string.multimedia_editor_popup_audio_clip)
- VIDEO_CLIP -> context.getString(R.string.multimedia_editor_popup_video_clip)
+ AUDIO_CLIP -> context.getString(CommonString.multimedia_editor_popup_audio_clip)
+ VIDEO_CLIP -> context.getString(CommonString.multimedia_editor_popup_video_clip)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaFragment.kt
index 159bec820410..2c5d7b1bf7df 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaFragment.kt
@@ -26,7 +26,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.ichi2.anki.CollectionManager.TR
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.compat.CompatHelper.Companion.getSerializableCompat
@@ -201,7 +201,7 @@ abstract class MultimediaFragment(
* "Something went wrong" message from the application's resources.
*/
fun showSomethingWentWrong() {
- showSnackbar(resources.getString(R.string.multimedia_editor_something_wrong))
+ showSnackbar(resources.getString(CommonString.multimedia_editor_something_wrong))
}
/**
@@ -211,8 +211,8 @@ abstract class MultimediaFragment(
*/
fun showErrorDialog(errorMessage: String? = null) {
AlertDialog.Builder(requireContext()).show {
- setMessage(errorMessage ?: resources.getString(R.string.something_wrong))
- setPositiveButton(getString(R.string.dialog_ok)) { _, _ ->
+ setMessage(errorMessage ?: resources.getString(CommonString.something_wrong))
+ setPositiveButton(getString(CommonString.dialog_ok)) { _, _ ->
requireActivity().finish()
}
setOnCancelListener { requireActivity().finish() }
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaImageFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaImageFragment.kt
index 15729873606a..a7fc7e17b099 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaImageFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/MultimediaImageFragment.kt
@@ -26,6 +26,7 @@ import androidx.core.os.BundleCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DrawingFragment
import com.ichi2.anki.R
import com.ichi2.anki.common.annotations.NeedsTest
@@ -80,7 +81,7 @@ class MultimediaImageFragment :
private var previewedImage: Uri? = null
override val title: String
- get() = resources.getString(R.string.multimedia_editor_popup_image)
+ get() = resources.getString(CommonString.multimedia_editor_popup_image)
private lateinit var selectedImageOptions: ImageOptions
@@ -110,7 +111,7 @@ class MultimediaImageFragment :
val selectedImage = PickedImage(data).trustedUri
if (selectedImage == null) {
- showSnackbar(getString(R.string.select_image_failed))
+ showSnackbar(getString(CommonString.select_image_failed))
return@registerForActivityResult
}
handleSelectImageIntent(selectedImage)
@@ -249,7 +250,7 @@ class MultimediaImageFragment :
ankiCacheDirectory = FileUtil.getAnkiCacheDirectory(requireContext(), "temp-photos")
if (ankiCacheDirectory == null) {
Timber.e("createUI() failed to get cache directory")
- showErrorDialog(errorMessage = resources.getString(R.string.multimedia_editor_failed))
+ showErrorDialog(errorMessage = resources.getString(CommonString.multimedia_editor_failed))
return
}
@@ -349,7 +350,7 @@ class MultimediaImageFragment :
pickImageLauncher.launch(intent)
} catch (e: ActivityNotFoundException) {
Timber.w(e, "MultimediaImageFragment:: No app found to select image")
- showSnackbar(R.string.activity_start_failed)
+ showSnackbar(CommonString.activity_start_failed)
}
}
@@ -380,7 +381,7 @@ class MultimediaImageFragment :
cameraLauncher.launch(photoURI)
} catch (e: ActivityNotFoundException) {
Timber.w(e, "MultimediaImageFragment:: No camera found")
- showSnackbar(R.string.activity_start_failed)
+ showSnackbar(CommonString.activity_start_failed)
}
}
}
@@ -431,7 +432,7 @@ class MultimediaImageFragment :
viewModel.currentMultimediaUri.value?.let { previewImage(it) }
updateAndDisplayImageSize(imageFile)
- showCropDialog(getString(R.string.crop_image))
+ showCropDialog(getString(CommonString.crop_image))
}
private fun updateAndDisplayImageSize(imageUri: String) {
@@ -447,24 +448,24 @@ class MultimediaImageFragment :
val numberFormat = NumberFormat.getInstance()
// length is in bits, other elements have MB, convert to MB
val size = numberFormat.format(length / 1000000.0)
- val message = getString(R.string.save_dialog_content, size)
+ val message = getString(CommonString.save_dialog_content, size)
showCompressImageDialog(message)
}
private fun showCompressImageDialog(message: String) {
AlertDialog.Builder(requireActivity()).show {
message(text = message)
- positiveButton(R.string.compress) {
+ positiveButton(CommonString.compress) {
viewModel.currentMultimediaPath.value.let {
if (it == null) return@positiveButton
if (!rotateAndCompress(it)) {
Timber.d("Unable to compress the clicked image")
- showErrorDialog(errorMessage = resources.getString(R.string.multimedia_editor_image_compression_failed))
+ showErrorDialog(errorMessage = resources.getString(CommonString.multimedia_editor_image_compression_failed))
return@positiveButton
}
}
}
- negativeButton(R.string.dialog_no) {
+ negativeButton(CommonString.dialog_no) {
finishAddingImage()
}
}
@@ -478,10 +479,10 @@ class MultimediaImageFragment :
AlertDialog.Builder(requireActivity()).show {
message(text = message)
- positiveButton(R.string.dialog_yes) {
+ positiveButton(CommonString.dialog_yes) {
requestCrop()
}
- negativeButton(R.string.dialog_no)
+ negativeButton(CommonString.dialog_no)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/audio/AudioRecordingController.kt b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/audio/AudioRecordingController.kt
index 0e65f915ab84..b124a9c29679 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/audio/AudioRecordingController.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimedia/audio/AudioRecordingController.kt
@@ -38,6 +38,7 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.material.button.MaterialButton
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.progressindicator.LinearProgressIndicator
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.Reviewer
import com.ichi2.anki.common.annotations.NeedsTest
@@ -162,7 +163,7 @@ class AudioRecordingController(
sv.addView(previewLayout)
val label = FixedTextView(this)
label.textSize = 20f
- label.text = UiUtil.makeBold(this.getString(R.string.audio_recording_field_list))
+ label.text = UiUtil.makeBold(this.getString(CommonString.audio_recording_field_list))
label.gravity = Gravity.CENTER_HORIZONTAL
previewLayout.addView(label)
var hasTextContents = false
@@ -389,7 +390,7 @@ class AudioRecordingController(
iconTint = ContextCompat.getColorStateList(context, R.color.audio_recorder_red)
strokeColor = ContextCompat.getColorStateList(context, R.color.audio_recorder_red)
setIconResource(R.drawable.ic_record)
- contentDescription = context.getString(R.string.start_recording)
+ contentDescription = context.getString(CommonString.start_recording)
}
audioWaveform.clear()
cancelAudioRecordingButton.isEnabled = false
@@ -400,7 +401,7 @@ class AudioRecordingController(
iconTint = ContextCompat.getColorStateList(context, R.color.audio_recorder_red)
strokeColor = ContextCompat.getColorStateList(context, R.color.audio_recorder_red)
setIconResource(R.drawable.ic_stop)
- contentDescription = context.getString(R.string.stop_recording)
+ contentDescription = context.getString(CommonString.stop_recording)
}
cancelAudioRecordingButton.isEnabled = true
audioProgressBar.isVisible = false
@@ -410,7 +411,7 @@ class AudioRecordingController(
iconTint = ContextCompat.getColorStateList(context, R.color.audio_recorder_grey)
strokeColor = ContextCompat.getColorStateList(context, R.color.audio_recorder_grey)
setIconResource(R.drawable.ic_skip_next)
- contentDescription = context.getString(R.string.next_recording)
+ contentDescription = context.getString(CommonString.next_recording)
}
cancelAudioRecordingButton.isEnabled = true
audioProgressBar.isVisible = true
@@ -421,7 +422,7 @@ class AudioRecordingController(
iconTint = ContextCompat.getColorStateList(context, R.color.audio_recorder_grey)
strokeColor = ContextCompat.getColorStateList(context, R.color.audio_recorder_grey)
setIconResource(R.drawable.ic_play)
- contentDescription = context.getString(R.string.play_recording)
+ contentDescription = context.getString(CommonString.play_recording)
}
cancelAudioRecordingButton.isEnabled = true
audioProgressBar.isVisible = true
@@ -533,7 +534,7 @@ class AudioRecordingController(
stopAndSaveRecording()
// TODO: discuss if we want to keep the snackbar here
// show this snackbar only in the edit field/multimedia activity
- if (inEditField) (context as Activity).showSnackbar(context.resources.getString(R.string.audio_saved))
+ if (inEditField) (context as Activity).showSnackbar(context.resources.getString(CommonString.audio_saved))
prepareAudioPlayer()
}
@@ -557,7 +558,7 @@ class AudioRecordingController(
Timber.w("Audio recording permission denied.")
showThemedToast(
context,
- context.resources.getString(R.string.multimedia_editor_audio_permission_denied),
+ context.resources.getString(CommonString.multimedia_editor_audio_permission_denied),
true,
)
return
@@ -603,7 +604,7 @@ class AudioRecordingController(
audioPlayer!!.start()
} catch (e: Exception) {
Timber.w(e, "error starting audioPlayer")
- showThemedToast(context, context.resources.getString(R.string.multimedia_editor_audio_view_playing_failed), true)
+ showThemedToast(context, context.resources.getString(CommonString.multimedia_editor_audio_view_playing_failed), true)
}
audioTimer.start()
setUiState(state.play())
@@ -671,7 +672,7 @@ class AudioRecordingController(
audioRecorder.stop()
} catch (e: RuntimeException) {
Timber.i(e, "Recording stop failed, this happens if stop was hit immediately after start")
- showThemedToast(context, context.resources.getString(R.string.multimedia_editor_audio_view_recording_failed), true)
+ showThemedToast(context, context.resources.getString(CommonString.multimedia_editor_audio_view_recording_failed), true)
}
isRecording = false
isAudioRecordingSaved = true
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt b/AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt
index c7fcc1453203..ba76d37ddebe 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt
@@ -719,6 +719,39 @@ class ProfileManager private constructor(
"broken",
)
+ /**
+ * Failure from the last [createOrNull], if any.
+ *
+ * Timber is not planted during [android.app.Application.attachBaseContext],
+ * so a failure there cannot be logged where it happens.
+ */
+ var attachError: Throwable? = null
+ private set
+
+ /**
+ * Loads the active profile, or returns null when its environment cannot be
+ * loaded.
+ *
+ * Never throws: a failure during [android.app.Application.attachBaseContext]
+ * would stop the app from starting at all, so callers run on the base context
+ * instead. Users with only the Default profile are unaffected either way, since
+ * the wrapper delegates everything to the base context for that profile.
+ */
+ fun createOrNull(base: Context): ProfileManager? =
+ try {
+ attachError = null
+ create(base)
+ } catch (e: Exception) {
+ // TODO: show this to the user instead of silently falling back to the Default profile
+ attachError = e
+ null
+ }
+
+ @VisibleForTesting
+ fun resetForTesting() {
+ attachError = null
+ }
+
/**
* Factory method to safely create and initialize the ProfileManager.
* Guaranteed to return a ProfileManager with a valid [activeProfileContext].
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.kt b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.kt
index 7d97673d0300..a93a6928dfd8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.kt
@@ -5,9 +5,9 @@ package com.ichi2.anki.noteeditor
import android.content.Context
import android.os.Bundle
import android.view.View
+import com.ichi2.anki.CommonString
import com.ichi2.anki.FieldEditLine
import com.ichi2.anki.NoteEditorFragment
-import com.ichi2.anki.R
import com.ichi2.anki.common.utils.annotation.KotlinCleanup
import com.ichi2.anki.libanki.Field
import com.ichi2.anki.libanki.NotetypeJson
@@ -157,7 +157,7 @@ class FieldState private constructor(
// Set the new field label text
if (allowFieldRemapping(oldFields)) {
// Show the content of old field if remapping is enabled
- fields[i][0] = String.format(context.resources.getString(R.string.field_remapping), fname, oldFields[j][0])
+ fields[i][0] = String.format(context.resources.getString(CommonString.field_remapping), fname, oldFields[j][0])
} else {
fields[i][0] = fname
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/NoteEditorMultimediaController.kt b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/NoteEditorMultimediaController.kt
index 17f093bacc73..9ee364fcbf1f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/NoteEditorMultimediaController.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/NoteEditorMultimediaController.kt
@@ -12,9 +12,9 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.VisibleForTesting
import androidx.core.os.BundleCompat
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.MediaRegistration
import com.ichi2.anki.NoteEditorFragment
-import com.ichi2.anki.R
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.compat.CompatHelper.Companion.getSerializableCompat
import com.ichi2.anki.exception.MediaSizeLimitExceededException
@@ -92,7 +92,7 @@ internal class NoteEditorMultimediaController(
if (imageUri == null) {
Timber.d("NoteEditor:: Image Uri is null")
- fragment.showSnackbar(R.string.something_wrong)
+ fragment.showSnackbar(CommonString.something_wrong)
return
}
@@ -109,7 +109,7 @@ internal class NoteEditorMultimediaController(
val cachedImagePath = copyUriToInternalCache(imageUri)
if (cachedImagePath == null) {
Timber.w("Failed to cache image")
- fragment.showSnackbar(R.string.something_wrong)
+ fragment.showSnackbar(CommonString.something_wrong)
return
}
val cachedUri = Uri.fromFile(File(fragment.requireContext().cacheDir, cachedImagePath))
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.kt b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.kt
index 6acbec65c32e..1b8393b1c903 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.kt
@@ -37,6 +37,7 @@ import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
+import com.ichi2.anki.CommonString
import com.ichi2.anki.NoteEditorFragment
import com.ichi2.anki.R
import com.ichi2.anki.common.android.appContext
@@ -302,7 +303,7 @@ class Toolbar : FrameLayout {
)
onFormat(formatter)
}
- title(R.string.menu_font_size)
+ title(CommonString.menu_font_size)
}
}
@@ -318,7 +319,7 @@ class Toolbar : FrameLayout {
val formatter = TextWrapper(prefix = "<$charSequence>", suffix = "$charSequence>")
onFormat(formatter)
}
- title(R.string.insert_heading)
+ title(CommonString.insert_heading)
}
}
@@ -332,7 +333,7 @@ class Toolbar : FrameLayout {
setItems(labels) { _, index ->
onFormat(options[index].toTextWrapper())
}
- title(R.string.insert_mathjax)
+ title(CommonString.insert_mathjax)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddNewNotesType.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddNewNotesType.kt
index 4234d45682cb..e982ea66724f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddNewNotesType.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/AddNewNotesType.kt
@@ -26,7 +26,7 @@ import anki.notetypes.StockNotetype
import anki.notetypes.copy
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.time.TimeManager
import com.ichi2.anki.databinding.DialogNewNoteTypeBinding
import com.ichi2.anki.launchCatchingTask
@@ -87,7 +87,7 @@ class AddNewNotesType(
paddingTop = 24.dp.toPx(activity),
paddingBottom = 0,
)
- positiveButton(R.string.dialog_add) { _ ->
+ positiveButton(CommonString.dialog_add) { _ ->
val newName =
binding.notetypeNewName.text
.toString()
@@ -102,7 +102,7 @@ class AddNewNotesType(
cloneStandardNotetype(newName, selectedOption)
}
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}.show()
dialog.initializeViewsWith(allOptions, currentNames)
}
@@ -111,8 +111,8 @@ class AddNewNotesType(
optionsToDisplay: List,
currentNames: List,
) {
- val addPrefixStr = context.resources.getString(R.string.model_browser_add_add)
- val clonePrefixStr = context.resources.getString(R.string.model_browser_add_clone)
+ val addPrefixStr = context.resources.getString(CommonString.model_browser_add_add)
+ val clonePrefixStr = context.resources.getString(CommonString.model_browser_add_clone)
binding.notetypeTypeLabel.text = TR.notetypesType()
binding.notetypeNewNameLayout.hint = TR.deckConfigNamePrompt()
@@ -123,7 +123,7 @@ class AddNewNotesType(
binding.notetypeNewNameLayout.error =
if (alreadyExists) {
- context.getString(R.string.error_name_exists)
+ context.getString(CommonString.error_name_exists)
} else {
null
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt
index 799ec83de084..86fda6aea89c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/ManageNotetypes.kt
@@ -37,6 +37,8 @@ import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.behavior.HideViewOnScrollBehavior
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.databinding.ActivityManageNoteTypesBinding
@@ -125,17 +127,17 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
if (userAcceptsSchemaChange()) {
val selection =
viewModel.selectedNoteTypes.joinToString { it.name }
- getString(R.string.model_delete_multiple_warning, selection)
+ getString(CommonString.model_delete_multiple_warning, selection)
} else {
return@launchCatchingTask
}
AlertDialog.Builder(this@ManageNotetypes).show {
- title(R.string.dialog_positive_delete)
+ title(CommonString.dialog_positive_delete)
message(text = deleteMessage)
- positiveButton(R.string.dialog_positive_delete) {
+ positiveButton(CommonString.dialog_positive_delete) {
viewModel.deleteSelectedNoteTypes()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
}
@@ -147,7 +149,7 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
if (state.message != null) {
val snackbarMessage =
when (state.message) {
- UserMessage.DeletingLastModel -> getString(R.string.toast_last_model)
+ UserMessage.DeletingLastModel -> getString(CommonString.toast_last_model)
}
showSnackbar(snackbarMessage)
viewModel.clearMessage()
@@ -199,7 +201,7 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
}
AlertDialog.Builder(this).show {
message(text = state.error.source.message)
- positiveButton(R.string.close) { viewModel.refreshNoteTypes() }
+ positiveButton(CommonString.close) { viewModel.refreshNoteTypes() }
}
viewModel.clearError()
return
@@ -225,7 +227,7 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
val selectedCount = state.noteTypes.count { it.isSelected }
binding.selectedLabel.text =
resources.getQuantityString(
- R.plurals.note_types_selected,
+ CommonPlurals.note_types_selected,
selectedCount,
selectedCount,
)
@@ -279,8 +281,8 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
AlertDialog
.Builder(this@ManageNotetypes)
.show {
- title(R.string.rename_model)
- positiveButton(R.string.rename) {
+ title(CommonString.rename_model)
+ positiveButton(CommonString.rename) {
val userInput =
(it as AlertDialog)
.getInputField()
@@ -290,7 +292,7 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
if (userInput.isEmpty()) return@positiveButton
viewModel.rename(state.id, userInput)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
setView(R.layout.dialog_generic_text_input)
}.input(
hint = TR.deckConfigNamePrompt(),
@@ -310,7 +312,7 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
dialog.positiveButton.isEnabled = false
return@input
} else if (isDuplicate && !isUnchanged) {
- dialog.getInputTextLayout().error = getString(R.string.error_name_exists)
+ dialog.getInputTextLayout().error = getString(CommonString.error_name_exists)
dialog.positiveButton.isEnabled = false
return@input
}
@@ -329,17 +331,17 @@ class ManageNotetypes : AnkiActivity(R.layout.activity_manage_note_types) {
launchCatchingTask {
@StringRes val messageResourceId: Int? =
if (userAcceptsSchemaChange()) {
- R.string.model_delete_warning
+ CommonString.model_delete_warning
} else {
return@launchCatchingTask
}
AlertDialog.Builder(this@ManageNotetypes).show {
- title(R.string.model_browser_delete)
+ title(CommonString.model_browser_delete)
message(messageResourceId)
- positiveButton(R.string.dialog_positive_delete) {
+ positiveButton(CommonString.dialog_positive_delete) {
viewModel.delete(state.id)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypesAdapter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypesAdapter.kt
index a1c43130f814..12930202013b 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypesAdapter.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/NoteTypesAdapter.kt
@@ -23,6 +23,7 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
+import com.ichi2.anki.CommonPlurals
import com.ichi2.anki.R
import com.ichi2.anki.databinding.ItemManageNoteTypeBinding
import com.ichi2.anki.notetype.NoteTypesAdapter.NoteTypeViewHolder
@@ -145,7 +146,7 @@ internal class NoteTypesAdapter(
binding.noteName.text = state.name
binding.noteUseCount.text =
context.resources.getQuantityString(
- R.plurals.model_browser_of_type,
+ CommonPlurals.model_browser_of_type,
state.useCount,
state.useCount,
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTypeDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTypeDialog.kt
index f61883866b04..98f108240d85 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTypeDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RenameCardTypeDialog.kt
@@ -5,6 +5,7 @@ package com.ichi2.anki.notetype
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.utils.ValidationResult
import com.ichi2.utils.input
@@ -31,9 +32,9 @@ class RenameCardTypeDialog {
AlertDialog
.Builder(context)
.show {
- title(R.string.rename_card_type)
- positiveButton(R.string.rename) { }
- negativeButton(R.string.dialog_cancel)
+ title(CommonString.rename_card_type)
+ positiveButton(CommonString.rename) { }
+ negativeButton(CommonString.dialog_cancel)
setView(R.layout.dialog_generic_text_input)
}.input(
hint = CollectionManager.TR.actionsNewName().removeSuffix(":"),
@@ -45,7 +46,7 @@ class RenameCardTypeDialog {
when {
currentName == name -> ValidationResult.REJECTED
!existingNames.contains(name) -> ValidationResult.VALID
- else -> ValidationResult.error(context.getString(R.string.error_name_exists))
+ else -> ValidationResult.error(context.getString(CommonString.error_name_exists))
}
},
callback = { dialog, result ->
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RepositionCardTemplateDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RepositionCardTemplateDialog.kt
index 913a271799a5..c4f54bfcb272 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RepositionCardTemplateDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/notetype/RepositionCardTemplateDialog.kt
@@ -6,6 +6,7 @@ import android.content.Context
import android.text.InputType
import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.utils.getInputField
import com.ichi2.utils.input
@@ -26,7 +27,7 @@ class RepositionCardTemplateDialog {
AlertDialog
.Builder(context)
.show {
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
result(
displayedDialog!!
.getInputField()
@@ -35,7 +36,7 @@ class RepositionCardTemplateDialog {
.toInt(),
)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
setMessage(CollectionManager.TR.cardTemplatesEnterNewCardPosition1(numberOfTemplates))
setView(R.layout.dialog_generic_text_input)
}.input(
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/CongratsPage.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/CongratsPage.kt
index 5eacf79ea941..3231227bdd5e 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/CongratsPage.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/CongratsPage.kt
@@ -20,6 +20,7 @@ import anki.scheduler.UnburyDeckRequest
import com.google.android.material.appbar.MaterialToolbar
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.OnErrorListener
import com.ichi2.anki.R
@@ -85,7 +86,7 @@ class CongratsPage :
.onEach { errorMessage ->
AlertDialog
.Builder(requireContext())
- .setTitle(R.string.vague_error)
+ .setTitle(CommonString.vague_error)
.setMessage(errorMessage)
.show()
}.launchIn(lifecycleScope)
@@ -103,7 +104,7 @@ class CongratsPage :
)
AlertDialog.Builder(requireContext()).show {
setTitle(TR.studyingWhatWouldYouLikeToUnbury())
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
listItems(
unburyOptions,
) { _, position ->
@@ -220,7 +221,7 @@ class CongratsPage :
activity.showSnackbar(message)
}
} else {
- activity.showSnackbar(R.string.studyoptions_no_cards_due)
+ activity.showSnackbar(CommonString.studyoptions_no_cards_due)
}
}
@@ -229,7 +230,7 @@ class CongratsPage :
val info = withCol { sched.congratulationsInfo() }
val secsUntilNextLearn = info.secsUntilNextLearn
if (secsUntilNextLearn >= SECONDS_PER_DAY) {
- return activity.getString(R.string.studyoptions_congrats_finished)
+ return activity.getString(CommonString.studyoptions_congrats_finished)
}
// https://github.com/ankitects/anki/blob/9b4dd54312de8798a3f2bee07892bb3a488d1f9b/ts/lib/tslib/time.ts#L22
val (unit, amount) =
@@ -242,7 +243,7 @@ class CongratsPage :
}
val nextLearnDue = TR.schedulingNextLearnDue(unit, round(amount).toInt())
- return activity.getString(R.string.studyoptions_congrats_next_due_in, nextLearnDue)
+ return activity.getString(CommonString.studyoptions_congrats_next_due_in, nextLearnDue)
}
fun DeckPicker.onDeckCompleted() {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/DeckOptions.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/DeckOptions.kt
index 4f926995fa37..001cbc7ed6cf 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/DeckOptions.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/DeckOptions.kt
@@ -19,6 +19,7 @@ import anki.deck_config.UpdateDeckConfigsRequest
import com.google.android.material.appbar.MaterialToolbar
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.ProgressContext
import com.ichi2.anki.R
import com.ichi2.anki.SingleFragmentActivity
@@ -360,7 +361,7 @@ suspend fun FragmentActivity.updateDeckConfigsRaw(input: ByteArray): ByteArray {
extractProgress = {
// TODO: Don't use the amount yet, unused as a progress indicator, and
// duplicates computeMemory's label
- text = this.toProgressText() ?: getString(R.string.dialog_processing)
+ text = this.toProgressText() ?: getString(CommonString.dialog_processing)
},
) {
withContext(Dispatchers.IO) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt
index 1eed8665ac43..19febacae9d0 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt
@@ -96,9 +96,20 @@ class ImageOcclusion : PageFragment(R.layout.page_image_occlusion) {
) {
super.onPageFinished(view, url)
viewModel.args.toImageOcclusionMode().let { options ->
- view?.evaluateJavascript("globalThis.anki.imageOcclusion.mode = $options") {
- super.onPageFinished(view, url)
- }
+ view?.evaluateJavascript(
+ """
+ (() => {
+ const mode = $options;
+ if (mode.kind === "add") {
+ mode.notetypeId = BigInt(mode.notetypeId);
+ } else {
+ mode.noteId = BigInt(mode.noteId);
+ }
+ globalThis.anki.imageOcclusion.mode = mode;
+ })();
+ """.trimIndent(),
+ null,
+ )
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageChromeClient.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageChromeClient.kt
index b9c7f8e1e9ed..5ea668d65697 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageChromeClient.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PageChromeClient.kt
@@ -7,7 +7,7 @@ import android.webkit.JsResult
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.appcompat.app.AlertDialog
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.utils.cancelable
import com.ichi2.utils.message
@@ -27,7 +27,7 @@ open class PageChromeClient : WebChromeClient() {
try {
AlertDialog.Builder(view.context).show {
message?.let { message(text = message) }
- positiveButton(R.string.dialog_ok) { result?.confirm() }
+ positiveButton(CommonString.dialog_ok) { result?.confirm() }
setOnCancelListener { result?.cancel() }
}
} catch (e: IllegalStateException) {
@@ -54,8 +54,8 @@ open class PageChromeClient : WebChromeClient() {
try {
AlertDialog.Builder(view.context).show {
message?.let { message(text = message) }
- positiveButton(R.string.dialog_ok) { result?.confirm() }
- negativeButton(R.string.dialog_cancel) { result?.cancel() }
+ positiveButton(CommonString.dialog_ok) { result?.confirm() }
+ negativeButton(CommonString.dialog_cancel) { result?.cancel() }
cancelable(false)
}
} catch (e: WindowManager.BadTokenException) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt
index cd16905ad9be..1b07de5f91f0 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/PostRequestHandler.kt
@@ -147,6 +147,12 @@ val uiMethods =
},
"importCsv" to { bytes -> lifecycleScope.async { importCsvRaw(bytes) } },
"importAnkiPackage" to { bytes -> lifecycleScope.async { importAnkiPackageUndoable(bytes) } },
+ "importDialogRequireClose" to { _ ->
+ lifecycleScope.async {
+ finish()
+ byteArrayOf()
+ }
+ },
"addImageOcclusionNote" to { bytes ->
lifecycleScope.async {
withCol { addImageOcclusionNoteRaw(bytes) }
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/RemoveAccountFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/RemoveAccountFragment.kt
index 14c5c7f986a5..dd1f24715d6c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/RemoveAccountFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/RemoveAccountFragment.kt
@@ -13,6 +13,7 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import com.google.android.material.appbar.MaterialToolbar
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.utils.bottomCornerClearance
@@ -77,7 +78,7 @@ class RemoveAccountFragment :
webViewLayout = view.findViewById(R.id.webview_layout)
setupWebView()
view.findViewById(R.id.toolbar)?.apply {
- title = getString(R.string.remove_account)
+ title = getString(CommonString.remove_account)
setNavigationOnClickListener {
requireActivity().onBackPressedDispatcher.onBackPressed()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt
index bb2b1b3bf5f1..0ccdbc002a96 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/Statistics.kt
@@ -10,6 +10,7 @@ import android.view.View
import androidx.core.content.ContextCompat.getSystemService
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.time.TimeManager
import com.ichi2.anki.common.time.getTimestamp
@@ -71,7 +72,7 @@ class Statistics : PageFragment(R.layout.page_statistics) {
private fun exportWebViewContentAsPDF() {
if (pendingPrintJob?.isActive == true) {
Timber.w("Duplicate print attempted - skipping")
- showSnackbar(R.string.already_in_progress)
+ showSnackbar(CommonString.already_in_progress)
return
}
Timber.i("Saving Stats to PDF")
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModel.kt
index 0974d95d615e..0219fa167b7a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModel.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModel.kt
@@ -48,6 +48,7 @@ sealed class ImageOcclusionArgs : Parcelable {
/**
* A [JSONObject] containing options for loading the [image occlusion page][ImageOcclusion].
* This includes the type of operation ("add" or "edit"), and relevant IDs and paths.
+ * IDs are decimal strings so JavaScript can convert them to bigint without losing precision.
*
* See 'IOMode' in https://github.com/ankitects/anki/blob/main/ts/routes/image-occlusion/lib.ts
*/
@@ -57,12 +58,12 @@ sealed class ImageOcclusionArgs : Parcelable {
JSONObject().also {
it.put("kind", "add")
it.put("imagePath", this.imagePath)
- it.put("notetypeId", this.noteTypeId)
+ it.put("notetypeId", this.noteTypeId.toString())
}
is Edit ->
JSONObject().also {
it.put("kind", "edit")
- it.put("noteId", this.noteId)
+ it.put("noteId", this.noteId.toString())
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AboutFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AboutFragment.kt
index 61f8ee0197c8..19cfffca2ad1 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AboutFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AboutFragment.kt
@@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.BuildConfig
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.destinations.ChangelogDestination
import com.ichi2.anki.common.destinations.navigate
@@ -67,7 +68,7 @@ class AboutFragment : Fragment(R.layout.fragment_about) {
val contributorsLink = getString(R.string.link_contributors)
val contributingGuideLink = getString(R.string.link_contribution)
binding.contributorsDescription.apply {
- text = getString(R.string.about_contributors_description, contributorsLink, contributingGuideLink).parseAsHtml()
+ text = getString(CommonString.about_contributors_description, contributorsLink, contributingGuideLink).parseAsHtml()
movementMethod = LinkMovementMethod.getInstance()
}
@@ -79,8 +80,8 @@ class AboutFragment : Fragment(R.layout.fragment_about) {
binding.licenseDescription.apply {
text =
(
- getString(R.string.license_description, gplLicenseLink, agplLicenseLink, sourceCodeLink) + "
" +
- getString(R.string.other_licenses, dependencyLicenseLink)
+ getString(CommonString.license_description, gplLicenseLink, agplLicenseLink, sourceCodeLink) + "
" +
+ getString(CommonString.other_licenses, dependencyLicenseLink)
).parseAsHtml()
movementMethod = LinkMovementMethod.getInstance()
}
@@ -89,7 +90,7 @@ class AboutFragment : Fragment(R.layout.fragment_about) {
if (BuildConfig.SHOW_DONATE_LINKS) {
val donateLink = getString(R.string.link_opencollective_donate)
binding.donateDescription.apply {
- text = getString(R.string.donate_description, donateLink).parseAsHtml()
+ text = getString(CommonString.donate_description, donateLink).parseAsHtml()
movementMethod = LinkMovementMethod.getInstance()
}
} else {
@@ -120,7 +121,7 @@ class AboutFragment : Fragment(R.layout.fragment_about) {
}
requireContext().copyToClipboard(
TruncatedString.from(debugInfo),
- failureMessageId = R.string.about_ankidroid_error_copy_debug_info,
+ failureMessageId = CommonString.about_ankidroid_error_copy_debug_info,
)
}
}
@@ -149,11 +150,11 @@ class AboutFragment : Fragment(R.layout.fragment_about) {
*/
fun showEnableDeveloperOptionsDialog(context: Context) {
AlertDialog.Builder(context).show {
- setTitle(R.string.dev_options_enabled_pref)
+ setTitle(CommonString.dev_options_enabled_pref)
setIcon(R.drawable.ic_warning)
- setMessage(R.string.dev_options_warning)
- setPositiveButton(R.string.dialog_ok) { _, _ -> enableDeveloperOptions(context) }
- setNegativeButton(R.string.dialog_cancel) { _, _ -> clickCount = 0 }
+ setMessage(CommonString.dev_options_warning)
+ setPositiveButton(CommonString.dialog_ok) { _, _ -> enableDeveloperOptions(context) }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> clickCount = 0 }
setCancelable(false)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AdvancedSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AdvancedSettingsFragment.kt
index c1bc2b7ff23f..830214ad5b65 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AdvancedSettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AdvancedSettingsFragment.kt
@@ -9,10 +9,10 @@ import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
-import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.MetaDB
import com.ichi2.anki.R
@@ -25,6 +25,7 @@ import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.startup.getDefaultAnkiDroidDirectory
import com.ichi2.anki.utils.openUrl
+import com.ichi2.preferences.ExternalDirectorySelectionPreference
import com.ichi2.utils.Permissions
import com.ichi2.utils.Permissions.openAppSettingsScreen
import com.ichi2.utils.show
@@ -49,12 +50,12 @@ class AdvancedSettingsFragment : SettingsFragment() {
}
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.permission_denied)
- setMessage(R.string.microphone_permission_denied_message)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setTitle(CommonString.permission_denied)
+ setMessage(CommonString.microphone_permission_denied_message)
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
openAppSettingsScreen()
}
- setNegativeButton(R.string.dialog_cancel, null)
+ setNegativeButton(CommonString.dialog_cancel, null)
}
}
@@ -62,7 +63,7 @@ class AdvancedSettingsFragment : SettingsFragment() {
removeUnnecessaryAdvancedPrefs()
// Check that input is valid before committing change in the collection path
- requirePreference(CollectionHelper.PREF_COLLECTION_PATH).apply {
+ requirePreference(CollectionHelper.PREF_COLLECTION_PATH).apply {
setOnPreferenceChangeListener { _, newValue: Any? ->
val newPath = newValue as String
try {
@@ -78,10 +79,10 @@ class AdvancedSettingsFragment : SettingsFragment() {
// TODO: Request MANAGE_EXTERNAL_STORAGE
Timber.e(e, "Could not initialize directory: %s", newPath)
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.dialog_collection_path_not_dir)
- setPositiveButton(R.string.dialog_ok) { _, _ -> }
- setNegativeButton(R.string.reset_custom_buttons) { _, _ ->
- text = getDefaultAnkiDroidDirectory(requireContext()).absolutePath
+ setTitle(CommonString.dialog_collection_path_not_dir)
+ setPositiveButton(CommonString.dialog_ok) { _, _ -> }
+ setNegativeButton(CommonString.reset_custom_buttons) { _, _ ->
+ value = getDefaultAnkiDroidDirectory(requireContext()).absolutePath
}
}
false
@@ -94,13 +95,13 @@ class AdvancedSettingsFragment : SettingsFragment() {
if (!(isChecked as Boolean)) return@setOnPreferenceChangeListener true
AlertDialog.Builder(requireContext()).show {
setIcon(R.drawable.ic_warning)
- setMessage(R.string.readtext_deprecation_warn)
- setNegativeButton(R.string.dialog_cancel) { _, _ -> ttsPref.isChecked = false }
- setNeutralButton(R.string.scoped_storage_learn_more) { _, _ ->
+ setMessage(CommonString.readtext_deprecation_warn)
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> ttsPref.isChecked = false }
+ setNeutralButton(CommonString.scoped_storage_learn_more) { _, _ ->
ttsPref.isChecked = false
requireContext().openUrl(R.string.link_tts)
}
- setPositiveButton(R.string.dialog_ok) { _, _ -> }
+ setPositiveButton(CommonString.dialog_ok) { _, _ -> }
setOnCancelListener { ttsPref.isChecked = false }
}
return@setOnPreferenceChangeListener true
@@ -109,15 +110,15 @@ class AdvancedSettingsFragment : SettingsFragment() {
// Configure "Reset languages" preference
requirePreference(R.string.pref_reset_languages_key).setOnPreferenceClickListener {
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.reset_languages)
+ setTitle(CommonString.reset_languages)
setIcon(R.drawable.ic_warning)
- setMessage(R.string.reset_languages_question)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setMessage(CommonString.reset_languages_question)
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
if (MetaDB.resetLanguages(requireContext())) {
- showSnackbar(R.string.reset_confirmation)
+ showSnackbar(CommonString.reset_confirmation)
}
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
false
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt
index a8aa66aee987..b737c620a86a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/AppearanceSettingsFragment.kt
@@ -12,6 +12,7 @@ import androidx.preference.Preference
import androidx.preference.SwitchPreferenceCompat
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.anki.common.utils.android.systemIsInNightMode
@@ -52,7 +53,7 @@ class AppearanceSettingsFragment : SettingsFragment() {
backgroundImageResultLauncher.launch("image/*")
} catch (ex: ActivityNotFoundException) {
Timber.w(ex, "No app found to handle background preference change request")
- activity?.showSnackbar(R.string.activity_start_failed)
+ activity?.showSnackbar(CommonString.activity_start_failed)
}
true
}
@@ -103,16 +104,16 @@ class AppearanceSettingsFragment : SettingsFragment() {
private fun showRemoveBackgroundImageDialog() {
AlertDialog.Builder(requireContext()).show {
- title(R.string.remove_background_image)
- positiveButton(R.string.dialog_remove) {
+ title(CommonString.remove_background_image)
+ positiveButton(CommonString.dialog_remove) {
if (BackgroundImage.remove(requireContext())) {
- showSnackbar(R.string.background_image_removed)
+ showSnackbar(CommonString.background_image_removed)
updateRemoveBackgroundVisibility()
} else {
- showSnackbar(R.string.error_deleting_image)
+ showSnackbar(CommonString.error_deleting_image)
}
}
- negativeButton(R.string.dialog_keep)
+ negativeButton(CommonString.dialog_keep)
}
}
@@ -176,7 +177,7 @@ class AppearanceSettingsFragment : SettingsFragment() {
if (BackgroundImage.shouldBeShown(requireContext())) {
showRemoveBackgroundImageDialog()
} else {
- showSnackbar(R.string.no_image_selected)
+ showSnackbar(CommonString.no_image_selected)
}
return@registerForActivityResult
}
@@ -184,12 +185,12 @@ class AppearanceSettingsFragment : SettingsFragment() {
try {
when (val sizeResult = BackgroundImage.validateBackgroundImageFileSize(this, selectedImage)) {
is FileSizeResult.FileTooLarge -> {
- showThemedToast(requireContext(), getString(R.string.image_max_size_allowed, sizeResult.maxMB), false)
+ showThemedToast(requireContext(), getString(CommonString.image_max_size_allowed, sizeResult.maxMB), false)
}
is FileSizeResult.UncompressedBitmapTooLarge -> {
showThemedToast(
requireContext(),
- getString(R.string.image_dimensions_too_large, sizeResult.width, sizeResult.height),
+ getString(CommonString.image_dimensions_too_large, sizeResult.width, sizeResult.height),
false,
)
}
@@ -200,10 +201,10 @@ class AppearanceSettingsFragment : SettingsFragment() {
}
} catch (e: OutOfMemoryError) {
Timber.w(e)
- showSnackbar(getString(R.string.error_selecting_image, e.localizedMessage))
+ showSnackbar(getString(CommonString.error_selecting_image, e.localizedMessage))
} catch (e: Exception) {
Timber.w(e)
- showSnackbar(getString(R.string.error_selecting_image, e.localizedMessage))
+ showSnackbar(getString(CommonString.error_selecting_image, e.localizedMessage))
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/ControlsSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/ControlsSettingsFragment.kt
index 3965651b85f1..57274fa25d31 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/ControlsSettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/ControlsSettingsFragment.kt
@@ -13,6 +13,7 @@ import androidx.preference.children
import com.bytehamster.lib.preferencesearch.SearchPreferenceResult
import com.google.android.material.tabs.TabLayout
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.cardviewer.ViewerCommand
import com.ichi2.anki.common.annotations.NeedsTest
@@ -26,6 +27,8 @@ import com.ichi2.anki.ui.internationalization.sentenceCase
import com.ichi2.anki.utils.ext.sharedPrefs
import com.ichi2.preferences.ControlPreference
import com.ichi2.preferences.ReviewerControlPreference
+import com.ichi2.utils.negativeButton
+import com.ichi2.utils.positiveButton
import com.ichi2.utils.show
import kotlinx.coroutines.launch
import timber.log.Timber
@@ -132,31 +135,31 @@ class ControlsSettingsFragment :
it.dialogTitle = preferenceTitle
}
findPreference(getString(R.string.toggle_whiteboard_command_key))?.let {
- it.title = TR.sentenceCase.gestureToggleWhiteboard(getString(R.string.gesture_toggle_whiteboard))
+ it.title = TR.sentenceCase.gestureToggleWhiteboard(getString(CommonString.gesture_toggle_whiteboard))
}
findPreference(getString(R.string.flag_red_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagRed(getString(R.string.gesture_flag_red))
+ it.title = TR.sentenceCase.gestureFlagRed(getString(CommonString.gesture_flag_red))
}
findPreference(getString(R.string.flag_orange_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagOrange(getString(R.string.gesture_flag_orange))
+ it.title = TR.sentenceCase.gestureFlagOrange(getString(CommonString.gesture_flag_orange))
}
findPreference(getString(R.string.flag_green_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagGreen(getString(R.string.gesture_flag_green))
+ it.title = TR.sentenceCase.gestureFlagGreen(getString(CommonString.gesture_flag_green))
}
findPreference(getString(R.string.flag_blue_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagBlue(getString(R.string.gesture_flag_blue))
+ it.title = TR.sentenceCase.gestureFlagBlue(getString(CommonString.gesture_flag_blue))
}
findPreference(getString(R.string.flag_pink_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagPink(getString(R.string.gesture_flag_pink))
+ it.title = TR.sentenceCase.gestureFlagPink(getString(CommonString.gesture_flag_pink))
}
findPreference(getString(R.string.flag_turquoise_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagTurquoise(getString(R.string.gesture_flag_turquoise))
+ it.title = TR.sentenceCase.gestureFlagTurquoise(getString(CommonString.gesture_flag_turquoise))
}
findPreference(getString(R.string.flag_purple_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagPurple(getString(R.string.gesture_flag_purple))
+ it.title = TR.sentenceCase.gestureFlagPurple(getString(CommonString.gesture_flag_purple))
}
findPreference(getString(R.string.remove_flag_command_key))?.let {
- it.title = TR.sentenceCase.gestureFlagRemove(getString(R.string.gesture_flag_remove))
+ it.title = TR.sentenceCase.gestureFlagRemove(getString(CommonString.gesture_flag_remove))
}
findPreference(getString(R.string.bury_card_command_key))?.let {
it.title = TR.sentenceCase.buryCard
@@ -238,23 +241,28 @@ class ControlsSettingsFragment :
)
for (key in answerCommandKeys) {
(findPreference(key) as? ReviewerControlPreference)?.let { answerPref ->
- val items =
- arrayOf(
- getString(R.string.only_answer),
- getString(R.string.flip_and_answer),
- )
answerPref.setOnBindingSelectedListener { binding ->
+ val isAlreadyAssigned =
+ showAnswerPref?.getMappableBindings()?.any {
+ it.binding == binding &&
+ (it.side == CardSide.QUESTION || it.side == CardSide.BOTH)
+ } == true
+ if (isAlreadyAssigned) {
+ answerPref.addBinding(binding, CardSide.ANSWER)
+ return@setOnBindingSelectedListener true
+ }
+
AlertDialog.Builder(requireContext()).show {
setTitle(answerPref.title)
setIcon(answerPref.icon)
- setItems(items) { _, index ->
- when (index) {
- 0 -> answerPref.addBinding(binding, CardSide.ANSWER)
- 1 -> {
- answerPref.addBinding(binding, CardSide.ANSWER)
- showAnswerPref?.addBinding(binding, CardSide.QUESTION)
- }
- }
+ setMessage(getString(CommonString.also_assign_binding_to_show_answer, getString(CommonString.show_answer)))
+
+ positiveButton(text = getString(CommonString.dialog_assign)) {
+ answerPref.addBinding(binding, CardSide.ANSWER)
+ showAnswerPref?.addBinding(binding, CardSide.QUESTION)
+ }
+ negativeButton(text = getString(CommonString.dialog_skip)) {
+ answerPref.addBinding(binding, CardSide.ANSWER)
}
}
true
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomButtonsSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomButtonsSettingsFragment.kt
index 61cd0fcd36b6..529295ece888 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomButtonsSettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomButtonsSettingsFragment.kt
@@ -8,6 +8,7 @@ import androidx.core.content.edit
import androidx.preference.ListPreference
import androidx.preference.Preference
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.preferences.sharedPrefs
import com.ichi2.anki.ui.internationalization.sentenceCase
@@ -30,8 +31,8 @@ class CustomButtonsSettingsFragment : SettingsFragment() {
resetCustomButtons.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
AlertDialog.Builder(requireContext()).show {
- title(R.string.reset_settings_to_default)
- positiveButton(R.string.reset) {
+ title(CommonString.reset_settings_to_default)
+ positiveButton(CommonString.reset) {
// Reset the settings to default
requireContext().sharedPrefs().edit {
allKeys().forEach {
@@ -43,7 +44,7 @@ class CustomButtonsSettingsFragment : SettingsFragment() {
addPreferencesFromResource(preferenceResource)
initSubscreen()
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
true
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt
index 703d64c098fc..4c6c3da7cd1f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/CustomSyncServerSettingsFragment.kt
@@ -7,6 +7,7 @@ import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.SyncPreferences
import com.ichi2.anki.snackbar.showSnackbar
@@ -36,14 +37,14 @@ class CustomSyncServerSettingsFragment : SettingsFragment() {
// Empty string input causes the certificate to be unset in the backend, i.e., no error
if (!CollectionManager.updateCustomCertificate(newCert)) {
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.dialog_invalid_custom_certificate_title)
- setMessage(R.string.dialog_invalid_custom_certificate)
- setPositiveButton(R.string.dialog_ok) { _, _ -> }
+ setTitle(CommonString.dialog_invalid_custom_certificate_title)
+ setMessage(CommonString.dialog_invalid_custom_certificate)
+ setPositiveButton(CommonString.dialog_ok) { _, _ -> }
}
return@setOnPreferenceChangeListener false
}
- showSnackbar(R.string.dialog_updated_custom_certificate)
+ showSnackbar(CommonString.dialog_updated_custom_certificate)
true
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DeveloperOptionsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DeveloperOptionsFragment.kt
index 6471b8472342..e1e0aa2d940f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DeveloperOptionsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/DeveloperOptionsFragment.kt
@@ -10,6 +10,7 @@ import androidx.preference.SwitchPreferenceCompat
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.BuildConfig
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnkiDroidUsageAnalytics
import com.ichi2.anki.common.crashreporting.CrashReportService
@@ -97,7 +98,7 @@ class DeveloperOptionsFragment : SettingsFragment() {
"This will most likely make it so that you cannot access your collection. " +
"It will be very difficult to recover your data.",
)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
Timber.w("Setting collection path to /storage/emulated/0/AnkiDroid")
AnkiDroidApp.sharedPrefs().edit {
putString(
@@ -106,7 +107,7 @@ class DeveloperOptionsFragment : SettingsFragment() {
)
}
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
false
}
@@ -134,7 +135,7 @@ class DeveloperOptionsFragment : SettingsFragment() {
setPositiveButton("OK") { _, _ ->
generateNotes(numberOfNotes)
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
true
}
@@ -160,7 +161,7 @@ class DeveloperOptionsFragment : SettingsFragment() {
setPositiveButton("OK") { _, _ ->
generateFiles(sizeOfFiles, numberOfFiles)
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
true
}
@@ -274,13 +275,13 @@ class DeveloperOptionsFragment : SettingsFragment() {
*/
private fun showDisableDeveloperOptionsDialog() {
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.disable_dev_options)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setTitle(CommonString.disable_dev_options)
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
Prefs.isDeveloperOptionsEnabled = false
parentFragmentManager.popBackStack()
ActivityCompat.recreate(requireActivity())
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/GeneralSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/GeneralSettingsFragment.kt
index 01d26b1d4402..4b53b4c28ee1 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/GeneralSettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/GeneralSettingsFragment.kt
@@ -10,6 +10,7 @@ import anki.config.ConfigKey
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.utils.ext.AddingDefaultsMode
@@ -56,11 +57,12 @@ class GeneralSettingsFragment : SettingsFragment() {
}
// Anki card context menu
requirePreference(R.string.anki_card_external_context_menu_key).apply {
- title = getString(R.string.card_browser_enable_external_context_menu, getString(R.string.context_menu_anki_card_label))
+ title =
+ getString(CommonString.card_browser_enable_external_context_menu, getString(CommonString.context_menu_anki_card_label))
summary =
getString(
- R.string.card_browser_enable_external_context_menu_summary,
- getString(R.string.context_menu_anki_card_label),
+ CommonString.card_browser_enable_external_context_menu_summary,
+ getString(CommonString.context_menu_anki_card_label),
)
setOnPreferenceChangeListener { newValue ->
AnkiCardContextMenu.ensureConsistentStateWithPreferenceStatus(requireContext(), newValue)
@@ -68,8 +70,12 @@ class GeneralSettingsFragment : SettingsFragment() {
}
// Card browser context menu
requirePreference(R.string.card_browser_external_context_menu_key).apply {
- title = getString(R.string.card_browser_enable_external_context_menu, getString(R.string.card_browser_context_menu))
- summary = getString(R.string.card_browser_enable_external_context_menu_summary, getString(R.string.card_browser_context_menu))
+ title = getString(CommonString.card_browser_enable_external_context_menu, getString(CommonString.card_browser_context_menu))
+ summary =
+ getString(
+ CommonString.card_browser_enable_external_context_menu_summary,
+ getString(CommonString.card_browser_context_menu),
+ )
setOnPreferenceChangeListener { newValue ->
CardBrowserContextMenu.ensureConsistentStateWithPreferenceStatus(requireContext(), newValue)
}
@@ -80,7 +86,7 @@ class GeneralSettingsFragment : SettingsFragment() {
val sortedLanguages = LanguageUtil.APP_LANGUAGES.toSortedMap(java.lang.String.CASE_INSENSITIVE_ORDER)
val systemLocale = getSystemLocale()
requirePreference(R.string.pref_language_key).apply {
- entries = arrayOf(getStringByLocale(R.string.language_system, systemLocale), *sortedLanguages.keys.toTypedArray())
+ entries = arrayOf(getStringByLocale(CommonString.language_system, systemLocale), *sortedLanguages.keys.toTypedArray())
entryValues = arrayOf(LanguageUtil.SYSTEM_LANGUAGE_TAG, *sortedLanguages.values.toTypedArray())
setOnPreferenceChangeListener { selectedLanguage ->
LanguageUtil.setDefaultBackendLanguages(selectedLanguage)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/HeaderFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/HeaderFragment.kt
index dcf28fd8a9b4..577a709efd02 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/HeaderFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/HeaderFragment.kt
@@ -12,6 +12,7 @@ import com.bytehamster.lib.preferencesearch.SearchConfiguration
import com.bytehamster.lib.preferencesearch.SearchPreference
import com.ichi2.anki.BuildConfig
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.android.AdaptionUtil
import com.ichi2.anki.compat.CompatHelper
@@ -40,7 +41,7 @@ class HeaderFragment : SettingsFragment() {
requirePreference(R.string.pref_sync_screen_key).summary =
HeaderPreference.buildHeaderSummary(
TR.sentenceCase.ankiWebAccount,
- getString(R.string.automatic_sync_choice),
+ getString(CommonString.automatic_sync_choice),
)
requirePreference(R.string.pref_advanced_screen_key).apply {
@@ -98,14 +99,14 @@ class HeaderFragment : SettingsFragment() {
index(R.xml.preferences_reviewing)
index(R.xml.preferences_sync)
index(R.xml.preferences_custom_sync_server)
- .addBreadcrumb(R.string.pref_cat_sync)
+ .addBreadcrumb(CommonString.pref_cat_sync)
if (Prefs.newReviewRemindersEnabled) {
indexItem()
.withKey(activity.getString(R.string.pref_review_reminders_screen_key))
- .withTitle(activity.getString(R.string.review_reminders_title))
+ .withTitle(activity.getString(CommonString.review_reminders_title))
.withResId(R.xml.preferences_review_reminders)
- .withSummary(activity.getString(R.string.notification_pref))
+ .withSummary(activity.getString(CommonString.notification_pref))
} else {
index(R.xml.preferences_notifications)
}
@@ -117,11 +118,11 @@ class HeaderFragment : SettingsFragment() {
}
index(R.xml.preferences_controls)
index(R.xml.preferences_reviewer_controls)
- .addBreadcrumb(activity.getString(R.string.pref_cat_controls))
- .addBreadcrumb(activity.getString(R.string.pref_controls_reviews_tab))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_controls))
+ .addBreadcrumb(activity.getString(CommonString.pref_controls_reviews_tab))
index(R.xml.preferences_previewer_controls)
- .addBreadcrumb(activity.getString(R.string.pref_cat_controls))
- .addBreadcrumb(activity.getString(R.string.pref_controls_previews_tab))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_controls))
+ .addBreadcrumb(activity.getString(CommonString.pref_controls_previews_tab))
index(R.xml.preferences_accessibility)
index(R.xml.preferences_backup_limits)
ignorePreference(activity.getString(R.string.pref_backups_help_key))
@@ -129,40 +130,40 @@ class HeaderFragment : SettingsFragment() {
.withKey(activity.getString(R.string.reschedule_command_key))
.withTitle(setDuePreferenceTitle)
.withResId(R.xml.preferences_controls)
- .addBreadcrumb(activity.getString(R.string.pref_cat_controls))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_controls))
.addBreadcrumb(setDuePreferenceTitle)
// Some strings can't be indexed from the XML document as they are loaded from the back-end. We add them manually.
indexItem()
.withKey(activity.getString(R.string.anki_card_external_context_menu_key))
.withTitle(
activity.getString(
- R.string.card_browser_enable_external_context_menu,
- activity.getString(R.string.context_menu_anki_card_label),
+ CommonString.card_browser_enable_external_context_menu,
+ activity.getString(CommonString.context_menu_anki_card_label),
),
).withSummary(
activity.getString(
- R.string.card_browser_enable_external_context_menu_summary,
- activity.getString(R.string.context_menu_anki_card_label),
+ CommonString.card_browser_enable_external_context_menu_summary,
+ activity.getString(CommonString.context_menu_anki_card_label),
),
).withResId(R.xml.preferences_general)
- .addBreadcrumb(activity.getString(R.string.pref_cat_general))
- .addBreadcrumb(activity.getString(R.string.pref_cat_system_wide))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_general))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_system_wide))
indexItem()
.withKey(activity.getString(R.string.card_browser_external_context_menu_key))
.withTitle(
activity.getString(
- R.string.card_browser_enable_external_context_menu,
- activity.getString(R.string.card_browser_context_menu),
+ CommonString.card_browser_enable_external_context_menu,
+ activity.getString(CommonString.card_browser_context_menu),
),
).withSummary(
activity.getString(
- R.string.card_browser_enable_external_context_menu_summary,
- activity.getString(R.string.card_browser_context_menu),
+ CommonString.card_browser_enable_external_context_menu_summary,
+ activity.getString(CommonString.card_browser_context_menu),
),
).withResId(R.xml.preferences_general)
- .addBreadcrumb(activity.getString(R.string.pref_cat_general))
- .addBreadcrumb(activity.getString(R.string.pref_cat_system_wide))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_general))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_system_wide))
if (!Prefs.isNewStudyScreenEnabled) {
indexItem()
@@ -171,17 +172,17 @@ class HeaderFragment : SettingsFragment() {
TR.preferencesShowPlayButtonsOnCardsWith(),
).withResId(R.xml.preferences_appearance)
.addBreadcrumb(TR.preferencesAppearance())
- .addBreadcrumb(activity.getString(R.string.pref_cat_reviewer))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_reviewer))
}
indexItem()
.withKey(activity.getString(R.string.one_way_sync_key))
.withTitle(
- activity.getString(R.string.one_way_sync_title),
+ activity.getString(CommonString.one_way_sync_title),
).withSummary(TR.preferencesOnNextSyncForceChangesIn())
.withResId(R.xml.preferences_sync)
- .addBreadcrumb(activity.getString(R.string.pref_cat_sync))
- .addBreadcrumb(activity.getString(R.string.pref_cat_advanced))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_sync))
+ .addBreadcrumb(activity.getString(CommonString.pref_cat_advanced))
}
// Some preferences and categories are only shown conditionally,
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt
index b003c02fce4c..064b17805c67 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/SyncSettingsFragment.kt
@@ -8,6 +8,7 @@ import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.account.AccountActivity
import com.ichi2.anki.common.crashreporting.runCatchingWithReport
@@ -43,7 +44,7 @@ class SyncSettingsFragment : SettingsFragment() {
Preference.SummaryProvider {
runCatchingWithReport("network_timeout") {
TR.qtMiscSecond(this.getValue())
- }.getOrNull() ?: getString(R.string.pref__etc__summary__error)
+ }.getOrNull() ?: getString(CommonString.pref__etc__summary__error)
}
}
@@ -52,15 +53,15 @@ class SyncSettingsFragment : SettingsFragment() {
setSummary(TR.preferencesOnNextSyncForceChangesIn())
setOnPreferenceClickListener {
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.one_way_sync_title)
+ setTitle(CommonString.one_way_sync_title)
setMessage(TR.preferencesOnNextSyncForceChangesIn())
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
launchCatchingTask {
withCol { modSchema(check = false) }
- showSnackbar(R.string.one_way_sync_confirmation, Snackbar.LENGTH_SHORT)
+ showSnackbar(CommonString.one_way_sync_confirmation, Snackbar.LENGTH_SHORT)
}
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
false
}
@@ -77,14 +78,14 @@ class SyncSettingsFragment : SettingsFragment() {
// Custom sync server
requirePreference(R.string.custom_sync_server_key).setSummaryProvider {
- customSyncBase() ?: getString(R.string.custom_sync_server_summary_none_of_the_two_servers_used)
+ customSyncBase() ?: getString(CommonString.custom_sync_server_summary_none_of_the_two_servers_used)
}
}
private fun updateSyncAccountSummary() {
requirePreference(R.string.sync_account_key)
.summary =
- Prefs.username.ifNullOrEmpty { getString(R.string.sync_account_summ_logged_out) }
+ Prefs.username.ifNullOrEmpty { getString(CommonString.sync_account_summ_logged_out) }
}
private fun updateOneWaySyncEnabledState() {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/AddProfileDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/AddProfileDialog.kt
index d068e7936b6a..e1b2270088d1 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/AddProfileDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/AddProfileDialog.kt
@@ -6,7 +6,7 @@ package com.ichi2.anki.preferences.profiles
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.multiprofile.ProfileName
import com.ichi2.anki.multiprofile.ProfileName.ValidationResult
import com.ichi2.compose.theme.AnkiDroidTheme
@@ -24,13 +24,13 @@ fun AddProfileDialog(
onDismissRequest: () -> Unit,
onConfirm: (ProfileName) -> Unit,
) {
- val emptyError = stringResource(R.string.profile_name_empty_error)
- val tooLongError = stringResource(R.string.profile_name_too_long_error, ProfileName.MAX_LENGTH)
+ val emptyError = stringResource(CommonString.profile_name_empty_error)
+ val tooLongError = stringResource(CommonString.profile_name_too_long_error, ProfileName.MAX_LENGTH)
TextInputDialog(
- title = stringResource(R.string.add_profile),
- label = stringResource(R.string.profile_name),
- confirmText = stringResource(R.string.dialog_add),
+ title = stringResource(CommonString.add_profile),
+ label = stringResource(CommonString.profile_name),
+ confirmText = stringResource(CommonString.dialog_add),
onConfirm = { text ->
(ProfileName.validate(text) as? ValidationResult.Valid)?.let { onConfirm(it.name) }
},
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/SwitchProfilesScreen.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/SwitchProfilesScreen.kt
index 08aa9142e5fa..9df62ce27221 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/SwitchProfilesScreen.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/profiles/SwitchProfilesScreen.kt
@@ -29,6 +29,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.multiprofile.ProfileId
import com.ichi2.anki.multiprofile.ProfileName
@@ -59,7 +60,7 @@ fun SwitchProfilesScreen(
modifier = modifier,
topBar = {
TopAppBar(
- title = { Text(stringResource(R.string.switch_profile)) },
+ title = { Text(stringResource(CommonString.switch_profile)) },
navigationIcon = {
IconButton(onClick = onNavigateUp) {
Icon(
@@ -76,7 +77,7 @@ fun SwitchProfilesScreen(
icon = {
Icon(painterResource(R.drawable.ic_switch_profile), contentDescription = null)
},
- text = { Text(stringResource(R.string.add_profile)) },
+ text = { Text(stringResource(CommonString.add_profile)) },
)
},
) { contentPadding ->
@@ -152,13 +153,13 @@ private fun ProfileRow(
IconButton(onClick = onEditClick) {
Icon(
painterResource(R.drawable.ic_popup_menu_item_editor),
- contentDescription = stringResource(R.string.edit_profile),
+ contentDescription = stringResource(CommonString.edit_profile),
)
}
IconButton(onClick = onDeleteClick) {
Icon(
painterResource(R.drawable.ic_delete),
- contentDescription = stringResource(R.string.delete_profile),
+ contentDescription = stringResource(CommonString.delete_profile),
)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/MenuDisplayType.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/MenuDisplayType.kt
index 4de9f64cebe4..c986d0b87ef4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/MenuDisplayType.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/MenuDisplayType.kt
@@ -5,19 +5,19 @@ package com.ichi2.anki.preferences.reviewer
import android.view.MenuItem
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
enum class MenuDisplayType(
@StringRes val title: Int,
) {
/** Shows the action as [MenuItem.SHOW_AS_ACTION_ALWAYS] */
- ALWAYS(R.string.custom_buttons_setting_always_show),
+ ALWAYS(CommonString.custom_buttons_setting_always_show),
/** Shows the action as [MenuItem.SHOW_AS_ACTION_NEVER] */
- MENU_ONLY(R.string.custom_buttons_setting_menu_only),
+ MENU_ONLY(CommonString.custom_buttons_setting_menu_only),
/** Action isn't added to the menu */
- DISABLED(R.string.disabled),
+ DISABLED(CommonString.disabled),
;
@VisibleForTesting
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/ViewerAction.kt b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/ViewerAction.kt
index a8debcc48aca..0a7935847bbd 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/ViewerAction.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/preferences/reviewer/ViewerAction.kt
@@ -8,6 +8,7 @@ import android.view.KeyEvent
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.Flag
import com.ichi2.anki.R
import com.ichi2.anki.preferences.reviewer.MenuDisplayType.ALWAYS
@@ -250,32 +251,32 @@ enum class ViewerAction(
STATISTICS -> TR.statisticsTitle()
RESCHEDULE_NOTE -> TR.sentenceCase.setDueDate
PREVIOUS_CARD_INFO -> TR.sentenceCase.previousCardInfo
- UNDO -> getString(R.string.undo)
- REDO -> getString(R.string.redo)
+ UNDO -> getString(CommonString.undo)
+ REDO -> getString(CommonString.redo)
FLAG_MENU -> TR.browsingFlag()
MARK -> TR.sentenceCase.markNote
- EDIT -> getString(R.string.cardeditor_title_edit_card)
+ EDIT -> getString(CommonString.cardeditor_title_edit_card)
BURY_MENU -> TR.studyingBury()
SUSPEND_MENU -> TR.studyingSuspend()
DELETE -> TR.sentenceCase.deleteNote
- TOGGLE_WHITEBOARD -> getString(R.string.gesture_toggle_whiteboard)
+ TOGGLE_WHITEBOARD -> getString(CommonString.gesture_toggle_whiteboard)
DECK_OPTIONS -> TR.sentenceCase.deckOptions
CARD_INFO -> TR.sentenceCase.cardInfo
- ADD_NOTE -> getString(R.string.menu_add_note)
- TAG -> getString(R.string.menu_edit_tags)
- RESET_PROGRESS -> getString(R.string.card_editor_reset_card)
- TOGGLE_AUTO_ADVANCE -> getString(R.string.toggle_auto_advance)
- RECORD_VOICE -> getString(R.string.record_voice)
- PLAY_MEDIA -> getString(R.string.replay_media)
- USER_ACTION_1 -> getString(R.string.user_action_1)
- USER_ACTION_2 -> getString(R.string.user_action_2)
- USER_ACTION_3 -> getString(R.string.user_action_3)
- USER_ACTION_4 -> getString(R.string.user_action_4)
- USER_ACTION_5 -> getString(R.string.user_action_5)
- USER_ACTION_6 -> getString(R.string.user_action_6)
- USER_ACTION_7 -> getString(R.string.user_action_7)
- USER_ACTION_8 -> getString(R.string.user_action_8)
- USER_ACTION_9 -> getString(R.string.user_action_9)
+ ADD_NOTE -> getString(CommonString.menu_add_note)
+ TAG -> getString(CommonString.menu_edit_tags)
+ RESET_PROGRESS -> getString(CommonString.card_editor_reset_card)
+ TOGGLE_AUTO_ADVANCE -> getString(CommonString.toggle_auto_advance)
+ RECORD_VOICE -> getString(CommonString.record_voice)
+ PLAY_MEDIA -> getString(CommonString.replay_media)
+ USER_ACTION_1 -> getString(CommonString.user_action_1)
+ USER_ACTION_2 -> getString(CommonString.user_action_2)
+ USER_ACTION_3 -> getString(CommonString.user_action_3)
+ USER_ACTION_4 -> getString(CommonString.user_action_4)
+ USER_ACTION_5 -> getString(CommonString.user_action_5)
+ USER_ACTION_6 -> getString(CommonString.user_action_6)
+ USER_ACTION_7 -> getString(CommonString.user_action_7)
+ USER_ACTION_8 -> getString(CommonString.user_action_8)
+ USER_ACTION_9 -> getString(CommonString.user_action_9)
BURY_NOTE -> TR.sentenceCase.buryNote
BURY_CARD -> TR.sentenceCase.buryCard
SUSPEND_NOTE -> TR.sentenceCase.suspendNote
@@ -306,7 +307,7 @@ enum class ViewerAction(
PAGE_UP,
PAGE_DOWN,
EXIT,
- -> getString(R.string.empty_string)
+ -> getString(CommonString.empty_string)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt
index 6408902ae7bb..f9192b41de47 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/CardViewerFragment.kt
@@ -26,6 +26,7 @@ import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.ViewerResourceHandler
import com.ichi2.anki.compat.CompatHelper.Companion.resolveActivityCompat
@@ -96,10 +97,10 @@ abstract class CardViewerFragment(
if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.RECORD_AUDIO)) return
AlertDialog.Builder(requireContext()).show {
- title(R.string.permission_denied)
- message(R.string.microphone_permission_denied_message)
- positiveButton(R.string.dialog_ok) { openAppSettingsScreen() }
- negativeButton(R.string.dialog_cancel)
+ title(CommonString.permission_denied)
+ message(CommonString.microphone_permission_denied_message)
+ positiveButton(CommonString.dialog_ok) { openAppSettingsScreen() }
+ negativeButton(CommonString.dialog_cancel)
}
}
@@ -124,8 +125,8 @@ abstract class CardViewerFragment(
activeRequest = request
optInDialog =
AlertDialog.Builder(requireContext()).show {
- message(R.string.template_is_trying_to_record_audio)
- positiveButton(R.string.dialog_allow) {
+ message(CommonString.template_is_trying_to_record_audio)
+ positiveButton(CommonString.dialog_allow) {
if (canRecordAudio) {
activeRequest = null
Prefs.allowTemplatesToRecordAudio = true
@@ -135,7 +136,7 @@ abstract class CardViewerFragment(
microphonePermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
}
}
- negativeButton(R.string.dialog_cancel) { decline() }
+ negativeButton(CommonString.dialog_cancel) { decline() }
setOnCancelListener { decline() }
setOnDismissListener { optInDialog = null }
}
@@ -215,7 +216,7 @@ abstract class CardViewerFragment(
.onEach { errorMessage ->
AlertDialog
.Builder(requireContext())
- .setTitle(R.string.vague_error)
+ .setTitle(CommonString.vague_error)
.setMessage(errorMessage)
.show()
}.launchIn(lifecycleScope)
@@ -293,11 +294,11 @@ abstract class CardViewerFragment(
"intent" -> handleIntentUrl(url, Intent.URI_INTENT_SCHEME)
"missing-user-action" -> {
val actionNumber = url.toString().substringAfter(":")
- val message = getString(R.string.missing_user_action_dialog_message, actionNumber)
+ val message = getString(CommonString.missing_user_action_dialog_message, actionNumber)
AlertDialog.Builder(requireContext()).show {
setMessage(message)
- setPositiveButton(R.string.dialog_ok) { _, _ -> }
- setNeutralButton(R.string.help) { _, _ ->
+ setPositiveButton(CommonString.dialog_ok) { _, _ -> }
+ setNeutralButton(CommonString.help) { _, _ ->
openUrl(R.string.link_user_actions_help)
}
}
@@ -437,8 +438,8 @@ abstract class CardViewerFragment(
}
private fun showMediaErrorSnackbar(filename: String) {
- showSnackbar(getString(R.string.card_viewer_could_not_find_image, filename)) {
- setAction(R.string.help) { openUrl(R.string.link_faq_missing_media) }
+ showSnackbar(getString(CommonString.card_viewer_could_not_find_image, filename)) {
+ setAction(CommonString.help) { openUrl(R.string.link_faq_missing_media) }
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/PreviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/PreviewerFragment.kt
index a4ba135229f4..099d8fcdec13 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/PreviewerFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/PreviewerFragment.kt
@@ -19,6 +19,7 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.google.android.material.slider.Slider
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DispatchKeyEventListener
import com.ichi2.anki.Flag
import com.ichi2.anki.R
@@ -78,7 +79,7 @@ class PreviewerFragment :
val displayIndex = currentIndex + 1
binding.slider.value = displayIndex.toFloat()
binding.progressIndicator.text =
- getString(R.string.preview_progress_bar_text, displayIndex, cardsCount)
+ getString(CommonString.preview_progress_bar_text, displayIndex, cardsCount)
}
}
// ************************************* Menu items *************************************
@@ -100,7 +101,7 @@ class PreviewerFragment :
with(menu.findItem(R.id.action_mark)) {
if (isMarked) {
setIcon(R.drawable.ic_star)
- setTitle(R.string.menu_unmark_note)
+ setTitle(CommonString.menu_unmark_note)
} else {
setIcon(R.drawable.ic_star_border_white)
title = TR.sentenceCase.markNote
@@ -249,10 +250,10 @@ class PreviewerFragment :
menu.findItem(R.id.action_back_side_only).apply {
if (isBackSideOnly) {
setIcon(R.drawable.ic_card_answer)
- setTitle(R.string.card_side_answer)
+ setTitle(CommonString.card_side_answer)
} else {
setIconRes(requireContext(), R.drawable.ic_card_question)
- setTitle(R.string.card_side_both)
+ setTitle(CommonString.card_side_both)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt
index ec2020849cd7..32a727270cea 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerFragment.kt
@@ -6,6 +6,7 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.FragmentTemplatePreviewerBinding
import com.ichi2.anki.libanki.CardOrdinal
@@ -52,9 +53,9 @@ class TemplatePreviewerFragment :
.onEach { showingAnswer ->
binding.showAnswer.text =
if (showingAnswer) {
- getString(R.string.hide_answer)
+ getString(CommonString.hide_answer)
} else {
- getString(R.string.show_answer)
+ getString(CommonString.show_answer)
}
}.launchIn(lifecycleScope)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerPage.kt b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerPage.kt
index 9d222b097b32..4f53ef11af03 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerPage.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/previewer/TemplatePreviewerPage.kt
@@ -10,6 +10,7 @@ import androidx.core.os.BundleCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.commitNow
import androidx.lifecycle.lifecycleScope
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.FragmentTemplatePreviewerContainerBinding
import com.ichi2.anki.previewer.TemplatePreviewerFragment.Companion.ARG_KEY
@@ -51,7 +52,7 @@ class TemplatePreviewerPage : Fragment(R.layout.fragment_template_previewer_cont
for ((index, templateName) in viewModel.getTemplateNames().withIndex()) {
val tabTitle =
if (cardsWithEmptyFronts?.get(index) == true) {
- getString(R.string.card_previewer_empty_front_indicator, templateName)
+ getString(CommonString.card_previewer_empty_front_indicator, templateName)
} else {
templateName
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/receiver/CopyToClipboardReceiver.kt b/AnkiDroid/src/main/java/com/ichi2/anki/receiver/CopyToClipboardReceiver.kt
index 94e887b3bbe4..082f249bc146 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/receiver/CopyToClipboardReceiver.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/receiver/CopyToClipboardReceiver.kt
@@ -6,7 +6,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationManagerCompat
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.anki.notifications.NotificationId
import com.ichi2.utils.TruncatedString
@@ -26,7 +26,7 @@ class CopyToClipboardReceiver : BroadcastReceiver() {
val text =
intent.getStringExtra(EXTRA_SYNC_ERROR_LOG) ?: run {
Timber.w("CopyToClipboardReceiver: no error log found")
- showThemedToast(context, R.string.something_wrong, shortLength = true)
+ showThemedToast(context, CommonString.something_wrong, shortLength = true)
return
}
// only dismiss the notification once the text is safely on the clipboard
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ReviewerBinding.kt b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ReviewerBinding.kt
index 4c9043f39d30..9baca34f25e9 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ReviewerBinding.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ReviewerBinding.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.reviewer
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.CheckResult
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.cardviewer.Gesture
import com.ichi2.anki.cardviewer.ViewerCommand
import java.util.Objects
@@ -48,8 +48,8 @@ class ReviewerBinding(
override fun toDisplayString(context: Context): String {
val bindingString = binding.toDisplayString(context)
return when (side) {
- CardSide.QUESTION -> context.getString(R.string.display_binding_card_side_question, bindingString)
- CardSide.ANSWER -> context.getString(R.string.display_binding_card_side_answer, bindingString)
+ CardSide.QUESTION -> context.getString(CommonString.display_binding_card_side_question, bindingString)
+ CardSide.ANSWER -> context.getString(CommonString.display_binding_card_side_answer, bindingString)
CardSide.BOTH -> bindingString
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/AddEditReminderDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/AddEditReminderDialog.kt
index 4f6fd234cf1d..265d8c6aa04c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/AddEditReminderDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/AddEditReminderDialog.kt
@@ -19,6 +19,7 @@ import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.ALL_DECKS_ID
import com.ichi2.anki.databinding.DialogAddEditReminderBinding
@@ -94,11 +95,11 @@ class AddEditReminderDialog : DialogFragment() {
val dialogBuilder =
AlertDialog.Builder(requireActivity()).apply {
customView(binding.root)
- positiveButton(R.string.dialog_ok)
- neutralButton(R.string.dialog_cancel)
+ positiveButton(CommonString.dialog_ok)
+ neutralButton(CommonString.dialog_cancel)
if (dialogMode is DialogMode.Edit) {
- negativeButton(R.string.dialog_positive_delete)
+ negativeButton(CommonString.dialog_positive_delete)
}
}
val dialog = dialogBuilder.create()
@@ -154,8 +155,8 @@ class AddEditReminderDialog : DialogFragment() {
binding.addEditReminderToolbar.title =
getString(
when (dialogMode) {
- is DialogMode.Add -> R.string.add_review_reminder
- is DialogMode.Edit -> R.string.edit_review_reminder
+ is DialogMode.Add -> CommonString.add_review_reminder
+ is DialogMode.Edit -> CommonString.edit_review_reminder
},
)
}
@@ -236,8 +237,8 @@ class AddEditReminderDialog : DialogFragment() {
val value: Int? = text.toString().toIntOrNull()
binding.addEditReminderCardThresholdInputWrapper.error =
when {
- (value == null) -> getString(R.string.add_edit_reminder_card_threshold_error_not_a_number)
- (value < 0) -> getString(R.string.add_edit_reminder_card_threshold_error_negative)
+ (value == null) -> getString(CommonString.add_edit_reminder_card_threshold_error_not_a_number)
+ (value < 0) -> getString(CommonString.add_edit_reminder_card_threshold_error_negative)
else -> null
}
viewModel.setCardTriggerThreshold(value ?: 0)
@@ -289,7 +290,7 @@ class AddEditReminderDialog : DialogFragment() {
Timber.i("Submitted dialog")
// Do nothing if numerical fields are invalid
binding.addEditReminderCardThresholdInputWrapper.error?.let {
- binding.root.showSnackbar(R.string.something_wrong)
+ binding.root.showSnackbar(CommonString.something_wrong)
return
}
@@ -318,8 +319,8 @@ class AddEditReminderDialog : DialogFragment() {
val confirmationDialog = ConfirmationDialog()
confirmationDialog.setArgs(
- getString(R.string.add_edit_reminder_delete_confirmation_title),
- getString(R.string.add_edit_reminder_delete_confirmation_message),
+ getString(CommonString.add_edit_reminder_delete_confirmation_title),
+ getString(CommonString.add_edit_reminder_delete_confirmation_message),
)
confirmationDialog.setConfirm {
setFragmentResult(
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingFragment.kt
index 94873628523e..3db774bd2118 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingFragment.kt
@@ -32,6 +32,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.NotificationChannel
import com.ichi2.anki.R
import com.ichi2.anki.common.utils.android.getColorFromAttr
@@ -163,21 +164,21 @@ class ReminderTroubleshootingFragment : Fragment(R.layout.fragment_reminder_trou
Triple(
R.drawable.ic_cancel_24,
context.getColor(android.R.color.holo_red_dark),
- context.getString(R.string.reminder_troubleshooting_summary_error),
+ context.getString(CommonString.reminder_troubleshooting_summary_error),
)
SummaryStatus.Warning ->
Triple(
R.drawable.ic_warning_24,
getColorFromAttr(context, R.attr.reminderTroubleshootingWarning),
- context.getString(R.string.reminder_troubleshooting_summary_warning),
+ context.getString(CommonString.reminder_troubleshooting_summary_warning),
)
SummaryStatus.Ok ->
Triple(
R.drawable.ic_check_circle_24,
getColorFromAttr(context, R.attr.reminderTroubleshootingOk),
- context.getString(R.string.reminder_troubleshooting_summary_ok),
+ context.getString(CommonString.reminder_troubleshooting_summary_ok),
)
}
binding.summaryIcon.setImageResource(iconRes)
@@ -215,7 +216,7 @@ class ReminderTroubleshootingFragment : Fragment(R.layout.fragment_reminder_trou
val debugInfo = ReminderLogTree.readReminderLog() + "\n\n" + ReviewRemindersDatabase.dumpContentsToString()
requireContext().copyToClipboard(
TruncatedString.from(debugInfo),
- failureMessageId = R.string.about_ankidroid_error_copy_debug_info,
+ failureMessageId = CommonString.about_ankidroid_error_copy_debug_info,
)
}
}
@@ -351,12 +352,12 @@ context(context: Context)
private fun TroubleshootingCheck.title(): String =
context.getString(
when (this) {
- is TroubleshootingCheck.NotificationPermission -> R.string.reminder_troubleshooting_check_notification_permission
- is TroubleshootingCheck.NotificationChannelEnabled -> R.string.reminder_troubleshooting_check_notification_channel
- is TroubleshootingCheck.DoNotDisturbOff -> R.string.reminder_troubleshooting_check_do_not_disturb
- is TroubleshootingCheck.UnrestrictedOptimizationEnabled -> R.string.reminder_troubleshooting_check_battery_optimization
- is TroubleshootingCheck.PowerSavingModeOff -> R.string.reminder_troubleshooting_check_power_saving_mode
- is TroubleshootingCheck.ExactAlarmPermission -> R.string.reminder_troubleshooting_check_exact_alarm_permission
+ is TroubleshootingCheck.NotificationPermission -> CommonString.reminder_troubleshooting_check_notification_permission
+ is TroubleshootingCheck.NotificationChannelEnabled -> CommonString.reminder_troubleshooting_check_notification_channel
+ is TroubleshootingCheck.DoNotDisturbOff -> CommonString.reminder_troubleshooting_check_do_not_disturb
+ is TroubleshootingCheck.UnrestrictedOptimizationEnabled -> CommonString.reminder_troubleshooting_check_battery_optimization
+ is TroubleshootingCheck.PowerSavingModeOff -> CommonString.reminder_troubleshooting_check_power_saving_mode
+ is TroubleshootingCheck.ExactAlarmPermission -> CommonString.reminder_troubleshooting_check_exact_alarm_permission
},
)
@@ -369,24 +370,24 @@ private fun TroubleshootingCheck.statusName(): String? {
is TroubleshootingCheck.ExactAlarmPermission,
->
if (passed) {
- R.string.reminder_troubleshooting_status_granted
+ CommonString.reminder_troubleshooting_status_granted
} else {
- R.string.reminder_troubleshooting_status_denied
+ CommonString.reminder_troubleshooting_status_denied
}
- is TroubleshootingCheck.NotificationChannelEnabled -> if (passed) R.string.enabled else R.string.disabled
+ is TroubleshootingCheck.NotificationChannelEnabled -> if (passed) CommonString.enabled else CommonString.disabled
is TroubleshootingCheck.DoNotDisturbOff,
is TroubleshootingCheck.PowerSavingModeOff,
->
if (passed) {
- R.string.reminder_troubleshooting_status_off
+ CommonString.reminder_troubleshooting_status_off
} else {
- R.string.reminder_troubleshooting_status_on
+ CommonString.reminder_troubleshooting_status_on
}
is TroubleshootingCheck.UnrestrictedOptimizationEnabled ->
when (result) {
- is CheckResult.Passed -> R.string.reminder_troubleshooting_status_unrestricted
- is CheckResult.Warning -> R.string.reminder_troubleshooting_status_optimized
- is CheckResult.Failed -> R.string.reminder_troubleshooting_status_restricted
+ is CheckResult.Passed -> CommonString.reminder_troubleshooting_status_unrestricted
+ is CheckResult.Warning -> CommonString.reminder_troubleshooting_status_optimized
+ is CheckResult.Failed -> CommonString.reminder_troubleshooting_status_restricted
else -> null
}
}
@@ -400,19 +401,19 @@ private fun TroubleshootingCheck.explanation(): String? {
// no need for an explanation: the 'grant permission' action should be sufficient
is TroubleshootingCheck.NotificationPermission -> null
is TroubleshootingCheck.NotificationChannelEnabled ->
- if (result.hasIssue) R.string.reminder_troubleshooting_explanation_notification_channel else null
+ if (result.hasIssue) CommonString.reminder_troubleshooting_explanation_notification_channel else null
is TroubleshootingCheck.DoNotDisturbOff ->
- if (result.hasIssue) R.string.reminder_troubleshooting_explanation_do_not_disturb else null
+ if (result.hasIssue) CommonString.reminder_troubleshooting_explanation_do_not_disturb else null
is TroubleshootingCheck.UnrestrictedOptimizationEnabled ->
when (result) {
- is CheckResult.Warning -> R.string.reminder_troubleshooting_explanation_battery_optimized
- is CheckResult.Failed -> R.string.reminder_troubleshooting_explanation_battery_restricted
+ is CheckResult.Warning -> CommonString.reminder_troubleshooting_explanation_battery_optimized
+ is CheckResult.Failed -> CommonString.reminder_troubleshooting_explanation_battery_restricted
else -> null
}
is TroubleshootingCheck.PowerSavingModeOff ->
- if (result.hasIssue) R.string.reminder_troubleshooting_explanation_power_saving_mode else null
+ if (result.hasIssue) CommonString.reminder_troubleshooting_explanation_power_saving_mode else null
is TroubleshootingCheck.ExactAlarmPermission ->
- if (result.hasIssue) R.string.reminder_troubleshooting_explanation_exact_alarm else null
+ if (result.hasIssue) CommonString.reminder_troubleshooting_explanation_exact_alarm else null
}
return explanationRes?.let { context.getString(it) }
}
@@ -446,7 +447,7 @@ private fun TroubleshootingCheck.resolveAction(): ResolveCheckAction? {
fun requestNotificationPermission(): ResolveCheckAction? =
ResolveCheckAction(
- label = context.getString(R.string.reminder_troubleshooting_action_grant_permission),
+ label = context.getString(CommonString.reminder_troubleshooting_action_grant_permission),
logDescription = "requesting POST_NOTIFICATIONS via system dialog or app settings",
) {
fragment.attemptToEnableNotifications(fragment.notificationPermissionLauncher)
@@ -455,7 +456,7 @@ private fun TroubleshootingCheck.resolveAction(): ResolveCheckAction? {
fun requestReminderNotifChannelPermission(): ResolveCheckAction? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null
return ResolveCheckAction(
- label = context.getString(R.string.reminder_troubleshooting_action_enable_notification_channel),
+ label = context.getString(CommonString.reminder_troubleshooting_action_enable_notification_channel),
logDescription = "opening app notification settings screen",
) {
fragment.openAppNotificationsSettingsScreen(highlightedChannel = NotificationChannel.REVIEW_REMINDERS)
@@ -467,7 +468,7 @@ private fun TroubleshootingCheck.resolveAction(): ResolveCheckAction? {
return if (Permissions.canRequestIgnoreBatteryOptimizations(context)) {
ResolveCheckAction(
- label = context.getString(R.string.reminder_troubleshooting_action_disable_battery_optimization),
+ label = context.getString(CommonString.reminder_troubleshooting_action_disable_battery_optimization),
logDescription = "opening ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
) {
try {
@@ -484,7 +485,7 @@ private fun TroubleshootingCheck.resolveAction(): ResolveCheckAction? {
}
} else {
ResolveCheckAction(
- label = context.getString(R.string.reminder_troubleshooting_action_open_battery_settings),
+ label = context.getString(CommonString.reminder_troubleshooting_action_open_battery_settings),
logDescription = "opening ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS",
) {
openBatteryOptimizationList()
@@ -494,7 +495,7 @@ private fun TroubleshootingCheck.resolveAction(): ResolveCheckAction? {
fun openBatterySaverSettings() =
ResolveCheckAction(
- label = context.getString(R.string.reminder_troubleshooting_action_open_battery_settings),
+ label = context.getString(CommonString.reminder_troubleshooting_action_open_battery_settings),
logDescription = "opening ACTION_BATTERY_SAVER_SETTINGS",
) {
context.startActivity(Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS))
@@ -503,7 +504,7 @@ private fun TroubleshootingCheck.resolveAction(): ResolveCheckAction? {
fun openExactAlarmSettings(): ResolveCheckAction? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return null
return ResolveCheckAction(
- label = context.getString(R.string.reminder_troubleshooting_action_grant_permission),
+ label = context.getString(CommonString.reminder_troubleshooting_action_grant_permission),
logDescription = "opening ACTION_REQUEST_SCHEDULE_EXACT_ALARM",
) {
context.startActivity(
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReviewRemindersDatabase.kt b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReviewRemindersDatabase.kt
index a411421d1b7c..a2b2ff7909f6 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReviewRemindersDatabase.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ReviewRemindersDatabase.kt
@@ -7,7 +7,7 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.VisibleForTesting
import androidx.core.content.edit
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.android.appContext
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.time.TimeManager
@@ -448,7 +448,7 @@ object ReviewRemindersDatabase {
if (errorString.isEmpty()) return
context.showError(
- message = context.getString(R.string.review_reminders_load_error) + "\n\n" + errorString.ellipsize(1000),
+ message = context.getString(CommonString.review_reminders_load_error) + "\n\n" + errorString.ellipsize(1000),
crashReportData = null, // Crash reports are sent when the error is first encountered
)
Prefs.reviewReminderDeserializationErrors = ""
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersAdapter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersAdapter.kt
index faa4ab4867a1..8673fd41fb62 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersAdapter.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersAdapter.kt
@@ -15,6 +15,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.materialswitch.MaterialSwitch
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.ItemScheduleRemindersBinding
import com.ichi2.anki.libanki.DeckId
@@ -96,7 +97,7 @@ class ScheduleRemindersAdapter(
setTextViewStrikethrough(holder.timeTextView, false)
setViewHolderColors(holder, activeTextColor, activeTrackColor)
} else {
- holder.deckTextView.text = holder.context.getString(R.string.schedule_reminders_deck_not_found)
+ holder.deckTextView.text = holder.context.getString(CommonString.schedule_reminders_deck_not_found)
setTextViewStrikethrough(holder.timeTextView, true)
setViewHolderColors(holder, inactiveTextColor, inactiveTrackColor)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersFragment.kt
index 2bd9a84fc0c3..7f6e8223263a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewreminders/ScheduleRemindersFragment.kt
@@ -31,6 +31,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.canUserAccessDeck
import com.ichi2.anki.databinding.FragmentScheduleRemindersBinding
@@ -276,8 +277,8 @@ class ScheduleRemindersFragment :
}
if (troubleshootingSnackbar?.isShown == true) return
troubleshootingSnackbar =
- showSnackbar(text = getString(R.string.schedule_reminders_unavailable), duration = Snackbar.LENGTH_INDEFINITE) {
- setAction(R.string.schedule_reminders_fix) { openTroubleshootingScreen() }
+ showSnackbar(text = getString(CommonString.schedule_reminders_unavailable), duration = Snackbar.LENGTH_INDEFINITE) {
+ setAction(CommonString.schedule_reminders_fix) { openTroubleshootingScreen() }
}
}
@@ -290,7 +291,7 @@ class ScheduleRemindersFragment :
Lifecycle.State.RESUMED,
)
retrieveSubtitle { subtitle ->
- setToolbarText(title = getString(R.string.review_reminders_title), subtitle = subtitle)
+ setToolbarText(title = getString(CommonString.review_reminders_title), subtitle = subtitle)
}
invalidateMenu()
}
@@ -316,7 +317,7 @@ class ScheduleRemindersFragment :
Lifecycle.State.RESUMED,
)
- title = getString(R.string.review_reminders_title)
+ title = getString(CommonString.review_reminders_title)
retrieveSubtitle { subtitle = it }
setNavigationOnClickListener {
@@ -461,11 +462,11 @@ class ScheduleRemindersFragment :
if (flowOfShowTroubleshooting.value) return // Don't override the troubleshooting snackbar, which is more important
showSnackbar(
when (modeOfFinishedDialog) {
- is AddEditReminderDialog.DialogMode.Add -> getString(R.string.schedule_reminders_added)
+ is AddEditReminderDialog.DialogMode.Add -> getString(CommonString.schedule_reminders_added)
is AddEditReminderDialog.DialogMode.Edit -> {
when (newOrModifiedReminder) {
- null -> getString(R.string.schedule_reminders_deleted)
- else -> getString(R.string.schedule_reminders_edited)
+ null -> getString(CommonString.schedule_reminders_deleted)
+ else -> getString(CommonString.schedule_reminders_edited)
}
}
},
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/ForgetCardsDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/ForgetCardsDialog.kt
index 477b14a2566a..4817c673c5d3 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/ForgetCardsDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/ForgetCardsDialog.kt
@@ -11,6 +11,8 @@ import androidx.fragment.app.setFragmentResultListener
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.common.annotations.NeedsTest
@@ -101,10 +103,10 @@ class ForgetCardsDialog : AnalyticsDialogFragment() {
return MaterialAlertDialogBuilder(requireContext()).create {
// BUG: this is 'Reset Card'/'Forget Card' in Anki Desktop (24.04)
// "Reset card progress" is less explicit on the singular/plural dimension
- titleWithHelpIcon(stringRes = R.string.reset_card_dialog_title) {
+ titleWithHelpIcon(stringRes = CommonString.reset_card_dialog_title) {
requireContext().openUrl(R.string.link_help_forget_cards)
}
- positiveButton(R.string.reset) {
+ positiveButton(CommonString.reset) {
sharedPrefs.edit {
putBoolean(ARG_RESTORE_ORIGINAL, restoreOriginalPositionIfPossible)
putBoolean(ARG_RESET_REPETITION, resetRepetitionAndLapseCounts)
@@ -117,7 +119,7 @@ class ForgetCardsDialog : AnalyticsDialogFragment() {
},
)
}
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
setView(binding.root)
}
}
@@ -192,7 +194,7 @@ private fun FragmentActivity.forgetCards(
Timber.d("forgot %d cards", cardsIds.size)
showSnackbar(
resources.getQuantityString(
- R.plurals.reset_cards_dialog_acknowledge,
+ CommonPlurals.reset_cards_dialog_acknowledge,
cardsIds.size,
cardsIds.size,
),
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateDialog.kt
index 5fad852119fe..7176fc7708d0 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateDialog.kt
@@ -31,6 +31,8 @@ import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.analytics.AnalyticsDialogFragment
import com.ichi2.anki.asyncCatching
@@ -110,7 +112,7 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
} catch (e: IOException) {
// the file may be missing, or truncated if the write was interrupted
Timber.w(e, "Failed to read cardIds")
- showThemedToast(requireContext(), R.string.something_wrong, false)
+ showThemedToast(requireContext(), CommonString.something_wrong, false)
dismiss()
return
}
@@ -173,8 +175,8 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
openUrl(R.string.link_set_due_date_help)
}
title(text = TR.sentenceCase.setDueDate)
- positiveButton(R.string.dialog_ok) { launchUpdateDueDate() }
- negativeButton(R.string.dialog_cancel)
+ positiveButton(CommonString.dialog_ok) { launchUpdateDueDate() }
+ negativeButton(CommonString.dialog_cancel)
setView(binding.root)
}.apply {
show()
@@ -231,7 +233,7 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
tv.isVisible = true
tv.text =
resources.getQuantityString(
- R.plurals.set_due_date_current_interval,
+ CommonPlurals.set_due_date_current_interval,
currentInterval,
currentInterval,
)
@@ -349,18 +351,18 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
viewModel.nextSingleDayDueDate = currentValue
suffixText =
resources.getQuantityString(
- R.plurals.set_due_date_label_suffix,
+ CommonPlurals.set_due_date_label_suffix,
currentValue ?: 0,
)
}
- suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, 0)
+ suffixText = resources.getQuantityString(CommonPlurals.set_due_date_label_suffix, 0)
helperText =
getString(
- R.string.set_due_date_hintText,
+ CommonString.set_due_date_hintText,
// 0 days
- resources.getQuantityString(R.plurals.set_due_date_label_suffix, 0),
+ resources.getQuantityString(CommonPlurals.set_due_date_label_suffix, 0),
// 1 day
- resources.getQuantityString(R.plurals.set_due_date_label_suffix, 1),
+ resources.getQuantityString(CommonPlurals.set_due_date_label_suffix, 1),
)
setOnEditorActionListener { _, actionId, event ->
return@setOnEditorActionListener if (actionId == EditorInfo.IME_ACTION_DONE ||
@@ -380,7 +382,7 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
}
binding.dateSingleLabel.text =
resources.getQuantityString(
- R.plurals.set_due_date_single_day_label,
+ CommonPlurals.set_due_date_single_day_label,
viewModel.cardCount,
)
}
@@ -415,11 +417,11 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
viewModel.setNextDateRangeStart(value)
suffixText =
resources.getQuantityString(
- R.plurals.set_due_date_label_suffix,
+ CommonPlurals.set_due_date_label_suffix,
value ?: 0,
)
}
- suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, 0)
+ suffixText = resources.getQuantityString(CommonPlurals.set_due_date_label_suffix, 0)
selectAllWhenFocused()
}
}
@@ -431,11 +433,11 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
viewModel.setNextDateRangeEnd(value)
suffixText =
resources.getQuantityString(
- R.plurals.set_due_date_label_suffix,
+ CommonPlurals.set_due_date_label_suffix,
value ?: 0,
)
}
- suffixText = resources.getQuantityString(R.plurals.set_due_date_label_suffix, 0)
+ suffixText = resources.getQuantityString(CommonPlurals.set_due_date_label_suffix, 0)
viewModel.dateRange.end?.let { end -> setText(end.toString()) }
setOnEditorActionListener { _, actionId, event ->
return@setOnEditorActionListener if (actionId == EditorInfo.IME_ACTION_DONE ||
@@ -454,7 +456,7 @@ class SetDueDateDialog : AnalyticsDialogFragment() {
}
}
binding.dateRangeLabel.text =
- resources.getQuantityString(R.plurals.set_due_date_range_label, viewModel.cardCount)
+ resources.getQuantityString(CommonPlurals.set_due_date_range_label, viewModel.cardCount)
}
override fun onResume() {
@@ -482,7 +484,7 @@ private fun AnkiActivity.updateDueDate(
if (cardsUpdated == null) {
Timber.w("unable to update due date")
if (showError) {
- showThemedToast(this@updateDueDate, R.string.something_wrong, true)
+ showThemedToast(this@updateDueDate, CommonString.something_wrong, true)
}
return@asyncCatching null
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateViewModel.kt
index f91160f6b7f4..1ddee60333b1 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateViewModel.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/scheduling/SetDueDateViewModel.kt
@@ -6,6 +6,7 @@ import androidx.annotation.DrawableRes
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.libanki.CardId
import com.ichi2.anki.libanki.CardType
@@ -198,10 +199,10 @@ class SetDueDateViewModel : ViewModel() {
val text: Int,
) {
/** Set the due date to a single day */
- SINGLE_DAY(0, R.drawable.calendar_single_day, R.string.set_due_date_day),
+ SINGLE_DAY(0, R.drawable.calendar_single_day, CommonString.set_due_date_day),
/** Sets the due date randomly between a range of days */
- DATE_RANGE(1, R.drawable.calendar_date_range, R.string.set_due_date_date_range),
+ DATE_RANGE(1, R.drawable.calendar_date_range, CommonString.set_due_date_date_range),
}
class DateRange(
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/security/AppPermissions.kt b/AnkiDroid/src/main/java/com/ichi2/anki/security/AppPermissions.kt
index bc717f80e39f..2d44b55f6866 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/security/AppPermissions.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/security/AppPermissions.kt
@@ -4,6 +4,7 @@ package com.ichi2.anki.security
import android.content.Context
import androidx.annotation.VisibleForTesting
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.preferences.sharedPrefs
import timber.log.Timber
@@ -52,7 +53,7 @@ class AppPermissions(
// Avoid decision fatigue by limiting the number of times the warning is shown.
// Reset on app restart.
if (launchDenialsSinceProcessStart.incrementAndGet() > MAX_SNACKBARS_PER_PROCESS) return
- showSnackbar(context.getString(R.string.card_external_launch_denied_snackbar))
+ showSnackbar(context.getString(CommonString.card_external_launch_denied_snackbar))
}
/**
@@ -75,7 +76,7 @@ class AppPermissions(
// Avoid decision fatigue by limiting the number of times the warning is shown.
// Reset on app restart.
if (jaApiDenialsSinceProcessStart.incrementAndGet() > MAX_SNACKBARS_PER_PROCESS) return
- showSnackbar(context.getString(R.string.dangerous_js_api_denied_snackbar))
+ showSnackbar(context.getString(CommonString.dangerous_js_api_denied_snackbar))
}
companion object {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/SchedulerService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/SchedulerService.kt
index 88530e2a7a78..3f01387b8a4c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/SchedulerService.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/SchedulerService.kt
@@ -5,7 +5,7 @@ package com.ichi2.anki.servicelayer
import androidx.fragment.app.FragmentActivity
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.CollectionManager.TR
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonPlurals
import com.ichi2.anki.libanki.CardId
import com.ichi2.anki.observability.undoableOp
import com.ichi2.anki.snackbar.showSnackbar
@@ -37,7 +37,7 @@ suspend fun FragmentActivity.resetCards(
val count = cardIds.size
showSnackbar(
resources.getQuantityString(
- R.plurals.reset_cards_dialog_acknowledge,
+ CommonPlurals.reset_cards_dialog_acknowledge,
count,
count,
),
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.kt
index 600bf68e5ab8..52775dfa80a4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.kt
@@ -9,6 +9,7 @@ import android.content.Context
import android.content.Intent
import androidx.core.app.PendingIntentCompat
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.IntentHandler.Companion.grantedStoragePermissions
import com.ichi2.anki.R
import com.ichi2.anki.common.android.AnkiBroadcastReceiver
@@ -95,10 +96,10 @@ class BootService : AnkiBroadcastReceiver() {
runnable.run()
} catch (ex: SecurityException) {
Timber.w(ex)
- error = R.string.boot_service_too_many_notifications
+ error = CommonString.boot_service_too_many_notifications
} catch (e: Exception) {
Timber.w(e)
- error = R.string.boot_service_failed_to_schedule_notifications
+ error = CommonString.boot_service_failed_to_schedule_notifications
}
if (error != null) {
if (!failedToShowNotifications) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/services/NotificationService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/services/NotificationService.kt
index 4ee4f0e84df5..206c1fa497ba 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/services/NotificationService.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/services/NotificationService.kt
@@ -12,6 +12,8 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.PendingIntentCompat
import androidx.core.content.getSystemService
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.IntentHandler
import com.ichi2.anki.NotificationChannel
@@ -218,20 +220,20 @@ class NotificationService : AnkiBroadcastReceiver() {
val title =
when (reviewReminder.scope) {
- is ReviewReminderScope.Global -> context.getString(R.string.review_reminder_notification_title_all_decks)
+ is ReviewReminderScope.Global -> context.getString(CommonString.review_reminder_notification_title_all_decks)
is ReviewReminderScope.DeckSpecific -> {
val fullDeckName = reviewReminder.scope.getDeckName()
val deckName =
Decks.basename(fullDeckName) // don't show the full path with "::" included
context.getString(
- R.string.review_reminder_notification_title_deck,
+ CommonString.review_reminder_notification_title_deck,
withUnicodeIsolation(deckName),
)
}
}
val description =
context.resources.getQuantityString(
- R.plurals.review_reminder_notification_cards_due,
+ CommonPlurals.review_reminder_notification_cards_due,
dueCardsTotal,
dueCardsTotal,
)
@@ -277,9 +279,12 @@ class NotificationService : AnkiBroadcastReceiver() {
.setAutoCancel(true) // Dismiss on click
.setTicker(title) // Accessibility
.addAction(
- context.getString(R.string.review_reminder_notification_snooze_button, 5.minutes.toString()),
+ context.getString(CommonString.review_reminder_notification_snooze_button, 5.minutes.toString()),
fiveMinuteSnooze,
- ).addAction(context.getString(R.string.review_reminder_notification_snooze_button, 1.hours.toString()), oneHourSnooze)
+ ).addAction(
+ context.getString(CommonString.review_reminder_notification_snooze_button, 1.hours.toString()),
+ oneHourSnooze,
+ )
// Vibration and priority are set here for backwards compatibility; they are set via channel for API 33+
.setVibrate(longArrayOf(0, 500))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
@@ -362,7 +367,7 @@ class NotificationService : AnkiBroadcastReceiver() {
val cardsDueText =
context.resources
.getQuantityString(
- R.plurals.widget_minimum_cards_due_notification_ticker_text,
+ CommonPlurals.widget_minimum_cards_due_notification_ticker_text,
dueCardsCount,
dueCardsCount,
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt b/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt
index e8c089a9012e..069388c1cf33 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/settings/Prefs.kt
@@ -391,6 +391,7 @@ open class PrefsRepository(
// **************************************** Advanced **************************************** //
+ var useSoftwareRendering by booleanPref(R.string.disable_hardware_render_key, defaultValue = false)
val isHtmlTypeAnswerEnabled by booleanPref(R.string.use_input_tag_key, defaultValue = false)
var useFixedPortInReviewer by booleanPref(R.string.use_fixed_port_pref_key, false)
var allowTemplatesToRecordAudio by booleanPref(R.string.pref_allow_template_audio_recording, false)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksActivity.kt
index 052a7482e165..ec0a0fc52037 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksActivity.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksActivity.kt
@@ -27,6 +27,7 @@ import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE
import com.ichi2.anki.AnkiActivity
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.utils.ext.requireSystemService
import com.ichi2.anki.databinding.ActivitySharedDecksBinding
@@ -173,12 +174,12 @@ class SharedDecksActivity : AnkiActivity(R.layout.activity_shared_decks) {
*/
private fun redirectUserToSignUpOrLogin() {
// inform the user they need to log in as they've hit a rate limit
- showSnackbar(R.string.shared_decks_login_required, LENGTH_INDEFINITE) {
+ showSnackbar(CommonString.shared_decks_login_required, LENGTH_INDEFINITE) {
if (isLoggedIn()) return@showSnackbar
// If a user is not logged in inside AnkiDroid, assume they have no AnkiWeb account
// and give them the option to sign up
- setAction(R.string.sign_up) {
+ setAction(CommonString.sign_up) {
binding.webView.loadUrl(getString(R.string.shared_decks_sign_up_url))
}
}
@@ -222,7 +223,7 @@ class SharedDecksActivity : AnkiActivity(R.layout.activity_shared_decks) {
//
// statusBarStyle matches the status bar theme of the rest of the app
enableEdgeToEdge(statusBarStyle = SystemBarStyle.dark(Color.TRANSPARENT))
- setTitle(R.string.download_deck)
+ setTitle(CommonString.download_deck)
setupEdgeToEdge()
binding.webviewToolbar.setTitleTextColor(getColor(R.color.white))
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksDownloadFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksDownloadFragment.kt
index 8742f4bbc301..e3280accb96b 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksDownloadFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/shareddeck/SharedDecksDownloadFragment.kt
@@ -31,6 +31,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.IntentHandler
import com.ichi2.anki.R
import com.ichi2.anki.common.android.AnkiBroadcastReceiver
@@ -193,13 +194,13 @@ class SharedDecksDownloadFragment : Fragment(R.layout.fragment_shared_decks_down
}
private fun render(state: SharedDecksDownloadUiState) {
- binding.downloadingTitle.text = state.fileName?.let { getString(R.string.downloading_file, it) }
+ binding.downloadingTitle.text = state.fileName?.let { getString(CommonString.downloading_file, it) }
binding.downloadPercentageText.text =
when {
- state.phase == DownloadPhase.Failed -> getString(R.string.download_failed)
+ state.phase == DownloadPhase.Failed -> getString(CommonString.download_failed)
// 19812: DownloadManager could not be queried, so all we can say is that it is running
state.percent == null -> TR.syncDownloadingFromAnkiweb()
- else -> getString(R.string.percentage, formatDownloadPercent(state.percent))
+ else -> getString(CommonString.percentage, formatDownloadPercent(state.percent))
}
binding.downloadProgressBar.progress = state.percent?.toInt() ?: 0
binding.checkNetworkInfoText.isVisible = state.isWaitingForNetwork
@@ -234,7 +235,7 @@ class SharedDecksDownloadFragment : Fragment(R.layout.fragment_shared_decks_down
private fun downloadFile(fileToBeDownloaded: DownloadFile) {
val externalFilesFolder = requireContext().getExternalFilesDir(null)
if (externalFilesFolder == null) {
- showSnackbar(R.string.external_storage_unavailable)
+ showSnackbar(CommonString.external_storage_unavailable)
parentFragmentManager.popBackStack()
return
}
@@ -521,7 +522,7 @@ class SharedDecksDownloadFragment : Fragment(R.layout.fragment_shared_decks_down
try {
context.startActivity(fileIntent)
} catch (e: ActivityNotFoundException) {
- showThemedToast(context, R.string.something_wrong, false)
+ showThemedToast(context, CommonString.something_wrong, false)
Timber.w(e)
}
}
@@ -537,14 +538,14 @@ class SharedDecksDownloadFragment : Fragment(R.layout.fragment_shared_decks_down
if (isInvalidDeckFile) {
Timber.i("File is not a valid deck, hence return from the download screen")
if (isVisible) {
- context?.let { showThemedToast(it, R.string.import_log_no_apkg, false) }
+ context?.let { showThemedToast(it, CommonString.import_log_no_apkg, false) }
// Go back if file is not a deck and cannot be imported
activity?.onBackPressedDispatcher?.onBackPressed()
}
} else {
Timber.i("Download failed, offer a retry")
if (isVisible) {
- context?.let { showThemedToast(it, R.string.something_wrong, false) }
+ context?.let { showThemedToast(it, CommonString.something_wrong, false) }
}
viewModel.onDownloadFailed()
}
@@ -561,15 +562,15 @@ class SharedDecksDownloadFragment : Fragment(R.layout.fragment_shared_decks_down
Timber.i("displaying cancel download confirmation dialog")
downloadCancelConfirmationDialog =
AlertDialog.Builder(requireContext()).create {
- setTitle(R.string.cancel_download_question_title)
- setPositiveButton(R.string.dialog_yes) { _, _ ->
+ setTitle(CommonString.cancel_download_question_title)
+ setPositiveButton(CommonString.dialog_yes) { _, _ ->
Timber.i("cancelling download")
downloadManager.remove(downloadId)
isDownloadInProgress = false
onBackPressedCallback.isEnabled = isDownloadInProgress
parentFragmentManager.popBackStack()
}
- setNegativeButton(R.string.dialog_no) { _, _ ->
+ setNegativeButton(CommonString.dialog_no) { _, _ ->
Timber.i("dismissed cancel download confirmation dialog")
downloadCancelConfirmationDialog?.dismiss()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/sync/MeteredSyncPolicy.kt b/AnkiDroid/src/main/java/com/ichi2/anki/sync/MeteredSyncPolicy.kt
index 39783b225339..f30e05a1f156 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/sync/MeteredSyncPolicy.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/sync/MeteredSyncPolicy.kt
@@ -4,7 +4,7 @@ package com.ichi2.anki.sync
import android.content.Context
import androidx.appcompat.app.AlertDialog
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.settings.Prefs
import com.ichi2.utils.NetworkUtils.isActiveNetworkMetered
import com.ichi2.utils.checkBoxPrompt
@@ -47,10 +47,10 @@ object MeteredSyncPolicy {
return
}
AlertDialog.Builder(context).show {
- message(R.string.metered_sync_data_warning)
- positiveButton(R.string.dialog_continue) { onConfirm() }
- negativeButton(R.string.dialog_cancel)
- checkBoxPrompt(R.string.button_do_not_show_again) { checked ->
+ message(CommonString.metered_sync_data_warning)
+ positiveButton(CommonString.dialog_continue) { onConfirm() }
+ negativeButton(CommonString.dialog_cancel)
+ checkBoxPrompt(CommonString.button_do_not_show_again) { checked ->
setAlwaysAllow(checked)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/sync/SchemaChanges.kt b/AnkiDroid/src/main/java/com/ichi2/anki/sync/SchemaChanges.kt
index 20962b548dbb..28bda256881d 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/sync/SchemaChanges.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/sync/SchemaChanges.kt
@@ -6,7 +6,7 @@ import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.dialogs.ConfirmationDialog
import com.ichi2.anki.launchCatchingTask
import com.ichi2.anki.libanki.Collection
@@ -20,7 +20,7 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
/**
- * [launchCatchingTask], showing a one-way sync dialog: [R.string.full_sync_confirmation]
+ * [launchCatchingTask], showing a one-way sync dialog: [CommonString.full_sync_confirmation]
*
* @param block calls a backend method which unconditionally performs a schema change,
* such as [Collection.changeNotetypeRaw]
@@ -31,7 +31,7 @@ fun AnkiActivity.launchCatchingRequiringOneWaySync(block: suspend () -> Unit) =
// .also is used to ensure the activity is used as context
val confirmModSchemaDialog =
ConfirmationDialog().also { dialog ->
- dialog.setArgs(message = getString(R.string.full_sync_confirmation))
+ dialog.setArgs(message = getString(CommonString.full_sync_confirmation))
dialog.setConfirm {
launchCatchingTask {
block()
@@ -46,7 +46,7 @@ fun AnkiActivity.launchCatchingRequiringOneWaySync(block: suspend () -> Unit) =
}
/**
- * [launchCatchingTask], showing a one-way sync dialog: [R.string.full_sync_confirmation]
+ * [launchCatchingTask], showing a one-way sync dialog: [CommonString.full_sync_confirmation]
*
* **This method discards the undo and study queues when consent is provided**
*/
@@ -60,7 +60,7 @@ fun AnkiActivity.launchCatchingRequiringOneWaySyncDiscardUndo(block: suspend ()
// .also is used to ensure the activity is used as context
val confirmModSchemaDialog =
ConfirmationDialog().also { dialog ->
- dialog.setArgs(message = getString(R.string.full_sync_confirmation))
+ dialog.setArgs(message = getString(CommonString.full_sync_confirmation))
dialog.setConfirm {
launchCatchingTask {
withCol { modSchema(check = false) }
@@ -86,8 +86,8 @@ suspend fun AnkiActivity.userAcceptsSchemaChange(): Boolean {
suspendCoroutine { coroutine ->
AlertDialog.Builder(this).show {
message(text = TR.deckConfigWillRequireFullSync().replace("\\s+".toRegex(), " "))
- positiveButton(R.string.dialog_ok) { coroutine.resume(true) }
- negativeButton(R.string.dialog_cancel) { coroutine.resume(false) }
+ positiveButton(CommonString.dialog_ok) { coroutine.resume(true) }
+ negativeButton(CommonString.dialog_cancel) { coroutine.resume(false) }
setOnCancelListener { coroutine.resume(false) }
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/dialogs/tools/AsyncDialogs.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/dialogs/tools/AsyncDialogs.kt
index e505606ab812..404333b9d2c2 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/dialogs/tools/AsyncDialogs.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/dialogs/tools/AsyncDialogs.kt
@@ -135,8 +135,8 @@ sealed interface DialogResult {
* val dialogResult = awaitDialog {
* setTitle("Title")
* setMessage("Message")
- * setPositiveButton(R.string.dialog_ok)
- * setNegativeButton(R.string.dialog_cancel)
+ * setPositiveButton(CommonString.dialog_ok)
+ * setNegativeButton(CommonString.dialog_cancel)
* }
*
* if (dialogResult is DialogResult.Ok) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/preferences/screens/BackupLimitsPresenter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/preferences/screens/BackupLimitsPresenter.kt
index c6f6569bce48..0640e7d62bd9 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/preferences/screens/BackupLimitsPresenter.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/preferences/screens/BackupLimitsPresenter.kt
@@ -26,6 +26,7 @@ import androidx.preference.PreferenceFragmentCompat
import anki.config.Preferences.BackupLimits
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.launchCatchingTask
import com.ichi2.anki.preferences.requirePreference
@@ -149,17 +150,17 @@ class BackupLimitsPresenter(
preference.summaryProvider =
Preference.SummaryProvider {
when (viewModel.flowOfState.value) {
- is State.Fetching -> fragment.getString(R.string.pref__etc__summary__fetching)
- is State.Error.NoCollection -> fragment.getString(R.string.pref__etc__summary__no_collection)
- is State.Error.Exception -> fragment.getString(R.string.pref__etc__summary__error)
+ is State.Fetching -> fragment.getString(CommonString.pref__etc__summary__fetching)
+ is State.Error.NoCollection -> fragment.getString(CommonString.pref__etc__summary__no_collection)
+ is State.Error.Exception -> fragment.getString(CommonString.pref__etc__summary__error)
is State.Fetched -> preference.text
}
}
preference.onClickListener = listener@{
when (val state = viewModel.flowOfState.value) {
- is State.Fetching -> fragment.showSnackbar(R.string.pref__etc__snackbar__fetching)
- is State.Error.NoCollection -> fragment.showSnackbar(R.string.pref__etc__snackbar__no_collection)
+ is State.Fetching -> fragment.showSnackbar(CommonString.pref__etc__snackbar__fetching)
+ is State.Error.NoCollection -> fragment.showSnackbar(CommonString.pref__etc__snackbar__no_collection)
is State.Error.Exception ->
fragment.showSnackbar(
text = fragment.requireContext().getUserFriendlyErrorText(state.exception),
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/FileUtils.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/FileUtils.kt
index 5af62e3cd9ed..5e6e449f2604 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/FileUtils.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/FileUtils.kt
@@ -10,7 +10,7 @@ import android.os.storage.StorageManager
import android.os.storage.StorageVolume
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.utils.TranslatableException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -200,7 +200,7 @@ class CanNotWriteToOrCreateFileException(
TranslatableException {
override val message get() = "Can not write to or create file: $file"
- override fun getTranslatedMessage(context: Context) = context.getString(R.string.error__etc__cannot_write_to_or_create_file, file)
+ override fun getTranslatedMessage(context: Context) = context.getString(CommonString.error__etc__cannot_write_to_or_create_file, file)
}
suspend fun CollectionDirectoryProvider.ensureCanWriteToOrCreateCollectionDirectory() {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/ManageSpaceFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/ManageSpaceFragment.kt
index f95a43d4472b..bbe2ac778ef2 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/ManageSpaceFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/managespace/ManageSpaceFragment.kt
@@ -18,6 +18,8 @@ import androidx.preference.Preference
import com.ichi2.anki.BackupManager
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.LocalizedUnambiguousBackupTimeFormatter
import com.ichi2.anki.R
import com.ichi2.anki.launchCatchingTask
@@ -53,7 +55,7 @@ sealed interface Size {
class Error(
val exception: Exception,
- @StringRes val widgetTextId: Int = R.string.pref__widget_text__error,
+ @StringRes val widgetTextId: Int = CommonString.pref__widget_text__error,
) : Size
}
@@ -231,7 +233,7 @@ class ManageSpaceFragment : SettingsFragment() {
) {
super.onViewCreated(view, savedInstanceState)
binding.toolbar.apply {
- title = getString(R.string.pref__manage_space__screen_title)
+ title = getString(CommonString.pref__manage_space__screen_title)
}
}
@@ -245,17 +247,17 @@ class ManageSpaceFragment : SettingsFragment() {
val deleteFilesPromptResult =
requireContext().awaitDialog {
- setTitle(R.string.dialog__delete_unused_media_files__title)
+ setTitle(CommonString.dialog__delete_unused_media_files__title)
setMultiChoiceItems(unusedFileNames, CheckedItems.All)
- setPositiveButton(R.string.dialog_positive_delete)
- setNegativeButton(R.string.dialog_cancel)
+ setPositiveButton(CommonString.dialog_positive_delete)
+ setNegativeButton(CommonString.dialog_cancel)
}
if (deleteFilesPromptResult is DialogResult.Ok.MultipleChoice) {
val checkedItems = deleteFilesPromptResult.checkedItems
val filesNamesToDelete = unusedFileNames.filterIndexed { index, _ -> checkedItems[index] }
- withProgress(R.string.delete_media_message) {
+ withProgress(CommonString.delete_media_message) {
viewModel.deleteMediaFiles(filesNamesToDelete)
}
}
@@ -275,17 +277,17 @@ class ManageSpaceFragment : SettingsFragment() {
val chooseBackupsPromptResult =
requireContext().awaitDialog {
- setTitle(R.string.dialog__delete_backups__title)
+ setTitle(CommonString.dialog__delete_backups__title)
setMultiChoiceItems(backupNames, CheckedItems.None)
- setPositiveButton(R.string.dialog_positive_delete)
- setNegativeButton(R.string.dialog_cancel)
+ setPositiveButton(CommonString.dialog_positive_delete)
+ setNegativeButton(CommonString.dialog_cancel)
}
if (chooseBackupsPromptResult is DialogResult.Ok.MultipleChoice) {
val checkedItems = chooseBackupsPromptResult.checkedItems
val backupsToDelete = backupFiles.filterIndexed { index, _ -> checkedItems[index] }
- withProgress(R.string.progress__deleting_backups) {
+ withProgress(CommonString.progress__deleting_backups) {
viewModel.deleteBackups(backupsToDelete)
}
}
@@ -308,15 +310,15 @@ class ManageSpaceFragment : SettingsFragment() {
if (size is Size.Bytes) {
val deleteCollectionPromptResult =
requireContext().awaitDialog {
- setTitle(R.string.dialog__delete_collection__title)
- setMessage(R.string.dialog__delete_collection__message)
- setPositiveButton(R.string.dialog_positive_delete)
- setNegativeButton(R.string.dialog_cancel)
+ setTitle(CommonString.dialog__delete_collection__title)
+ setMessage(CommonString.dialog__delete_collection__message)
+ setPositiveButton(CommonString.dialog_positive_delete)
+ setNegativeButton(CommonString.dialog_cancel)
}
if (deleteCollectionPromptResult is DialogResult.Ok) {
try {
- withProgress(R.string.progress__deleting_collection) {
+ withProgress(CommonString.progress__deleting_collection) {
viewModel.deleteCollection()
}
} finally {
@@ -336,15 +338,15 @@ class ManageSpaceFragment : SettingsFragment() {
private fun adjustDeleteEverythingStringsDependingOnCollectionLocation(preference: Preference) {
if (viewModel.collectionDirectory.isInsideDirectoriesRemovedWithTheApp(requireContext())) {
- preference.setTitle(R.string.pref__delete_everything__title)
- preference.setSummary(R.string.pref__delete_everything__summary)
- deleteEverythingDialogTitle = R.string.dialog__delete_everything__title
- deleteEverythingDialogMessage = R.string.dialog__delete_everything__message
+ preference.setTitle(CommonString.pref__delete_everything__title)
+ preference.setSummary(CommonString.pref__delete_everything__summary)
+ deleteEverythingDialogTitle = CommonString.dialog__delete_everything__title
+ deleteEverythingDialogMessage = CommonString.dialog__delete_everything__message
} else {
- preference.setTitle(R.string.pref__delete_app_data__title)
- preference.setSummary(R.string.pref__delete_app_data__summary)
- deleteEverythingDialogTitle = R.string.dialog__delete_app_data__title
- deleteEverythingDialogMessage = R.string.dialog__delete_app_data__message
+ preference.setTitle(CommonString.pref__delete_app_data__title)
+ preference.setSummary(CommonString.pref__delete_app_data__summary)
+ deleteEverythingDialogTitle = CommonString.dialog__delete_app_data__title
+ deleteEverythingDialogMessage = CommonString.dialog__delete_app_data__message
}
}
@@ -353,8 +355,8 @@ class ManageSpaceFragment : SettingsFragment() {
requireContext().awaitDialog {
setTitle(deleteEverythingDialogTitle)
setMessage(deleteEverythingDialogMessage)
- setPositiveButton(R.string.dialog_positive_delete)
- setNegativeButton(R.string.dialog_cancel)
+ setPositiveButton(CommonString.dialog_positive_delete)
+ setNegativeButton(CommonString.dialog_cancel)
}
if (deleteEverythingPromptResult is DialogResult.Ok) {
@@ -373,12 +375,12 @@ class ManageSpaceFragment : SettingsFragment() {
widgetText =
when (size) {
- is Size.Calculating -> getString(R.string.pref__widget_text__calculating)
+ is Size.Calculating -> getString(CommonString.pref__widget_text__calculating)
is Size.Error -> getString(size.widgetTextId)
is Size.Bytes -> size.totalSize.toHumanReadableSize()
is Size.FilesAndBytes ->
resources.getQuantityString(
- R.plurals.pref__widget_text__n_files_n_bytes,
+ CommonPlurals.pref__widget_text__n_files_n_bytes,
size.files.size,
size.files.size,
size.totalSize.toHumanReadableSize(),
@@ -401,7 +403,7 @@ class ManageSpaceFragment : SettingsFragment() {
private fun showSnackbarIfCalculatingOrError(size: Size) {
when (size) {
- is Size.Calculating -> showSnackbar(R.string.pref__etc__snackbar__calculating)
+ is Size.Calculating -> showSnackbar(CommonString.pref__etc__snackbar__calculating)
is Size.Error -> showSnackbar(requireContext().getUserFriendlyErrorText(size.exception))
else -> {}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AllPermissionsExplanationFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AllPermissionsExplanationFragment.kt
index 0b7f421b4192..bf913ef960e9 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AllPermissionsExplanationFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AllPermissionsExplanationFragment.kt
@@ -9,6 +9,7 @@ import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.core.view.isVisible
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.permissions.LEGACY_POST_NOTIFICATIONS
import com.ichi2.anki.databinding.FragmentAllPermissionsExplanationBinding
@@ -82,7 +83,7 @@ class AllPermissionsExplanationFragment : PermissionsFragment(R.layout.fragment_
isVisible = true
// If it's already granted, offer to revoke it on click; otherwise, request it
revokeIfGrantedOnClickElse {
- showToastAndOpenAppSettingsScreenForPermission(LEGACY_POST_NOTIFICATIONS, R.string.manually_grant_permissions)
+ showToastAndOpenAppSettingsScreenForPermission(LEGACY_POST_NOTIFICATIONS, CommonString.manually_grant_permissions)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AndroidPermanentlyRevokedPermissionsDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AndroidPermanentlyRevokedPermissionsDialog.kt
index 46d147895c76..45acaa074d0e 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AndroidPermanentlyRevokedPermissionsDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/AndroidPermanentlyRevokedPermissionsDialog.kt
@@ -7,7 +7,7 @@ import android.content.Context
import android.content.DialogInterface
import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.AnkiActivity
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.storage.CollectionHelper
import com.ichi2.anki.dialogs.DatabaseErrorDialog.DatabaseErrorDialogType
import com.ichi2.anki.dialogs.DatabaseErrorDialog.UninstallListItem.Companion.createNoStorageList
@@ -35,7 +35,7 @@ object AndroidPermanentlyRevokedPermissionsDialog {
val message =
context.getString(
- R.string.directory_revoked_after_inactivity,
+ CommonString.directory_revoked_after_inactivity,
"WRITE_EXTERNAL_STORAGE",
getCurrentAnkiDroidDirectoryPath(context),
)
@@ -59,6 +59,6 @@ object AndroidPermanentlyRevokedPermissionsDialog {
CollectionHelper.getCurrentAnkiDroidDirectory(context).absolutePath
} catch (e: Exception) {
Timber.w(e)
- context.getString(R.string.card_browser_unknown_deck_name)
+ context.getString(CommonString.card_browser_unknown_deck_name)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/LegacyNotificationsPermissionFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/LegacyNotificationsPermissionFragment.kt
index 70987c876c01..b036fa9f1a4c 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/LegacyNotificationsPermissionFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/LegacyNotificationsPermissionFragment.kt
@@ -6,6 +6,7 @@ package com.ichi2.anki.ui.windows.permissions
import android.os.Bundle
import android.view.View
import androidx.fragment.app.setFragmentResult
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.permissions.LEGACY_POST_NOTIFICATIONS
import com.ichi2.anki.common.permissions.canPostNotifications
@@ -43,7 +44,7 @@ class LegacyNotificationsPermissionFragment : PermissionsFragment(R.layout.fragm
savedInstanceState: Bundle?,
) {
binding.legacyNotificationPermission.revokeIfGrantedOnClickElse {
- showToastAndOpenAppSettingsScreenForPermission(LEGACY_POST_NOTIFICATIONS, R.string.manually_grant_permissions)
+ showToastAndOpenAppSettingsScreenForPermission(LEGACY_POST_NOTIFICATIONS, CommonString.manually_grant_permissions)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivity.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivity.kt
index 77660ce5397e..0ff66eb17d8e 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivity.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivity.kt
@@ -10,6 +10,7 @@ import androidx.activity.addCallback
import androidx.activity.enableEdgeToEdge
import androidx.fragment.app.commit
import com.ichi2.anki.AnkiActivity
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.StoragePermissionSet
import com.ichi2.anki.common.utils.android.showThemedToast
@@ -51,7 +52,7 @@ class PermissionsActivity : AnkiActivity(R.layout.activity_permissions) {
val permissionSet = intent.getParcelableExtraCompat(EXTRA_PERMISSIONS_SET)
if (permissionSet == null) {
Timber.w("EXTRA_PERMISSIONS_SET not set; finishing")
- showThemedToast(this, R.string.something_wrong, false)
+ showThemedToast(this, CommonString.something_wrong, false)
setResult(RESULT_CANCELED)
finish()
return
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsFragment.kt
index 9b756b081b3f..bb3cade2921f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsFragment.kt
@@ -16,7 +16,7 @@ import androidx.core.view.allViews
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.annotations.NeedsTest
import com.ichi2.anki.common.permissions.MANAGE_EXTERNAL_STORAGE
import com.ichi2.anki.common.permissions.hasPermission
@@ -52,7 +52,7 @@ abstract class PermissionsFragment(
Timber.i("Internet permission denied")
showToastAndOpenAppSettingsScreenForPermission(
Manifest.permission.INTERNET,
- getString(R.string.permission_required_message, getString(R.string.internet_access_title)),
+ getString(CommonString.permission_required_message, getString(CommonString.internet_access_title)),
)
}
}
@@ -127,7 +127,7 @@ abstract class PermissionsFragment(
protected fun PermissionsItem.revokeIfGrantedOnClickElse(callback: () -> Unit) {
setOnPermissionsRequested { areAlreadyGranted ->
if (areAlreadyGranted) {
- showToastAndOpenAppSettingsScreenForPermission(permissions.singleOrNull(), R.string.revoke_permissions)
+ showToastAndOpenAppSettingsScreenForPermission(permissions.singleOrNull(), CommonString.revoke_permissions)
} else {
callback()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsUntil29Fragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsUntil29Fragment.kt
index d7697fba3425..c071a90711ed 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsUntil29Fragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/permissions/PermissionsUntil29Fragment.kt
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import com.ichi2.anki.AnkiActivity
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.FragmentPermissionsUntil29Binding
import com.ichi2.utils.Permissions
@@ -32,7 +33,7 @@ class PermissionsUntil29Fragment : PermissionsFragment(R.layout.fragment_permiss
// manually grant the permission.
showToastAndOpenAppSettingsScreenForPermission(
requestedPermissions.keys.singleOrNull(),
- R.string.startup_no_storage_permission,
+ CommonString.startup_no_storage_permission,
)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt
index fe88b4f60cd5..0753a0ba9ea7 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt
@@ -39,6 +39,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import anki.scheduler.CardAnswer.Rating
import com.ichi2.anki.CollectionManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DispatchKeyEventListener
import com.ichi2.anki.Flag
import com.ichi2.anki.R
@@ -81,6 +82,7 @@ import com.ichi2.anki.utils.ext.showDialogFragment
import com.ichi2.anki.utils.ext.window
import com.ichi2.anki.workarounds.SafeWebViewLayout
import com.ichi2.themes.Themes
+import com.ichi2.utils.ViewGroupUtils.setRenderWorkaround
import com.ichi2.utils.dp
import com.ichi2.utils.show
import com.squareup.seismic.ShakeDetector
@@ -246,6 +248,13 @@ class ReviewerFragment :
if (Prefs.keepScreenOn) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
+
+ setRenderWorkaround(requireActivity())
+ }
+
+ override fun onWebViewRecreated(webView: WebView) {
+ super.onWebViewRecreated(webView)
+ setRenderWorkaround(requireActivity())
}
private fun setupTypeAnswer() {
@@ -601,7 +610,7 @@ class ReviewerFragment :
val message = CollectionManager.TR.studyingCardStudiedIn(timebox.reps) + " " + CollectionManager.TR.studyingMinute(minutes)
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.timebox_reached_title)
+ setTitle(CommonString.timebox_reached_title)
setMessage(message)
setPositiveButton(CollectionManager.TR.studyingContinue()) { _, _ ->
Timber.i("ReviewerFragment: Timebox 'Continue'")
@@ -737,8 +746,8 @@ class ReviewerFragment :
if (hasShownUnsupportedFeatureWarning) return true
hasShownUnsupportedFeatureWarning = true
AlertDialog.Builder(requireContext()).show {
- setMessage(R.string.feature_not_supported_by_study_screen)
- setPositiveButton(R.string.dialog_ok) { _, _ -> }
+ setMessage(CommonString.feature_not_supported_by_study_screen)
+ setPositiveButton(CommonString.dialog_ok) { _, _ -> }
}
true
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerMenu.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerMenu.kt
index 53540b36ec63..b4edd70fbeb3 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerMenu.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerMenu.kt
@@ -10,6 +10,7 @@ import androidx.lifecycle.coroutineScope
import androidx.lifecycle.flowWithLifecycle
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.preferences.reviewer.ReviewerMenuView
import com.ichi2.anki.preferences.reviewer.ViewerAction
@@ -42,7 +43,7 @@ fun ReviewerMenuView.setup(
.collectLatestIn(lifecycle.coroutineScope) { isMarked ->
if (isMarked) {
markItem.setPaddedIcon(context, R.drawable.ic_star)
- markItem.setTitle(R.string.menu_unmark_note)
+ markItem.setTitle(CommonString.menu_unmark_note)
} else {
markItem.setPaddedIcon(context, R.drawable.ic_star_border_white)
markItem.title = with(context) { TR.sentenceCase.markNote }
@@ -110,10 +111,10 @@ fun ReviewerMenuView.setup(
recordVoiceFlow.collectLatestIn(lifecycle.coroutineScope) { isEnabled ->
if (isEnabled) {
recordVoiceItem.setPaddedIcon(context, R.drawable.ic_action_mic)
- recordVoiceItem.setTitle(R.string.disable_voice_recording)
+ recordVoiceItem.setTitle(CommonString.disable_voice_recording)
} else {
recordVoiceItem.setPaddedIcon(context, R.drawable.ic_mic_outlined)
- recordVoiceItem.setTitle(R.string.enable_voice_recording)
+ recordVoiceItem.setTitle(CommonString.enable_voice_recording)
}
}
}
@@ -123,10 +124,10 @@ fun ReviewerMenuView.setup(
toggleWhiteboardFlow.collectLatestIn(lifecycle.coroutineScope) { isEnabled ->
if (isEnabled) {
toggleWhiteboardItem.setPaddedIcon(context, R.drawable.ic_draw_filled)
- toggleWhiteboardItem.setTitle(R.string.disable_whiteboard)
+ toggleWhiteboardItem.setTitle(CommonString.disable_whiteboard)
} else {
toggleWhiteboardItem.setPaddedIcon(context, R.drawable.ic_enable_whiteboard)
- toggleWhiteboardItem.setTitle(R.string.enable_whiteboard)
+ toggleWhiteboardItem.setTitle(CommonString.enable_whiteboard)
}
}
}
@@ -136,10 +137,10 @@ fun ReviewerMenuView.setup(
isAutoAdvancedEnabledFlow.collectLatestIn(lifecycle.coroutineScope) { isEnabled ->
if (isEnabled) {
autoAdvanceItem.setPaddedIcon(context, R.drawable.ic_fast_forward)
- autoAdvanceItem.setTitle(R.string.disable_auto_advance)
+ autoAdvanceItem.setTitle(CommonString.disable_auto_advance)
} else {
autoAdvanceItem.setPaddedIcon(context, R.drawable.ic_fast_forward_outlined)
- autoAdvanceItem.setTitle(R.string.enable_auto_advance)
+ autoAdvanceItem.setTitle(CommonString.enable_auto_advance)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayView.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayView.kt
index 4151f09f2ed8..e5481e17b4fd 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayView.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayView.kt
@@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.animation.DecelerateInterpolator
import androidx.annotation.DrawableRes
import androidx.constraintlayout.widget.ConstraintLayout
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.ViewAudioPlayBinding
@@ -67,7 +68,7 @@ class AudioPlayView : ConstraintLayout {
@DrawableRes iconRes: Int,
) {
binding.playButton.contentDescription =
- context.getString(if (iconRes == R.drawable.ic_replay) R.string.replay_voice else R.string.play_recording)
+ context.getString(if (iconRes == R.drawable.ic_replay) CommonString.replay_voice else CommonString.play_recording)
binding.playIconView
.animate()
.alpha(0f)
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/CheckPronunciationFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/CheckPronunciationFragment.kt
index 7b3dfccafe4c..3d8567471136 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/CheckPronunciationFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/CheckPronunciationFragment.kt
@@ -15,6 +15,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.FragmentCheckPronunciationBinding
import com.ichi2.anki.ui.windows.reviewer.ReviewerViewModel
@@ -35,15 +36,15 @@ class CheckPronunciationFragment : Fragment(R.layout.fragment_check_pronunciatio
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (!isGranted) {
AlertDialog.Builder(requireContext()).show {
- setTitle(R.string.permission_denied)
- setMessage(R.string.microphone_permission_denied_message)
- setPositiveButton(R.string.dialog_ok) { _, _ ->
+ setTitle(CommonString.permission_denied)
+ setMessage(CommonString.microphone_permission_denied_message)
+ setPositiveButton(CommonString.dialog_ok) { _, _ ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", requireContext().packageName, null)
intent.data = uri
startActivity(intent)
}
- setNegativeButton(R.string.dialog_cancel, null)
+ setNegativeButton(CommonString.dialog_cancel, null)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/ColorPickerDialog.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/ColorPickerDialog.kt
index ce22d753182b..57d69c09bd2b 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/ColorPickerDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/ColorPickerDialog.kt
@@ -11,6 +11,7 @@ import android.widget.FrameLayout
import androidx.annotation.ColorInt
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.utils.Dp
import com.ichi2.utils.dp
@@ -80,9 +81,9 @@ fun Context.showColorPickerDialog(
this.height = 12.dp.toPx(this@showColorPickerDialog)
}
}
- setTitle(R.string.choose_color)
- setPositiveButton(R.string.dialog_ok, onPositiveCallback)
- negativeButton(R.string.dialog_cancel)
+ setTitle(CommonString.choose_color)
+ setPositiveButton(CommonString.dialog_ok, onPositiveCallback)
+ negativeButton(CommonString.dialog_cancel)
setBottomSpace(12.dp)
val callback =
object : Choreographer.FrameCallback {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/WhiteboardFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/WhiteboardFragment.kt
index e81b7723847c..4315da56bc67 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/WhiteboardFragment.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/whiteboard/WhiteboardFragment.kt
@@ -26,6 +26,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.AnkiDroidApp
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DispatchKeyEventListener
import com.ichi2.anki.R
import com.ichi2.anki.android.back.doubleBackPressCallback
@@ -95,7 +96,7 @@ class WhiteboardFragment :
doubleBackCallback =
doubleBackPressCallback(
enabled = computeDoubleBackEnabled(),
- onFirstBack = { showSnackbar(R.string.back_pressed_once, Snackbar.LENGTH_SHORT) },
+ onFirstBack = { showSnackbar(CommonString.back_pressed_once, Snackbar.LENGTH_SHORT) },
shouldReEnable = { computeDoubleBackEnabled() },
).also {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, it)
@@ -177,7 +178,7 @@ class WhiteboardFragment :
showRemoveColorDialog(index)
} else {
Timber.i("Tried to remove the last brush of the whiteboard")
- showSnackbar(R.string.cannot_remove_last_brush_message)
+ showSnackbar(CommonString.cannot_remove_last_brush_message)
}
}
@@ -295,11 +296,11 @@ class WhiteboardFragment :
private fun showRemoveColorDialog(index: Int) {
AlertDialog
.Builder(requireContext())
- .setMessage(R.string.whiteboard_remove_brush_message)
- .setPositiveButton(R.string.dialog_remove) { dialog, _ ->
+ .setMessage(CommonString.whiteboard_remove_brush_message)
+ .setPositiveButton(CommonString.dialog_remove) { dialog, _ ->
Timber.i("Removed brush of index %d", index)
viewModel.removeBrush(index)
- }.setNegativeButton(R.string.dialog_cancel, null)
+ }.setNegativeButton(CommonString.dialog_cancel, null)
.show()
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/AndroidUtils.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/AndroidUtils.kt
index 3a8024ff1619..d971d5058431 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/AndroidUtils.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/AndroidUtils.kt
@@ -18,7 +18,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.android.AdaptionUtil
import com.ichi2.anki.common.android.appContext
import com.ichi2.anki.common.utils.android.showThemedToast
@@ -69,7 +69,7 @@ fun Context.openUrl(url: String) {
fun Context.openUrl(uri: Uri) {
if (!AdaptionUtil.hasWebBrowser(this)) {
- val noBrowserMessage = getString(R.string.no_browser_msg, uri.toString())
+ val noBrowserMessage = getString(CommonString.no_browser_msg, uri.toString())
if (this is FragmentActivity) {
showSnackbar(noBrowserMessage) {
setAction(android.R.string.copyUrl) {
@@ -86,9 +86,9 @@ fun Context.openUrl(uri: Uri) {
} catch (ex: Exception) {
Timber.w("No app found to handle opening an external url from ${this::class.java.name}")
if (this is FragmentActivity) {
- showSnackbar(R.string.activity_start_failed)
+ showSnackbar(CommonString.activity_start_failed)
} else {
- showThemedToast(this, R.string.activity_start_failed, false)
+ showThemedToast(this, CommonString.activity_start_failed, false)
}
}
}
@@ -96,14 +96,14 @@ fun Context.openUrl(uri: Uri) {
// necessary for Fragments that are BaseSnackbarBuilderProvider to work correctly
fun Fragment.openUrl(uri: Uri) {
if (!AdaptionUtil.hasWebBrowser(requireContext())) {
- showSnackbar(getString(R.string.no_browser_msg, uri.toString()))
+ showSnackbar(getString(CommonString.no_browser_msg, uri.toString()))
return
}
try {
startActivity(Intent(Intent.ACTION_VIEW, uri))
} catch (ex: ActivityNotFoundException) {
Timber.w("No app found to handle opening an external url from ${Fragment::class.java.name}")
- showSnackbar(R.string.activity_start_failed)
+ showSnackbar(CommonString.activity_start_failed)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/Exceptions.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/Exceptions.kt
index 3ad5c57670e2..9bab44cb242a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/Exceptions.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/Exceptions.kt
@@ -15,7 +15,7 @@
package com.ichi2.anki.utils
import android.content.Context
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
fun interface TranslatableException {
fun getTranslatedMessage(context: Context): String
@@ -34,7 +34,7 @@ fun Context.getUserFriendlyErrorText(e: Exception): String =
e.localizedMessage?.ifBlank { null }
?: e.message?.ifBlank { null }
?: e::class.simpleName?.ifBlank { null }
- ?: getString(R.string.error__etc__unknown_error)
+ ?: getString(CommonString.error__etc__unknown_error)
}
/**
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/Time.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/Time.kt
index be02bfb2976f..500b9270b550 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/Time.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/Time.kt
@@ -6,7 +6,7 @@ import android.content.Context
import android.media.MediaPlayer
import android.os.Handler
import android.view.View
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonPlurals
import com.ichi2.anki.common.time.TIME_HOUR
import com.ichi2.anki.common.time.TIME_MINUTE
import kotlin.math.max
@@ -40,7 +40,7 @@ fun remainingTime(
(timeSeconds / TIME_MINUTE).roundToInt(),
1,
)
- res.getQuantityString(R.plurals.reviewer_window_title, timeX, timeX)
+ res.getQuantityString(CommonPlurals.reviewer_window_title, timeX, timeX)
// It used to be minutes only. So the word "minutes" is not
// explicitly written in the resource name.
/*
@@ -58,7 +58,7 @@ fun remainingTime(
remaining =
(remainingSeconds.toFloat() / TIME_MINUTE).roundToInt()
res.getQuantityString(
- R.plurals.reviewer_window_title_hours_new,
+ CommonPlurals.reviewer_window_title_hours_new,
timeX,
timeX,
remaining,
@@ -69,7 +69,7 @@ fun remainingTime(
remaining =
(remainingSeconds / TIME_HOUR).roundToInt()
res.getQuantityString(
- R.plurals.reviewer_window_title_days_new,
+ CommonPlurals.reviewer_window_title_days_new,
timeX,
timeX,
remaining,
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/File.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/File.kt
new file mode 100644
index 000000000000..5c0f078f309b
--- /dev/null
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/File.kt
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: 2026 Shaan Narendran
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.utils.ext
+
+import java.io.File
+
+/**
+ * @param name Relative file name or path inside this directory.
+ * @return `true` if the named file exists within this directory
+ *
+ * @throws SecurityException If [SecurityManager.checkRead] is used and access is denied.
+ */
+fun File.containsFile(name: String): Boolean {
+ if (!isDirectory) return false
+
+ val maybeFile = File(this, name).canonicalFile
+
+ // guard against path traversal
+ if (!maybeFile.path.startsWith(this.canonicalPath + File.separator)) return false
+
+ // ensure the file exists, and is not a directory
+ return maybeFile.isFile
+}
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckAdapter.kt b/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckAdapter.kt
index 4e298d187109..0b31b2dd202e 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckAdapter.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckAdapter.kt
@@ -18,6 +18,7 @@ import androidx.core.widget.TextViewCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.ItemDeckBinding
import com.ichi2.anki.deckpicker.DisplayDeckNode
@@ -234,10 +235,10 @@ class DeckAdapter(
expander.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
if (node.collapsed) {
expander.setImageDrawable(expandImage)
- expander.contentDescription = expander.context.getString(R.string.expand)
+ expander.contentDescription = expander.context.getString(CommonString.expand)
} else {
expander.setImageDrawable(collapseImage)
- expander.contentDescription = expander.context.getString(R.string.collapse)
+ expander.contentDescription = expander.context.getString(CommonString.collapse)
}
} else {
// Siblings must perfectly align their text with each other regardless of whether they have children.
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/workarounds/AppLoadedFromBackupWorkaround.kt b/AnkiDroid/src/main/java/com/ichi2/anki/workarounds/AppLoadedFromBackupWorkaround.kt
index 640e26193785..262f84dac262 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/workarounds/AppLoadedFromBackupWorkaround.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/workarounds/AppLoadedFromBackupWorkaround.kt
@@ -6,7 +6,7 @@ import android.app.Activity
import android.os.Bundle
import android.os.Process
import com.ichi2.anki.AnkiDroidApp
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.exception.ManuallyReportedException
import com.ichi2.anki.common.utils.android.showThemedToast
@@ -44,7 +44,7 @@ object AppLoadedFromBackupWorkaround {
Timber.w("Activity started with no application instance")
showThemedToast(
this,
- getString(R.string.ankidroid_cannot_open_after_backup_try_again),
+ getString(CommonString.ankidroid_cannot_open_after_backup_try_again),
false,
)
CrashReportService.sendExceptionReport(
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt
index f868bac9fcff..549d8d0d9fb3 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncMediaWorker.kt
@@ -26,6 +26,7 @@ import anki.sync.SyncAuth
import anki.sync.syncAuth
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.NotificationChannel
import com.ichi2.anki.R
import com.ichi2.anki.cancelMediaSync
@@ -133,7 +134,7 @@ class SyncMediaWorker(
}
override suspend fun getForegroundInfo(): ForegroundInfo {
- val title = applicationContext.getString(R.string.syncing_media)
+ val title = applicationContext.getString(CommonString.syncing_media)
val cancelTitle = TR.syncAbortButton()
val notification =
buildNotification {
@@ -179,7 +180,7 @@ class SyncMediaWorker(
}.build()
private fun getProgressNotification(progress: CharSequence): Notification {
- val title = applicationContext.getString(R.string.syncing_media)
+ val title = applicationContext.getString(CommonString.syncing_media)
val cancelTitle = TR.syncAbortButton()
return buildNotification {
diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncWorker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncWorker.kt
index 7adb7776b9a9..ddf554c25035 100644
--- a/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncWorker.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/anki/worker/SyncWorker.kt
@@ -25,6 +25,7 @@ import anki.sync.syncAuth
import com.ichi2.anki.CollectionManager
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.NotificationChannel
import com.ichi2.anki.R
import com.ichi2.anki.cancelSync
@@ -94,7 +95,7 @@ class SyncWorker(
} catch (throwable: Throwable) {
Timber.w(throwable, "SyncWorker failed")
notify {
- setContentTitle(applicationContext.getString(R.string.sync_error))
+ setContentTitle(applicationContext.getString(CommonString.sync_error))
throwable.localizedMessage?.let { message ->
setContentText(message)
}
@@ -182,7 +183,7 @@ class SyncWorker(
}
override suspend fun getForegroundInfo(): ForegroundInfo {
- val cancelTitle = applicationContext.getString(R.string.dialog_cancel)
+ val cancelTitle = applicationContext.getString(CommonString.dialog_cancel)
val notification =
buildNotification {
setContentTitle(TR.syncSyncing())
@@ -218,7 +219,7 @@ class SyncWorker(
}.build()
private fun getProgressNotification(progress: CharSequence): Notification {
- val cancelTitle = applicationContext.getString(R.string.dialog_cancel)
+ val cancelTitle = applicationContext.getString(CommonString.dialog_cancel)
return buildNotification {
setContentTitle(TR.syncSyncing())
diff --git a/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabActivityHelper.kt b/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabActivityHelper.kt
index f2eba8cb1a76..47a71c76152f 100644
--- a/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabActivityHelper.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabActivityHelper.kt
@@ -15,7 +15,7 @@ import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsServiceConnection
import androidx.browser.customtabs.CustomTabsSession
import androidx.core.content.pm.PackageInfoCompat
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.compat.CompatHelper.Companion.getPackageInfoCompat
import com.ichi2.anki.compat.PackageInfoFlagsCompat
@@ -204,7 +204,7 @@ class CustomTabActivityHelper : ServiceConnectionCallback {
customTabsIntent.launchUrl(activity, uri)
} catch (ex: ActivityNotFoundException) {
Timber.w("No app found to handle opening an external url from CustomTabsActivityHelper")
- activity.showSnackbar(R.string.activity_start_failed)
+ activity.showSnackbar(CommonString.activity_start_failed)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabsFallback.kt b/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabsFallback.kt
index 723e3d1e1aac..2acd56b6e976 100644
--- a/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabsFallback.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/compat/customtabs/CustomTabsFallback.kt
@@ -6,7 +6,7 @@ package com.ichi2.compat.customtabs
import android.app.Activity
import android.content.Intent
import android.net.Uri
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.compat.customtabs.CustomTabActivityHelper.CustomTabFallback
@@ -28,7 +28,7 @@ class CustomTabsFallback : CustomTabFallback {
// and I assume an exported intent will take priority over a non-exported intent.
// Add an exception report to see if I'm wrong
CrashReportService.sendExceptionReport(e, "CustomTabsFallback::openUri")
- showThemedToast(activity, activity.getString(R.string.web_page_error, uri), false)
+ showThemedToast(activity, activity.getString(CommonString.web_page_error, uri), false)
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/compose/ui/dialogs/TextInputDialog.kt b/AnkiDroid/src/main/java/com/ichi2/compose/ui/dialogs/TextInputDialog.kt
index bcdb980d9351..640337092f62 100644
--- a/AnkiDroid/src/main/java/com/ichi2/compose/ui/dialogs/TextInputDialog.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/compose/ui/dialogs/TextInputDialog.kt
@@ -36,7 +36,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.compose.theme.AnkiDroidTheme
import com.ichi2.compose.ui.preview.ThemePreviews
@@ -58,7 +58,7 @@ fun TextInputDialog(
confirmText: String,
onConfirm: (String) -> Unit,
onDismissRequest: () -> Unit,
- dismissText: String = stringResource(R.string.dialog_cancel),
+ dismissText: String = stringResource(CommonString.dialog_cancel),
initialText: String = "",
maxLengthCounter: Int? = null,
capitalization: KeyboardCapitalization = KeyboardCapitalization.Sentences,
diff --git a/AnkiDroid/src/main/java/com/ichi2/imagecropper/ImageCropper.kt b/AnkiDroid/src/main/java/com/ichi2/imagecropper/ImageCropper.kt
index 668f79a8dbd3..d5cc191f50df 100644
--- a/AnkiDroid/src/main/java/com/ichi2/imagecropper/ImageCropper.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/imagecropper/ImageCropper.kt
@@ -37,6 +37,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.canhub.cropper.CropImageView
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.FragmentImageCropperBinding
import com.ichi2.anki.snackbar.showSnackbar
@@ -195,7 +196,7 @@ class ImageCropper :
) {
if (error != null) {
Timber.e(error, "Failed to load image by URI")
- showSnackbar(R.string.something_wrong)
+ showSnackbar(CommonString.something_wrong)
}
}
@@ -216,7 +217,7 @@ class ImageCropper :
activity?.finish()
} else {
Timber.e(result.error, "Failed to crop image")
- showSnackbar(R.string.something_wrong)
+ showSnackbar(CommonString.something_wrong)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/preferences/ControlPreference.kt b/AnkiDroid/src/main/java/com/ichi2/preferences/ControlPreference.kt
index 00d90d0a22d2..8c33d7626ca8 100644
--- a/AnkiDroid/src/main/java/com/ichi2/preferences/ControlPreference.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/preferences/ControlPreference.kt
@@ -12,6 +12,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
import androidx.preference.DialogPreference
import androidx.preference.PreferenceFragmentCompat
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.DialogControlPreferenceBinding
import com.ichi2.anki.dialogs.GestureSelectionDialogUtils
@@ -78,7 +79,7 @@ open class ControlPreference :
val bindingPreference = getPreferenceAssignedTo(binding) ?: return false
if (bindingPreference == this) return false
val actionTitle = bindingPreference.title ?: ""
- val warning = context.getString(R.string.bindings_already_bound, actionTitle)
+ val warning = context.getString(CommonString.bindings_already_bound, actionTitle)
warningDisplay.setWarning(warning)
return true
}
@@ -103,13 +104,13 @@ open class ControlPreference :
setTitle(title)
setIcon(icon)
val gesturePicker = createGesturePicker()
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
val gesture = gesturePicker.getGesture() ?: return@positiveButton
val binding = Binding.GestureInput(gesture)
onGestureSelected(binding)
it.dismiss()
}
- negativeButton(R.string.dialog_cancel) { it.dismiss() }
+ negativeButton(CommonString.dialog_cancel) { it.dismiss() }
customView(view = gesturePicker)
gesturePicker.onGestureChanged { gesture ->
warnIfUsedOrClearWarning(Binding.GestureInput(gesture), gesturePicker)
@@ -128,12 +129,12 @@ open class ControlPreference :
keyPicker.setBindingChangedListener { binding ->
warnIfUsedOrClearWarning(binding, keyPicker)
}
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
val binding = keyPicker.getBinding() ?: return@positiveButton
onKeySelected(binding)
it.dismiss()
}
- negativeButton(R.string.dialog_cancel) { it.dismiss() }
+ negativeButton(CommonString.dialog_cancel) { it.dismiss() }
keyPicker.setKeycodeValidation(KeySelectionDialogUtils.disallowModifierKeyCodes())
}
}
@@ -145,20 +146,20 @@ open class ControlPreference :
customView(view = axisPicker.binding.root)
setTitle(title)
setIcon(icon)
- negativeButton(R.string.dialog_cancel) { it.dismiss() }
+ negativeButton(CommonString.dialog_cancel) { it.dismiss() }
}
axisPicker.setBindingChangedListener { binding ->
dialog.dismiss()
val bindingPref = getPreferenceAssignedTo(binding)
if (bindingPref != null && bindingPref != this) {
AlertDialog.Builder(context).show {
- val warning = context.getString(R.string.bindings_already_bound, bindingPref.title)
+ val warning = context.getString(CommonString.bindings_already_bound, bindingPref.title)
setTitle(binding.toDisplayString(context))
setMessage(warning)
- setPositiveButton(R.string.dialog_positive_replace) { _, _ ->
+ setPositiveButton(CommonString.dialog_positive_replace) { _, _ ->
onAxisSelected(binding)
}
- setNegativeButton(R.string.dialog_cancel) { _, _ -> }
+ setNegativeButton(CommonString.dialog_cancel) { _, _ -> }
}
} else {
onAxisSelected(binding)
@@ -234,7 +235,7 @@ open class ControlPreferenceDialogFragment : DialogFragment() {
setTitle(preference.title)
setIcon(preference.icon)
customView(binding.root, paddingTop = 16.dp.toPx(context))
- negativeButton(R.string.dialog_cancel)
+ negativeButton(CommonString.dialog_cancel)
}
}
@@ -266,7 +267,7 @@ open class ControlPreferenceDialogFragment : DialogFragment() {
}
val titles =
bindings.map {
- getString(R.string.binding_remove_binding, getDisplayString(it))
+ getString(CommonString.binding_remove_binding, getDisplayString(it))
}
binding.listView.apply {
adapter = ArrayAdapter(requireContext(), R.layout.item_control_preference, titles)
diff --git a/AnkiDroid/src/main/java/com/ichi2/preferences/ExternalDirectorySelectionPreference.kt b/AnkiDroid/src/main/java/com/ichi2/preferences/ExternalDirectorySelectionPreference.kt
new file mode 100644
index 000000000000..2b004719075c
--- /dev/null
+++ b/AnkiDroid/src/main/java/com/ichi2/preferences/ExternalDirectorySelectionPreference.kt
@@ -0,0 +1,193 @@
+// SPDX-FileCopyrightText: 2024 David Allison
+// SPDX-FileCopyrightText: 2026 Shaan Narendran
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.preferences
+
+import android.content.Context
+import android.graphics.Color
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.ForegroundColorSpan
+import android.util.AttributeSet
+import android.view.ViewGroup
+import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
+import androidx.preference.ListPreference
+import androidx.preference.ListPreferenceDialogFragmentCompat
+import com.ichi2.anki.CommonString
+import com.ichi2.anki.R
+import com.ichi2.anki.common.crashreporting.runCatchingWithLog
+import com.ichi2.anki.common.utils.android.showThemedToast
+import com.ichi2.anki.startup.getDefaultAnkiDroidDirectory
+import com.ichi2.anki.utils.ext.containsFile
+import com.ichi2.utils.input
+import com.ichi2.utils.negativeButton
+import com.ichi2.utils.positiveButton
+import com.ichi2.utils.show
+import timber.log.Timber
+import java.io.File
+import java.nio.file.Files
+import java.nio.file.Paths
+
+/**
+ * Lets the user pick the AnkiDroid directory from a list of detected storage
+ * locations, so an SD card can be chosen without knowing its path.
+ * A custom path can still be typed in via the dialog.
+ */
+class ExternalDirectorySelectionPreference(
+ context: Context,
+ attrs: AttributeSet?,
+) : ListPreference(context, attrs),
+ DialogFragmentProvider {
+ init {
+ summaryProvider =
+ SummaryProvider { pref ->
+ pref.value.takeUnless { it.isNullOrEmpty() } ?: context.getString(CommonString.pref_directory_not_set)
+ }
+ }
+
+ /** The default AnkiDroid directory, or null if storage is unavailable. */
+ private val defaultAnkiDir: File?
+ get() =
+ try {
+ getDefaultAnkiDroidDirectory(context)
+ } catch (e: Exception) {
+ Timber.w(e, "Could not access default AnkiDroid directory")
+ null
+ }
+
+ /**
+ * Builds the candidate paths to show: the current value, the default
+ * directory, and anything found by scanning storage.
+ */
+ private fun loadDirectories(): List =
+ buildList {
+ if (value?.isNotEmpty() == true) {
+ add(File(value))
+ }
+ defaultAnkiDir?.let { add(it) }
+ addAll(getScannedDirectories())
+ }.map { it.absolutePath }
+ .distinct()
+
+ private fun isValidAnkiDir(dir: File): Boolean {
+ try {
+ if (!dir.isDirectory || dir.name.startsWith(".")) return false
+ if (IGNORED_DIRECTORIES.contains(dir.name.lowercase())) return false
+ if (dir.name.startsWith("AnkiDroid", ignoreCase = true)) return true
+ return dir.containsFile("collection.anki2")
+ } catch (e: Exception) {
+ Timber.w(e, "Could not access directory")
+ return false
+ }
+ }
+
+ /**
+ * Scans the external storage roots for AnkiDroid directories.
+ * A root with none, or which cannot be listed, is offered as a new AnkiDroid location instead.
+ */
+ private fun getScannedDirectories(): List {
+ val storageRoots =
+ runCatchingWithLog("get storage roots") {
+ context.getExternalFilesDirs(null)
+ }.getOrNull()
+ .orEmpty()
+ .filterNotNull()
+
+ fun File.getValidAnkiSubfolders(): List? {
+ val subFolders = listFiles() ?: return null
+ return subFolders.filter { isValidAnkiDir(it) }.ifEmpty { null }
+ }
+
+ return storageRoots.flatMap { root ->
+ when (val ankiFolders = root.getValidAnkiSubfolders()) {
+ // If no anki directories are found, we can list this as it is likely an SD card
+ null -> listOf(File(root, "AnkiDroid"))
+ else -> ankiFolders
+ }
+ }
+ }
+
+ // TODO: Possibly move loadDirectories() to a background thread if ANR occurs
+ override fun makeDialogFragment(): DialogFragment {
+ val paths = loadDirectories()
+ entries = paths.map(::toDisplayLabel).toTypedArray()
+ entryValues = paths.toTypedArray()
+ return FullWidthListPreferenceDialogFragment()
+ }
+
+ /** Builds the display label for a path, graying out its storage root. */
+ private fun toDisplayLabel(path: String): CharSequence {
+ // Split at "/Android/" so the root is gray and the rest keeps the normal color
+ // Eg: "/storage/emulated/0" is gray, "/Android/data/com.ichi2.anki" is not
+ val androidIndex = path.indexOf("/Android/")
+ if (androidIndex == -1) return path
+ val displayString = "${path.take(androidIndex)}\n${path.substring(androidIndex)}"
+ return SpannableString(displayString).apply {
+ setSpan(ForegroundColorSpan(Color.GRAY), 0, androidIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
+ }
+ }
+
+ companion object {
+ private val IGNORED_DIRECTORIES = setOf("collection.media", "backups", "cache", "code_cache")
+ }
+}
+
+/** List dialog stretched to full width, with a neutral button to enter a custom path. */
+class FullWidthListPreferenceDialogFragment : ListPreferenceDialogFragmentCompat() {
+ override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
+ super.onPrepareDialogBuilder(builder)
+ builder.setNeutralButton(CommonString.pref_custom_path) { _, _ -> showCustomPathInput() }
+ }
+
+ private fun showCustomPathInput() {
+ val context = requireContext()
+ val pref = (preference as? ExternalDirectorySelectionPreference) ?: return
+ AlertDialog
+ .Builder(context)
+ .show {
+ setTitle(CommonString.pref_enter_custom_path)
+ setView(R.layout.dialog_generic_text_input)
+ positiveButton(android.R.string.ok)
+ negativeButton(android.R.string.cancel)
+ }.input(
+ prefill = pref.value ?: "",
+ allowEmpty = false,
+ ) { dialog, text ->
+ try {
+ // normalize before saving so "a//b/" and "a/b" become one entry
+ val pathObj = Paths.get(text.toString().trim()).toAbsolutePath().normalize()
+ val newPath = pathObj.toString()
+ Files.createDirectories(pathObj)
+ if (!Files.isWritable(pathObj)) {
+ showThemedToast(
+ context,
+ context.getString(CommonString.pref_directory_not_writable),
+ true,
+ )
+ return@input
+ }
+ dialog.dismiss()
+ if (pref.callChangeListener(newPath)) {
+ pref.value = newPath
+ }
+ } catch (e: Exception) {
+ Timber.w(e, "Failed to set custom path")
+ AlertDialog.Builder(context).show {
+ setTitle(context.getString(CommonString.could_not_create_dir, text.toString()))
+ setMessage(e.stackTraceToString())
+ positiveButton(android.R.string.ok)
+ }
+ }
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ dialog?.window?.setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
+ }
+}
diff --git a/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreferenceCompat.kt b/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreferenceCompat.kt
index 724ef2b13b63..2f1b5ba0e4ca 100644
--- a/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreferenceCompat.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreferenceCompat.kt
@@ -23,7 +23,7 @@ import android.view.View
import android.widget.Button
import androidx.core.widget.doAfterTextChanged
import com.ichi2.anki.CollectionManager.TR
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.databinding.DialogIncrementerPreferenceBinding
import com.ichi2.anki.ui.NonLeadingZeroInputFilter
import com.ichi2.utils.moveCursorToEnd
@@ -160,14 +160,14 @@ class IncrementerNumberRangePreferenceCompat :
}
ValidationResult.OVERFLOW -> {
binding.decrementButton.isEnabled = true
- binding.textInputLayout.error = getString(R.string.maximum_value_is, numberRangePreference.max)
+ binding.textInputLayout.error = getString(CommonString.maximum_value_is, numberRangePreference.max)
}
ValidationResult.UNDERFLOW -> {
binding.incrementButton.isEnabled = true
- binding.textInputLayout.error = getString(R.string.minimum_value_is, numberRangePreference.min)
+ binding.textInputLayout.error = getString(CommonString.minimum_value_is, numberRangePreference.min)
}
ValidationResult.INVALID -> {
- binding.textInputLayout.error = getString(R.string.invalid_value)
+ binding.textInputLayout.error = getString(CommonString.invalid_value)
}
ValidationResult.EMPTY -> {
// Empty input is invalid for submission, but doesn't warrant an error message yet
diff --git a/AnkiDroid/src/main/java/com/ichi2/ui/AccessibleSearchView.kt b/AnkiDroid/src/main/java/com/ichi2/ui/AccessibleSearchView.kt
index ca4ca7ce9bc1..5936b87a1df4 100644
--- a/AnkiDroid/src/main/java/com/ichi2/ui/AccessibleSearchView.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/ui/AccessibleSearchView.kt
@@ -19,7 +19,7 @@ package com.ichi2.ui
import android.content.Context
import android.util.AttributeSet
import android.widget.ImageView
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.compat.setTooltipTextCompat
/**
@@ -35,7 +35,7 @@ open class AccessibleSearchView : androidx.appcompat.widget.SearchView {
init {
// close_btn is the cross that deletes the search field content. It does not close the search view.
findViewById(androidx.appcompat.R.id.search_close_btn)
- ?.setTooltipTextCompat(context.getString(R.string.discard))
+ ?.setTooltipTextCompat(context.getString(CommonString.discard))
}
// SearchView contains four buttons. The three others seems never to appear in ankidroid.
// there is also an arrow to the trailing side, that should get a tooltip. Alas, I fail to see the id of this button, so I can't add it.
diff --git a/AnkiDroid/src/main/java/com/ichi2/ui/GesturePicker.kt b/AnkiDroid/src/main/java/com/ichi2/ui/GesturePicker.kt
index 20398b26aa1c..71f4d8b64592 100644
--- a/AnkiDroid/src/main/java/com/ichi2/ui/GesturePicker.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/ui/GesturePicker.kt
@@ -12,7 +12,7 @@ import android.widget.ArrayAdapter
import android.widget.Spinner
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.cardviewer.Gesture
import com.ichi2.anki.cardviewer.GestureListener
import com.ichi2.anki.databinding.ViewGesturePickerBinding
@@ -80,7 +80,7 @@ open class GesturePicker(
inner class GestureWrapper(
val gesture: Gesture?,
) {
- override fun toString(): String = gesture?.toDisplayString(context) ?: resources.getString(R.string.gestures_none)
+ override fun toString(): String = gesture?.toDisplayString(context) ?: resources.getString(CommonString.gestures_none)
override fun equals(other: Any?): Boolean {
if (this === other) return true
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/AlarmManagement.kt b/AnkiDroid/src/main/java/com/ichi2/utils/AlarmManagement.kt
index 42453070cd89..a733b4b43141 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/AlarmManagement.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/AlarmManagement.kt
@@ -7,7 +7,7 @@ import android.app.AlarmManager
import android.content.Context
import androidx.annotation.StringRes
import androidx.core.content.getSystemService
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.anki.reviewreminders.ReviewReminderAlarmManager
import timber.log.Timber
@@ -49,10 +49,10 @@ object AlarmManagement {
} catch (ex: SecurityException) {
// #6332 - Too Many Alarms on Samsung Devices - this stops a fatal startup crash.
// We warn the user if they breach this limit
- error = R.string.boot_service_too_many_notifications
+ error = CommonString.boot_service_too_many_notifications
errorHandler(ex, "Too many alarms set")
} catch (e: Exception) {
- error = R.string.boot_service_failed_to_schedule_notifications
+ error = CommonString.boot_service_failed_to_schedule_notifications
errorHandler(e, "Failed to schedule alarm")
}
if (error != null && showToastOnFailure) {
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt b/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt
index d1c5c284d42f..1446d8df72de 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/AlertDialogFacade.kt
@@ -469,7 +469,7 @@ fun AlertDialog.Builder.listItemsAndMessage(
* **Example:**
* ```kotlin
* MaterialAlertDialogBuilder(context).create {
- * titleWithHelpIcon(stringRes = R.string.reset_card_dialog_title) {
+ * titleWithHelpIcon(stringRes = CommonString.reset_card_dialog_title) {
* requireActivity().openUrl(Uri.parse(getString(R.string.link_manual)))
* }
* }
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/ClipboardUtil.kt b/AnkiDroid/src/main/java/com/ichi2/utils/ClipboardUtil.kt
index 27d831c2bbf6..ea9511cf4617 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/ClipboardUtil.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/ClipboardUtil.kt
@@ -14,7 +14,7 @@ import androidx.annotation.CheckResult
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.core.content.getSystemService
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.utils.android.showThemedToast
import com.ichi2.anki.snackbar.canProperlyShowSnackbars
import com.ichi2.anki.snackbar.showSnackbar
@@ -118,8 +118,8 @@ value class TruncatedString private constructor(
*/
fun Context.copyToClipboard(
text: TruncatedString,
- @StringRes successMessageId: Int = R.string.about_ankidroid_successfully_copied_debug_info,
- @StringRes failureMessageId: Int = R.string.failed_to_copy,
+ @StringRes successMessageId: Int = CommonString.about_ankidroid_successfully_copied_debug_info,
+ @StringRes failureMessageId: Int = CommonString.failed_to_copy,
): Boolean {
val copied = copyTextToClipboard(text)
// in Android S_V2 and above, the system is guaranteed to show a message on a successful copy
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/ExceptionUtil.kt b/AnkiDroid/src/main/java/com/ichi2/utils/ExceptionUtil.kt
index c4a3f06580ea..f62214ff7a5a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/ExceptionUtil.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/ExceptionUtil.kt
@@ -4,7 +4,7 @@ package com.ichi2.utils
import android.content.Context
import androidx.annotation.CheckResult
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.utils.android.showThemedToast
import java.io.PrintWriter
@@ -51,7 +51,7 @@ object ExceptionUtil {
CrashReportService.sendExceptionReport(e, origin)
showThemedToast(
context,
- context.getString(R.string.multimedia_editor_something_wrong),
+ context.getString(CommonString.multimedia_editor_something_wrong),
true,
)
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt b/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt
index 9c0efe8b7049..0afc66b6aadb 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/ImportUtils.kt
@@ -17,6 +17,7 @@ import androidx.appcompat.app.AlertDialog
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.android.appContext
import com.ichi2.anki.common.annotations.NeedsTest
@@ -135,7 +136,7 @@ object ImportUtils {
CrashReportService.sendExceptionReport(e, "handleFileImport")
Timber.e(e, "failed to handle import intent")
ImportResult.Failure(
- humanReadableMessage = context.getString(R.string.import_error_handle_exception, e.localizedMessage),
+ humanReadableMessage = context.getString(CommonString.import_error_handle_exception, e.localizedMessage),
exception = e,
)
}
@@ -149,7 +150,7 @@ object ImportUtils {
return if (importPathUri != null) {
handleContentProviderFile(context, importPathUri, intent)
} else {
- ImportResult.Failure(context.getString(R.string.import_error_handle_exception))
+ ImportResult.Failure(context.getString(CommonString.import_error_handle_exception))
}
}
@@ -194,7 +195,7 @@ object ImportUtils {
): ImportResult {
// Note: intent.getData() can be null. Use data instead.
if (!isValidImportType(context, importPathUri)) {
- return ImportResult.Failure(context.getString(R.string.import_log_no_apkg))
+ return ImportResult.Failure(context.getString(CommonString.import_log_no_apkg))
}
// Get the original filename from the content provider URI
var filename = getFileNameFromContentProvider(context, importPathUri)
@@ -216,7 +217,7 @@ object ImportUtils {
)
return ImportResult.Failure(
AnkiDroidApp.appResources.getString(
- R.string.import_error_content_provider,
+ CommonString.import_error_content_provider,
AnkiDroidApp.manualUrl + "#importing",
),
)
@@ -230,10 +231,10 @@ object ImportUtils {
return if (isAnkiDatabase(filename)) {
// .anki2 files aren't supported by Anki Desktop, we should eventually support them, because we can
// but for now, show a "nice" error.
- ImportResult.Failure(context.resources.getString(R.string.import_error_load_imported_database))
+ ImportResult.Failure(context.resources.getString(CommonString.import_error_load_imported_database))
} else {
// Don't import if file doesn't have an Anki package extension
- ImportResult.Failure(context.resources.getString(R.string.import_error_not_apkg_extension, filename))
+ ImportResult.Failure(context.resources.getString(CommonString.import_error_not_apkg_extension, filename))
}
}
@@ -245,11 +246,11 @@ object ImportUtils {
} catch (_: SecurityException) {
// Do not log the exception: it may interpolate attacker-controlled paths (PII).
Timber.e("Path traversal detected in handleContentProviderFile")
- return ImportResult.Failure(context.getString(R.string.import_error_handle_exception, "Invalid path"))
+ return ImportResult.Failure(context.getString(CommonString.import_error_handle_exception, "Invalid path"))
} catch (_: IllegalArgumentException) {
// Do not log the exception: it may interpolate attacker-controlled paths (PII).
Timber.e("Path traversal detected in handleContentProviderFile")
- return ImportResult.Failure(context.getString(R.string.import_error_handle_exception, "Invalid path"))
+ return ImportResult.Failure(context.getString(CommonString.import_error_handle_exception, "Invalid path"))
}
val tempOutDir: String = Uri.fromFile(checkFile).encodedPath!!
@@ -371,13 +372,13 @@ object ImportUtils {
}
Timber.d("showImportUnsuccessfulDialog() message %s", failure.humanReadableMessage)
- val title = failure.title ?: activity.getString(R.string.import_title_error)
+ val title = failure.title ?: activity.getString(CommonString.import_title_error)
val dialog =
AlertDialog.Builder(activity).show {
title(text = title)
message(text = failure.humanReadableMessage)
setCancelable(false)
- positiveButton(R.string.dialog_ok) {
+ positiveButton(CommonString.dialog_ok) {
if (exitActivity) {
activity.finish()
}
@@ -475,7 +476,7 @@ object ImportUtils {
val exceptionForReport: Exception?,
val userFacingException: Exception?,
) {
- fun buildTitle(context: Context) = context.getString(R.string.import_error_copy_to_cache_title)
+ fun buildTitle(context: Context) = context.getString(CommonString.import_error_copy_to_cache_title)
fun buildHumanReadableMessage(context: Context) =
buildString {
@@ -495,7 +496,7 @@ object ImportUtils {
appendLine("\u2022 $line")
}
}
- append(context.getString(R.string.import_error_copy_to_cache_explanation, suggestedFixes))
+ append(context.getString(CommonString.import_error_copy_to_cache_explanation, suggestedFixes))
}
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/IntentUtil.kt b/AnkiDroid/src/main/java/com/ichi2/utils/IntentUtil.kt
index 7ac11bd5f36a..c48adf3d0a23 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/IntentUtil.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/IntentUtil.kt
@@ -7,7 +7,7 @@ import android.content.Intent
import android.webkit.MimeTypeMap
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.AnkiActivity
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.snackbar.showSnackbar
import timber.log.Timber
@@ -63,12 +63,12 @@ object IntentUtil {
if (canOpenIntent(activity, intent)) {
activity.startActivity(intent)
} else {
- val errorMsg = activity.getString(R.string.feedback_no_suitable_app_found)
+ val errorMsg = activity.getString(CommonString.feedback_no_suitable_app_found)
activity.showSnackbar(errorMsg, Snackbar.LENGTH_SHORT)
}
} catch (e: Exception) {
Timber.w(e)
- val errorMsg = activity.getString(R.string.feedback_no_suitable_app_found)
+ val errorMsg = activity.getString(CommonString.feedback_no_suitable_app_found)
activity.showSnackbar(errorMsg, Snackbar.LENGTH_SHORT)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt b/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt
index 0ebf5bfc83c8..b76722010e02 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt
@@ -8,7 +8,7 @@ import android.content.res.Resources
import androidx.annotation.StringRes
import androidx.core.os.ConfigurationCompat
import androidx.fragment.app.Fragment
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.android.appContext
import com.ichi2.anki.common.preferences.sharedPrefs
import com.ichi2.anki.compat.CompatHelper
@@ -361,7 +361,7 @@ object LanguageUtil {
* Uses ListFormatter on API 26+ to dynamically get the locale-specific separator
*/
fun getListSeparator(context: Context): String =
- CompatHelper.compat.getListSeparator(context, context.getString(R.string.list_separator))
+ CompatHelper.compat.getListSeparator(context, context.getString(CommonString.list_separator))
/**
* Wraps the provided string in Unicode isolation markers (FSI and PDI) to ensure bidirectional markers
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/Permissions.kt b/AnkiDroid/src/main/java/com/ichi2/utils/Permissions.kt
index a62b808da68f..fac9135b3903 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/Permissions.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/Permissions.kt
@@ -17,9 +17,9 @@ import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
+import com.ichi2.anki.CommonString
import com.ichi2.anki.NotificationChannel
import com.ichi2.anki.OptionalPermissionSet
-import com.ichi2.anki.R
import com.ichi2.anki.common.permissions.LEGACY_POST_NOTIFICATIONS
import com.ichi2.anki.common.permissions.MANAGE_EXTERNAL_STORAGE
import com.ichi2.anki.common.permissions.canPostNotifications
@@ -86,7 +86,7 @@ object Permissions {
permissionRequestedFlag.setter.call(true)
permissionRequestLauncher.launch(permission)
} else {
- showToastAndOpenAppSettingsScreenForPermission(permission, R.string.manually_grant_permissions)
+ showToastAndOpenAppSettingsScreenForPermission(permission, CommonString.manually_grant_permissions)
}
}
@@ -104,7 +104,7 @@ object Permissions {
permissionRequestLauncher = notificationPermissionLauncher,
)
} else {
- showToastAndOpenAppSettingsScreenForPermission(LEGACY_POST_NOTIFICATIONS, R.string.manually_grant_permissions)
+ showToastAndOpenAppSettingsScreenForPermission(LEGACY_POST_NOTIFICATIONS, CommonString.manually_grant_permissions)
}
}
@@ -273,7 +273,7 @@ object Permissions {
)
} catch (e: ActivityNotFoundException) {
Timber.w(e, "No app can show the app settings screen")
- showThemedToast(this, R.string.activity_start_failed, false)
+ showThemedToast(this, CommonString.activity_start_failed, false)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.kt b/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.kt
index 043f542ba46f..89d16c55379a 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.kt
@@ -6,7 +6,7 @@ import android.app.Activity
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
-import com.ichi2.anki.common.preferences.sharedPrefs
+import com.ichi2.anki.settings.Prefs
import timber.log.Timber
import java.util.ArrayList
@@ -33,7 +33,7 @@ object ViewGroupUtils {
}
fun setRenderWorkaround(activity: Activity) {
- if (activity.sharedPrefs().getBoolean("softwareRender", false)) {
+ if (Prefs.useSoftwareRendering) {
Timber.i("ViewGroupUtils::setRenderWorkaround - software render requested, altering Views...")
setContentViewLayerTypeSoftware(activity)
} else {
diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/WebViewUtils.kt b/AnkiDroid/src/main/java/com/ichi2/utils/WebViewUtils.kt
index bdd9ce9262b7..d20156f4fa23 100644
--- a/AnkiDroid/src/main/java/com/ichi2/utils/WebViewUtils.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/utils/WebViewUtils.kt
@@ -14,6 +14,7 @@ import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.core.content.pm.PackageInfoCompat
import androidx.webkit.WebViewCompat
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.utils.openUrl
@@ -175,8 +176,8 @@ private fun showOutdatedWebViewDialog(
onDismiss: () -> Unit = {},
) {
AlertDialog.Builder(context).show {
- setMessage(context.getString(R.string.webview_update_message, installedVersion, OLDEST_WORKING_WEBVIEW_VERSION.value))
- setPositiveButton(R.string.scoped_storage_learn_more) { _, _ ->
+ setMessage(context.getString(CommonString.webview_update_message, installedVersion, OLDEST_WORKING_WEBVIEW_VERSION.value))
+ setPositiveButton(CommonString.scoped_storage_learn_more) { _, _ ->
context.openUrl(learnMoreUrl)
}
setOnDismissListener { onDismiss() }
diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.kt b/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.kt
index 848d9f3d3518..917563f462eb 100644
--- a/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.kt
@@ -20,6 +20,7 @@ import androidx.annotation.LayoutRes
import androidx.core.app.PendingIntentCompat
import androidx.core.content.ContextCompat
import androidx.core.content.edit
+import com.ichi2.anki.CommonPlurals
import com.ichi2.anki.R
import com.ichi2.anki.common.android.appContext
import com.ichi2.anki.common.destinations.DeferredNavigation
@@ -147,7 +148,7 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
updateViews.setTextViewText(R.id.widget_due, dueCardsCount.toString())
updateViews.setContentDescription(
R.id.widget_due,
- context.resources.getQuantityString(R.plurals.widget_cards_due, dueCardsCount, dueCardsCount),
+ context.resources.getQuantityString(CommonPlurals.widget_cards_due, dueCardsCount, dueCardsCount),
)
if (eta <= 0) {
@@ -158,7 +159,7 @@ class AnkiDroidWidgetSmall : AnalyticsWidgetProvider() {
updateViews.setTextViewText(R.id.widget_eta, etaText)
updateViews.setContentDescription(
R.id.widget_eta,
- context.resources.getQuantityString(R.plurals.widget_eta, eta, eta),
+ context.resources.getQuantityString(CommonPlurals.widget_eta, eta, eta),
)
}
}
diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidget.kt b/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidget.kt
index 3997e058f87f..8e65a91109e2 100644
--- a/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidget.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidget.kt
@@ -11,6 +11,7 @@ import android.content.Context
import android.content.Intent
import android.view.View
import android.widget.RemoteViews
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.coroutines.applicationScope
import com.ichi2.anki.common.crashreporting.CrashReportService
@@ -110,7 +111,7 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
appWidgetId: AppWidgetId,
remoteViews: RemoteViews,
) {
- remoteViews.setTextViewText(R.id.empty_widget, context.getString(R.string.empty_collection_state_in_widget))
+ remoteViews.setTextViewText(R.id.empty_widget, context.getString(CommonString.empty_collection_state_in_widget))
remoteViews.setViewVisibility(R.id.empty_widget, View.VISIBLE)
remoteViews.setViewVisibility(R.id.cardAnalysisDataHolder, View.GONE)
remoteViews.setViewVisibility(R.id.deckNameCardAnalysis, View.GONE)
@@ -139,7 +140,7 @@ class CardAnalysisWidget : AnalyticsWidgetProvider() {
remoteViews: RemoteViews,
) {
// Show empty_widget and set click listener to open configuration
- remoteViews.setTextViewText(R.id.empty_widget, context.getString(R.string.empty_widget_state))
+ remoteViews.setTextViewText(R.id.empty_widget, context.getString(CommonString.empty_widget_state))
remoteViews.setViewVisibility(R.id.empty_widget, View.VISIBLE)
remoteViews.setViewVisibility(R.id.cardAnalysisDataHolder, View.GONE)
remoteViews.setViewVisibility(R.id.deckNameCardAnalysis, View.GONE)
diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt b/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt
index aa8e8d7be1ec..1f4722ecadec 100644
--- a/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/widget/cardanalysis/CardAnalysisWidgetConfig.kt
@@ -19,6 +19,7 @@ import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.updateMargins
import com.ichi2.anki.AnkiActivity
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.android.AnkiBroadcastReceiver
import com.ichi2.anki.common.utils.android.showThemedToast
@@ -118,7 +119,7 @@ class CardAnalysisWidgetConfig : AnkiActivity(R.layout.activity_card_analysis_wi
private fun onDeckSelected(deck: SelectableDeck?) {
if (deck == null || deck !is SelectableDeck.Deck?) {
- showThemedToast(this, R.string.something_wrong, false)
+ showThemedToast(this, CommonString.something_wrong, false)
setResult(RESULT_CANCELED)
finish()
return
@@ -142,7 +143,7 @@ class CardAnalysisWidgetConfig : AnkiActivity(R.layout.activity_card_analysis_wi
Timber.w("CardAnalysisWidgetConfig: collection is empty")
showThemedToast(
this@CardAnalysisWidgetConfig,
- R.string.no_cards_placeholder_title,
+ CommonString.no_cards_placeholder_title,
false,
)
finish()
@@ -161,7 +162,7 @@ class CardAnalysisWidgetConfig : AnkiActivity(R.layout.activity_card_analysis_wi
private fun showDeckSelectionDialog() {
startDeckSelection(
- title = getString(R.string.select_deck_title),
+ title = getString(CommonString.select_deck_title),
allowAll = false,
skipEmptyDefault = true,
)
diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidget.kt b/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidget.kt
index ece3b92dcfd7..a5dcb0910079 100644
--- a/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidget.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidget.kt
@@ -12,6 +12,7 @@ import android.content.Intent
import android.view.View
import android.widget.RemoteViews
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.coroutines.applicationScope
import com.ichi2.anki.common.crashreporting.CrashReportService
@@ -159,7 +160,7 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
appWidgetId: AppWidgetId,
remoteViews: RemoteViews,
) {
- remoteViews.setTextViewText(R.id.empty_widget, context.getString(R.string.empty_collection_state_in_widget))
+ remoteViews.setTextViewText(R.id.empty_widget, context.getString(CommonString.empty_collection_state_in_widget))
remoteViews.setViewVisibility(R.id.empty_widget, View.VISIBLE)
remoteViews.setViewVisibility(R.id.deckCollection, View.GONE)
@@ -186,7 +187,7 @@ class DeckPickerWidget : AnalyticsWidgetProvider() {
appWidgetId: AppWidgetId,
remoteViews: RemoteViews,
) {
- remoteViews.setTextViewText(R.id.empty_widget, context.getString(R.string.empty_widget_state))
+ remoteViews.setTextViewText(R.id.empty_widget, context.getString(CommonString.empty_widget_state))
remoteViews.setViewVisibility(R.id.empty_widget, View.VISIBLE)
remoteViews.setViewVisibility(R.id.deckCollection, View.GONE)
diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt b/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt
index b5c81420b804..0cea9c030a18 100644
--- a/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt
+++ b/AnkiDroid/src/main/java/com/ichi2/widget/deckpicker/DeckPickerWidgetConfig.kt
@@ -27,6 +27,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import com.ichi2.anki.AnkiActivity
+import com.ichi2.anki.CommonPlurals
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.common.android.AnkiBroadcastReceiver
import com.ichi2.anki.common.utils.android.showThemedToast
@@ -121,7 +123,7 @@ class DeckPickerWidgetConfig :
Timber.w("Closing: Collection is empty")
showThemedToast(
this@DeckPickerWidgetConfig,
- R.string.empty_collection_state_in_widget,
+ CommonString.empty_collection_state_in_widget,
false,
)
finish()
@@ -154,7 +156,7 @@ class DeckPickerWidgetConfig :
// anchors to it instead of the Save button while the async
// updateFabVisibility() is still in flight.
binding.fabWidgetDeckPicker.isVisible = true
- showSnackbar(R.string.deck_removed_from_widget)
+ showSnackbar(CommonString.deck_removed_from_widget)
updateViewVisibility()
updateFabVisibility()
updateDoneButtonVisibility()
@@ -174,7 +176,7 @@ class DeckPickerWidgetConfig :
binding.fabWidgetDeckPicker.setOnClickListener {
// TODO previous code filtered the already selected decks, we guard against this for now
startDeckSelection(
- title = getString(R.string.select_decks_title),
+ title = getString(CommonString.select_decks_title),
allowAll = false,
skipEmptyDefault = true,
allowMultipleSelection = true,
@@ -257,7 +259,7 @@ class DeckPickerWidgetConfig :
* and the activity is finished.
*/
private fun setupDoneButton() {
- val saveText = getString(R.string.save).uppercase()
+ val saveText = getString(CommonString.save).uppercase()
// Set the button text and click listener only once during initialization
binding.submitButton.text = saveText
@@ -351,7 +353,7 @@ class DeckPickerWidgetConfig :
val isDeckAlreadySelected = deckAdapter.deckIds.contains(deck.deckId)
if (isDeckAlreadySelected) {
- showSnackbar(getString(R.string.deck_already_selected_message))
+ showSnackbar(getString(CommonString.deck_already_selected_message))
return
}
@@ -359,7 +361,7 @@ class DeckPickerWidgetConfig :
if (deckAdapter.itemCount >= MAX_DECKS_ALLOWED) {
// Snackbar will only be shown when adding the 5th deck
if (deckAdapter.itemCount == MAX_DECKS_ALLOWED) {
- showSnackbar(resources.getQuantityString(R.plurals.deck_limit_reached, MAX_DECKS_ALLOWED, MAX_DECKS_ALLOWED))
+ showSnackbar(resources.getQuantityString(CommonPlurals.deck_limit_reached, MAX_DECKS_ALLOWED, MAX_DECKS_ALLOWED))
}
// The FAB visibility should be handled in updateFabVisibility()
return
@@ -373,7 +375,7 @@ class DeckPickerWidgetConfig :
setUnsavedChanges(true)
// Show snackbar and dismiss the selection dialog once the 5th deck is reached
if (deckAdapter.itemCount == MAX_DECKS_ALLOWED) {
- showSnackbar(resources.getQuantityString(R.plurals.deck_limit_reached, MAX_DECKS_ALLOWED, MAX_DECKS_ALLOWED))
+ showSnackbar(resources.getQuantityString(CommonPlurals.deck_limit_reached, MAX_DECKS_ALLOWED, MAX_DECKS_ALLOWED))
dismissDeckSelectionDialog()
}
}
diff --git a/AnkiDroid/src/main/res/layout/fragment_custom_study.xml b/AnkiDroid/src/main/res/layout/fragment_custom_study.xml
index 1d9ac2f63810..f6a647f921cb 100644
--- a/AnkiDroid/src/main/res/layout/fragment_custom_study.xml
+++ b/AnkiDroid/src/main/res/layout/fragment_custom_study.xml
@@ -31,7 +31,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
- android:layout_marginBottom="16dip"
+ android:layout_marginBottom="4dip"
android:layout_marginStart="4dip"
android:layout_weight="4"
android:gravity="start|center"
diff --git a/AnkiDroid/src/main/res/menu/note_editor.xml b/AnkiDroid/src/main/res/menu/note_editor.xml
index 289f79ac5103..d8ad9ed56d34 100644
--- a/AnkiDroid/src/main/res/menu/note_editor.xml
+++ b/AnkiDroid/src/main/res/menu/note_editor.xml
@@ -44,4 +44,9 @@
android:checkable="true"
android:checked="true"
ankidroid:showAsAction="never"/>
-
\ No newline at end of file
+
+
diff --git a/AnkiDroid/src/main/res/values-af/02-strings.xml b/AnkiDroid/src/main/res/values-af/02-strings.xml
index a4621044650a..a44df437282d 100644
--- a/AnkiDroid/src/main/res/values-af/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-af/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Kaartpak:
- Pak:
+ Pak:
Tipe:
Etikette: %1$s
Kaarte: %1$s
@@ -220,6 +220,8 @@
Lettergrootte
Wys nutsbalk
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-af/03-dialogs.xml b/AnkiDroid/src/main/res/values-af/03-dialogs.xml
index aafa5d5e99bb..a09cbaee9716 100644
--- a/AnkiDroid/src/main/res/values-af/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-af/03-dialogs.xml
@@ -49,7 +49,7 @@
Verhoog/verlaag (“-3”) vandag se nuwe hersieningslimiet met
Hersien kaarte wat in die laaste x dae vergeet is
Review cards due in the next:
- Voorskou nuwe kaarte wat in die laaste x dae bygevoeg is:
+ Preview new cards added in the last:
Select x cards from the deck:
Stoor is vol
Stoor is amper vol
@@ -78,6 +78,7 @@
Oorskryf
Herstel
Vervang
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Die gespesifiseerde pad was nie \'n geldige gids nie.
Die verskafde teks los nie op na \'n geldige PEM-gekodeerde X509 sertifikaat nie.
Fout met die ontleding van sertifikaat
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Media word nagegaan…
Adding tags…
- Tagging results
+ Tags added
Media word uitgevee…
Verwyderingsresultaat
diff --git a/AnkiDroid/src/main/res/values-af/10-preferences.xml b/AnkiDroid/src/main/res/values-af/10-preferences.xml
index af4c66c9b022..51a5078f78ae 100644
--- a/AnkiDroid/src/main/res/values-af/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-af/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Af
+ Af
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-am/02-strings.xml b/AnkiDroid/src/main/res/values-am/02-strings.xml
index 49d3076932e4..7cd1529bac88 100644
--- a/AnkiDroid/src/main/res/values-am/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-am/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Type:
Tags: %1$s
Cards: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-am/03-dialogs.xml b/AnkiDroid/src/main/res/values-am/03-dialogs.xml
index 58d9cf1414e9..202ed1b91ba6 100644
--- a/AnkiDroid/src/main/res/values-am/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-am/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-am/10-preferences.xml b/AnkiDroid/src/main/res/values-am/10-preferences.xml
index fca2e8cb68c3..88ffb716551e 100644
--- a/AnkiDroid/src/main/res/values-am/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-am/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
የተለያዪ
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ar/02-strings.xml b/AnkiDroid/src/main/res/values-ar/02-strings.xml
index 9463bdf574f0..bf13435d2bed 100644
--- a/AnkiDroid/src/main/res/values-ar/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ar/02-strings.xml
@@ -29,7 +29,7 @@
تم حفظ الملحوظة
رزمة البطاقة:
- الرزمة:
+ الرزمة:
النوع:
الوسوم: %1$s
البطاقات: %1$s
@@ -232,6 +232,8 @@
حجم الخط
إظهار شريط الأدوات
+ Show preview
+ Hide preview
تعريض
إمالة
إضافة خطٍ سفلي
diff --git a/AnkiDroid/src/main/res/values-ar/03-dialogs.xml b/AnkiDroid/src/main/res/values-ar/03-dialogs.xml
index 0d90deb713e8..98127d3260cd 100644
--- a/AnkiDroid/src/main/res/values-ar/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ar/03-dialogs.xml
@@ -57,7 +57,7 @@
إضافة/طرح (مثلًا «-٣») إلى حد المراجعات لهذا اليوم
راجع البطاقات المنسية في آخر س يوم:
Review cards due in the next:
- عاين البطاقات الجديدة المضافة في آخر س يوم:
+ Preview new cards added in the last:
إختر س بطاقة من الرزمة:
ذاكرة التخزين ممتلئة
ذاكرة التخزين ممتلئة تقريبًا
@@ -86,6 +86,7 @@
استبدال
إصلاح
استبدال
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
المسار المحدد ليس مجلدًا صالحًا
لا يُحال النص المُدخل إلى شهادة «X509» مشفرة ب«PEM» صالحة.
حدث خطأ في تحليل الشهادة
@@ -94,7 +95,7 @@
قد يستغرق هذا وقتًا طويلًا مع مجموعات الوسائط الكبيرة. أتود الاستمرار؟
يتم فحص الوسائط…
تجري إضافة الوسوم…
- Tagging results
+ Tags added
جار حذف الوسائط…
نتيجة الحذف
diff --git a/AnkiDroid/src/main/res/values-ar/10-preferences.xml b/AnkiDroid/src/main/res/values-ar/10-preferences.xml
index b09452200de1..d9f172739ca1 100644
--- a/AnkiDroid/src/main/res/values-ar/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ar/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- مُعَطَّل
+ Enabled
+ مُعَطَّل
- %d دقيقة
- %d دقيقة
@@ -76,7 +77,7 @@
عرض أسماء الملفات الصوتية في حقول السؤال والجواب في متصفح البطاقات
مجلد آنكي درويد
وضع ملء الشاشة
- غير مفعل
+ غير مفعل
إخفاء أشرطة النظام
إخفاء أشرطة النظام وأزرار الإجابة
موضع أزرار الإجابة
@@ -226,11 +227,84 @@
التنقل
الوسائط
منوع
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
إضافة تنبيه
- إضافة تنبيه للمراجعة
- تعديل تنبيه المراجعة
ليس لديك تنبيهات للمراجعة
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ إضافة تنبيه للمراجعة
+ تعديل تنبيه المراجعة
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
اختيار وسوم
@@ -383,9 +457,15 @@
برأيك أو عن أي خلل تجده.]]>
إظهار توكيد الإجابة
شاشة
- إجابة فقط
- قلب وإجابة
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
فتح الإعدادات
الإعدادات المرتبطة
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-az/02-strings.xml b/AnkiDroid/src/main/res/values-az/02-strings.xml
index 458c76be51c5..1db0a8126b84 100644
--- a/AnkiDroid/src/main/res/values-az/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-az/02-strings.xml
@@ -29,7 +29,7 @@
Qeyd yadda saxlanıldı
Kart dəsti:
- Dəst:
+ Dəst:
Növ:
Etiketlər: %1$s
Kartlar: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-az/03-dialogs.xml b/AnkiDroid/src/main/res/values-az/03-dialogs.xml
index c2a9ed0314a6..0ed372b2fe0c 100644
--- a/AnkiDroid/src/main/res/values-az/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-az/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Son x günlərdə unudulmuş kartları nəzərdən keçirin:
Review cards due in the next:
- Son x günlərdə əlavə edilmiş yeni kartların Ön görünüşü:
+ Preview new cards added in the last:
Select x cards from the deck:
Yaddaş doludur
Yaddaş dolmaq üzrədir
@@ -78,6 +78,7 @@
Overwrite
Repair
Əvəz et
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Media silinir…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-az/10-preferences.xml b/AnkiDroid/src/main/res/values-az/10-preferences.xml
index 47fba78a508a..061eec00420d 100644
--- a/AnkiDroid/src/main/res/values-az/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-az/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Ləğv edildi
+ Enabled
+ Ləğv edildi
- %d dəqiqə
- %d dəqiqə
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Tam ekran rejimi
- Bağlı
+ Bağlı
Sistem sətirlərini gizlət
Sistem sətirlərini və cavab düymələrini gizlət
Cavab düymələrinin mövqeyi
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-be/02-strings.xml b/AnkiDroid/src/main/res/values-be/02-strings.xml
index 39fd6fd8da6e..8c0904fe2553 100644
--- a/AnkiDroid/src/main/res/values-be/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-be/02-strings.xml
@@ -29,7 +29,7 @@
Нататка захавана
Калода картак:
- Калода:
+ Калода:
Тып:
Меткі: %1$s
Карткі: %1$s
@@ -224,6 +224,8 @@
Памер шрыфту
Паказаць панэль інструментаў
+ Show preview
+ Hide preview
Фарматаваць як тлусты
Фарматаваць як курсіў
Фарматаваць як падкрэслены
diff --git a/AnkiDroid/src/main/res/values-be/03-dialogs.xml b/AnkiDroid/src/main/res/values-be/03-dialogs.xml
index fe4ba51d968e..2239c8945de6 100644
--- a/AnkiDroid/src/main/res/values-be/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-be/03-dialogs.xml
@@ -53,7 +53,7 @@
Павялічыць/паменшыць (“-3”) абмежаванні паўтарэння новых картак на сёння
Паўтарыць забытыя карткі за апошнія некалькі дзён:
Review cards due in the next:
- Прагледзець новыя карткі, якія дадзены за апошнія некалькі дзён:
+ Preview new cards added in the last:
Выберыце х картак з калоды:
Сховішча запоўнена
Сховішча амаль запоўнена
@@ -82,6 +82,7 @@
Перазапісаць
Выправіць
Замяніць
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Азначаны шлях не з\'яўляецца дапушчальным каталогам
Прыведзены тэкст не пераўтвараецца ў дапушчальны сертыфікат X509 з кадзіраваннем PEM
Памылка разборкі сертыфіката
@@ -90,7 +91,7 @@
Гэта можа заняць шмат часу ў тым выпадку, калі калекцыя медыяфайлаў занадта вялікая. Працягнуць?
Праверка медыяфайлаў…
Дадаванне тэгаў…
- Вынікі тэгавання
+ Tags added
Выдаленне медыяфайлаў…
Вынік выдалення
diff --git a/AnkiDroid/src/main/res/values-be/10-preferences.xml b/AnkiDroid/src/main/res/values-be/10-preferences.xml
index 8b319441cfe2..1238b9e006c6 100644
--- a/AnkiDroid/src/main/res/values-be/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-be/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Адключана
+ Enabled
+ Адключана
- %d хв
- %d хв
@@ -74,7 +75,7 @@
Адлюстроўваць назвы аўдыяфайлаў у аглядальніку картак у палях пытанні/адказы
Каталог AnkiDroid
Поўнаэкранны рэжым
- Выключаны
+ Выключаны
Схаваць сістэмную панэль
Схаваць сістэмную панэль і панэль кнопак адказу
Становішча кнопак адказу
@@ -223,11 +224,82 @@
Навігацыя
Медыя
Рознае
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Дадаць напамін
- Дадаць нагадванне паўтарэння
- Рэдагаваць нагадванне паўтарэння
У вас адсутнічаюць нагадванні паўтарэння
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Дадаць нагадванне паўтарэння
+ Рэдагаваць нагадванне паўтарэння
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Выберыце меткі
@@ -380,9 +452,15 @@
водгукі або памылкі.]]>
Паказваць зваротную сувязь адказу
Экран
- Толькі адказ
- Перавярнуць і адказаць
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Адкрыць налады
Звязаныя налады
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-bg/02-strings.xml b/AnkiDroid/src/main/res/values-bg/02-strings.xml
index 2b67e0c9e9eb..b37a2379eb2c 100644
--- a/AnkiDroid/src/main/res/values-bg/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-bg/02-strings.xml
@@ -29,7 +29,7 @@
Запазена бележка
Тесте от карти:
- Тесте:
+ Тесте:
Тип:
Етикети:%1$s
Карти: %1$s
@@ -220,6 +220,8 @@
Големина на шрифт
Показване на лентата с инструменти
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-bg/03-dialogs.xml b/AnkiDroid/src/main/res/values-bg/03-dialogs.xml
index 6344af322996..357dfef7e79c 100644
--- a/AnkiDroid/src/main/res/values-bg/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-bg/03-dialogs.xml
@@ -49,7 +49,7 @@
Увеличаване/намаляване (\"-3\") днешен лимитът на повтарящи се карти
Повтаряне на забравени карти през последните x дни:
Review cards due in the next:
- Преглед на нови карти, добавени през последните x дни:
+ Preview new cards added in the last:
Select x cards from the deck:
Пълно хранилище
Почти пълно хранилище
@@ -78,6 +78,7 @@
Презапиши
Поправи
Замени
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Указаният път не е валидна директория
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Проверка на медия…
Adding tags…
- Tagging results
+ Tags added
Изтриване на медийни файлове…
Резултат за изтриване
diff --git a/AnkiDroid/src/main/res/values-bg/10-preferences.xml b/AnkiDroid/src/main/res/values-bg/10-preferences.xml
index d899e2dcdb15..8d42ad08ecb5 100644
--- a/AnkiDroid/src/main/res/values-bg/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-bg/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Изключен
+ Enabled
+ Изключен
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
Директория на AnkiDroid
Режим на цял екран
- Изкл.
+ Изкл.
Скриване на системните ленти
Скриване на системните ленти и бутоните за отговори
Място на бутона за отговор
@@ -218,11 +219,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Изберете етикети
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-bn/02-strings.xml b/AnkiDroid/src/main/res/values-bn/02-strings.xml
index f474b7ac8b25..f565bc9d97bc 100644
--- a/AnkiDroid/src/main/res/values-bn/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-bn/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
কার্ড ডেক:
- ডেক:
+ ডেক:
ধরন:
ট্যাগসমূহ: %1$s
কার্ড: %1$s
@@ -220,6 +220,8 @@
ফন্টের আকার
টুলবার দেখান
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-bn/03-dialogs.xml b/AnkiDroid/src/main/res/values-bn/03-dialogs.xml
index 8d9d920f852a..5f84559395c2 100644
--- a/AnkiDroid/src/main/res/values-bn/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-bn/03-dialogs.xml
@@ -49,7 +49,7 @@
আজকের পর্যালোচনা সীমা বাড়ান/কমান (“-3”)
গত x দিনে ভুলে যাওয়া কার্ডগুলি পর্যালোচনা করুন:
Review cards due in the next:
- গত x দিনে যোগ করা নতুন কার্ডের পূর্বরূপ দেখুন:
+ Preview new cards added in the last:
Select x cards from the deck:
স্টোরেজ পূর্ণ
স্টোরেজ প্রায় পূর্ণ
@@ -78,6 +78,7 @@
ওভাররাইট করুন
মেরামত
প্রতিস্থাপন
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
নির্দিষ্ট পথ একটি বৈধ ডিরেক্টরি ছিল না
The provided text does not resolve to a valid PEM-encoded X509 certificate
শংসাপত্র পার্সিং ত্রুটি৷
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
মিডিয়া পরীক্ষা করা হচ্ছে…
Adding tags…
- Tagging results
+ Tags added
মিডিয়া মুছে ফেলা হচ্ছে…
মুছে ফেলার ফলাফল
diff --git a/AnkiDroid/src/main/res/values-bn/10-preferences.xml b/AnkiDroid/src/main/res/values-bn/10-preferences.xml
index b61f44d2c244..64a295369ca6 100644
--- a/AnkiDroid/src/main/res/values-bn/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-bn/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- নিষ্ক্রিয় করুন
+ Enabled
+ নিষ্ক্রিয় করুন
- %d মিনিট
- %d মিনিট
@@ -73,7 +74,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid ডিরেক্টরি
ফুলস্ক্রিন মোড
- বন্ধ
+ বন্ধ
সিস্টেম বার আড়াল করুন
সিস্টেম বার এবং উত্তর বোতাম লুকান
উত্তর বোতামের অবস্থান
@@ -219,11 +220,80 @@
নেভিগেশন
মিডিয়া
বিবিধ
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
ট্যাগ নির্বাচন করুন
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
সেটিংস খুলুন
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ca/02-strings.xml b/AnkiDroid/src/main/res/values-ca/02-strings.xml
index 859c30891ca9..165d72ef7e86 100644
--- a/AnkiDroid/src/main/res/values-ca/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ca/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Paquet de cartes:
- Paquet:
+ Paquet:
Tipus:
Etiquetes: %1$s
Cartes: %1$s
@@ -220,6 +220,8 @@
Mida de la lletra
Mostra la barra d\'eines
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ca/03-dialogs.xml b/AnkiDroid/src/main/res/values-ca/03-dialogs.xml
index dd0986101c76..df7a0839e935 100644
--- a/AnkiDroid/src/main/res/values-ca/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ca/03-dialogs.xml
@@ -49,7 +49,7 @@
Augmenta/disminueix (\"-3\") el límit d\'avui de revisions a
Revisa les fitxes oblidades en els darrers x dies:
Review cards due in the next:
- Mostra les fitxes noves afegides en els últims x dies:
+ Preview new cards added in the last:
Select x cards from the deck:
No queda espai al disc
El disc està gairebé ple
@@ -78,6 +78,7 @@
Sobreescriure
Repara
Reemplaça
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
El camí especificat no era un directori vàlid
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Comprovant multimèdia…
Adding tags…
- Tagging results
+ Tags added
Suprimint medis…
Resultat de la supressió
diff --git a/AnkiDroid/src/main/res/values-ca/10-preferences.xml b/AnkiDroid/src/main/res/values-ca/10-preferences.xml
index a1c0df2afa6f..8e197c080c83 100644
--- a/AnkiDroid/src/main/res/values-ca/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ca/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Deshabilitat
+ Enabled
+ Deshabilitat
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
Directori de l\'AnkiDroid
Mode de pantalla completa
- Desactivat
+ Desactivat
Amagar les barres del sistema
Amagar les barres del sistema i els botons de resposta
Posició dels botons de resposta
@@ -218,11 +219,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Trieu les etiquetes
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ckb/02-strings.xml b/AnkiDroid/src/main/res/values-ckb/02-strings.xml
index a2f2b8dd4164..33f5940d12b4 100644
--- a/AnkiDroid/src/main/res/values-ckb/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ckb/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- دەستە:
+ دەستە:
جۆر:
Nîşan: %1$s
کارتەکان: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ckb/03-dialogs.xml b/AnkiDroid/src/main/res/values-ckb/03-dialogs.xml
index e6f1feb769cd..a391163131d6 100644
--- a/AnkiDroid/src/main/res/values-ckb/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ckb/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Tamîr bike
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Rêya ku hat rawekirin rêzdarekî rast nîne
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
…Medyayê kontrol dike
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-ckb/10-preferences.xml b/AnkiDroid/src/main/res/values-ckb/10-preferences.xml
index 3df74e2d5e26..fe965ce1afde 100644
--- a/AnkiDroid/src/main/res/values-ckb/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ckb/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-cs/02-strings.xml b/AnkiDroid/src/main/res/values-cs/02-strings.xml
index 7f613f4dd1d0..5b8e11d5cfce 100644
--- a/AnkiDroid/src/main/res/values-cs/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-cs/02-strings.xml
@@ -29,7 +29,7 @@
Poznámka uložena
Balíček karet:
- Balíček:
+ Balíček:
Typ poznámky:
Štítky: %1$s
Karty: %1$s
@@ -226,6 +226,8 @@
Velikost písma
Zobrazit nástrojovou lištu
+ Show preview
+ Hide preview
Formátovat jako tučné
Formát jako kurzíva
Formát jako podtržené
diff --git a/AnkiDroid/src/main/res/values-cs/03-dialogs.xml b/AnkiDroid/src/main/res/values-cs/03-dialogs.xml
index d73fc472aff5..fbe668d1243a 100644
--- a/AnkiDroid/src/main/res/values-cs/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-cs/03-dialogs.xml
@@ -53,7 +53,7 @@
Zvýšit/snížit („-3“) dnešní limit opakování o
Opakovat zapomenuté karty z posledních x dnů:
Review cards due in the next:
- Zkoušení nových karet přidaných v posledních x dnech:
+ Preview new cards added in the last:
Vybrat x karet z balíčku:
Úložiště je plné
Úložiště téměř plné
@@ -82,6 +82,7 @@
Přepsat
Opravit
Nahradit
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Zadaná cesta není platný adresář
Zadaný text neposkytuje platný certifikát X509 v kódování PEM.
Chyba zpracování certifikátu
@@ -90,7 +91,7 @@
Toto může trvat dlouhou dobu u velké sbírky multimédií? Pokračovat?
Kontrolují se multimédia…
Přidávání štítků…
- Tagging results
+ Tags added
Odstraňují se multimédia…
Výsledek odstraňování
diff --git a/AnkiDroid/src/main/res/values-cs/10-preferences.xml b/AnkiDroid/src/main/res/values-cs/10-preferences.xml
index 729a7dfffd63..47145a42a25f 100644
--- a/AnkiDroid/src/main/res/values-cs/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-cs/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Vypnuto
+ Enabled
+ Vypnuto
- %d min
- %d min
@@ -74,7 +75,7 @@
Zobrazit názvy audio souborů v polích otázka/odpověď v prohlížeči karet
Adresář AnkiDroid
Celoobrazovkový režim
- Vypnout
+ Vypnout
Skrýt systémové lišty
Skrýt systémové lišty a tlačítka odpovědí
Pozice tlačítek odpovědí
@@ -222,11 +223,82 @@
Navigace
Multimédia
Různé
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Přidat upomínku
- Přidat upomínku opakování
- Upravit upomínku opakování
Nemáte žádné upomínky opakování
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Přidat upomínku opakování
+ Upravit upomínku opakování
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Výběr štítků
@@ -376,9 +448,15 @@
zpětnou vazbu nebo chybu.]]>
Zobrazit zpětnou vazbu odpovědi
Obrazovka
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Otevřít nastavení
Související nastavení
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-da/02-strings.xml b/AnkiDroid/src/main/res/values-da/02-strings.xml
index 053b86624b55..936a6155699c 100644
--- a/AnkiDroid/src/main/res/values-da/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-da/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Kortstak:
- Stak:
+ Stak:
Type:
Tags: %1$s
Kort: %1$s
@@ -220,6 +220,8 @@
Skriftstørrelse
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-da/03-dialogs.xml b/AnkiDroid/src/main/res/values-da/03-dialogs.xml
index 6a75e6a2fa53..f73fdabf8e17 100644
--- a/AnkiDroid/src/main/res/values-da/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-da/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Lagerplads opbrugt
Lagerplads næsten fuld
@@ -78,6 +78,7 @@
Overskriv
Reparér
Erstat
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-da/10-preferences.xml b/AnkiDroid/src/main/res/values-da/10-preferences.xml
index f4a65c9a0c03..e3828c0c7e80 100644
--- a/AnkiDroid/src/main/res/values-da/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-da/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d minutter
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-de/02-strings.xml b/AnkiDroid/src/main/res/values-de/02-strings.xml
index 16fdfa7dcae0..811b0f258d0b 100644
--- a/AnkiDroid/src/main/res/values-de/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-de/02-strings.xml
@@ -29,7 +29,7 @@
Notiz gespeichert
Kartenstapel:
- Stapel:
+ Stapel:
Typ:
Schlagwörter: %1$s
Karten: %1$s
@@ -220,6 +220,8 @@
Schriftgröße
Werkzeug zeigen
+ Show preview
+ Hide preview
Als fett formatieren
Kursiv formatieren
Als Unterstrichen formatieren
diff --git a/AnkiDroid/src/main/res/values-de/03-dialogs.xml b/AnkiDroid/src/main/res/values-de/03-dialogs.xml
index 21cf535420a8..d9709066e4cb 100644
--- a/AnkiDroid/src/main/res/values-de/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-de/03-dialogs.xml
@@ -49,7 +49,7 @@
Heutigen Tageshöchstwerts für Wiederholungskarten erhöhen („3“) oder verringern („-3“) um
Karten wiederholen, die in den letzten x Tagen vergessen wurden:
Review cards due in the next:
- Vorschau auf neue Karten, die in den letzten x Tagen hinzugefügt wurden:
+ Preview new cards added in the last:
Wählen Sie x Karten aus dem Stapel aus:
Speicher voll
Speicher fast voll
@@ -78,6 +78,7 @@
Überschreiben
Reparieren
Ersetzen
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Der angegebene Pfad ist kein gültiges Verzeichnis
Der angegebene text ergibt kein gültiges PEM-codiertes X509-zertifikat
Fehler beim parsen des Zertifikats
@@ -86,7 +87,7 @@
Das kann mit großen Mediensammlungen lange dauern. Fortfahren?
Medien werden überprüft …
Schlagworte werden hinzugefügt…
- Tagging results
+ Tags added
Medien werden gelöscht …
Löschergebnis
diff --git a/AnkiDroid/src/main/res/values-de/10-preferences.xml b/AnkiDroid/src/main/res/values-de/10-preferences.xml
index ff1cd179461c..117249b4678e 100644
--- a/AnkiDroid/src/main/res/values-de/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-de/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Deaktiviert
+ Enabled
+ Deaktiviert
- %d min
- %d Minuten
@@ -72,7 +73,7 @@
Audiodateinamen anzeigen in den Frage-/Antwortfeldern der Kartenübersicht
AnkiDroid-Verzeichnis
Vollbildmodus
- Aus
+ Aus
Systemleisten ausblenden
Systemleisten und Antwortschaltflächen ausblenden
Position der Antwortschaltflächen
@@ -218,11 +219,80 @@
Navigation
Medien
Sonstiges
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Erinnerung hinzufügen
- Bewertungserinnerung machen
- Eine Bewertungserinnerung editieren
Sie haben keine Bewertungserinnerungen
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Bewertungserinnerung machen
+ Eine Bewertungserinnerung editieren
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Schlagwörter auswählen
@@ -372,9 +442,15 @@
Feedback oder Fehler.]]>
Antwort-Feedback anzeigen
Bildschirm
- Nur Antwort
- Umdrehen und Antworten
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Einstellungen öffnen
Verwandte Einstellungen
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-el/02-strings.xml b/AnkiDroid/src/main/res/values-el/02-strings.xml
index 02c858b8a580..6553ae9f7e50 100644
--- a/AnkiDroid/src/main/res/values-el/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-el/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Τράπουλα κάρτας:
- Τράπουλα:
+ Τράπουλα:
Τύπος:
Ετικέτες:%1$s
Κάρτες %1$s
@@ -220,6 +220,8 @@
Μέγεθος γραμματοσειράς
Εμφάνιση γραμμής εργαλείων
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-el/03-dialogs.xml b/AnkiDroid/src/main/res/values-el/03-dialogs.xml
index 2477711ce780..c3e1fc4b23a2 100644
--- a/AnkiDroid/src/main/res/values-el/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-el/03-dialogs.xml
@@ -49,7 +49,7 @@
Αύξηση / μείωση (“-3”) σημερινού ορίου επαναλήψεων κατά
Επανάληψη ξεχασμένων καρτών στις τελευταίες x μέρες:
Review cards due in the next:
- Προεπισκόπηση νέων καρτών του προστέθηκαν στις τελευταίες x μέρες:
+ Preview new cards added in the last:
Επιλέξτε x κάρτες από την τράπουλα:
Γεμάτος χώρος αποθήκευσης
Χώρος αποθήκευσης σχεδόν πλήρης
@@ -78,6 +78,7 @@
Αντικατάσταση
Επισκευή
Αντικατάσταση
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Η καθορισμένη διαδρομή δεν ήταν ένας έγκυρος κατάλογος
Το κείμενο που εισέρθηκε δεν αντιπροσωπεί σε έγκυρο πιστοποιητικό Χ509 του κωδικού PEM.
Σφάλμα ανάλυσης πιστοποιητικού
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Έλεγχος πολυμέσων…
Προσθήκη ετικετών…
- Tagging results
+ Tags added
Διαγραφή πολυμέσων…
Αποτέλεσμα διαγραφής
diff --git a/AnkiDroid/src/main/res/values-el/10-preferences.xml b/AnkiDroid/src/main/res/values-el/10-preferences.xml
index dadd645555a7..dafb01db0920 100644
--- a/AnkiDroid/src/main/res/values-el/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-el/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Ανενεργό
+ Enabled
+ Ανενεργό
- %d λεπτό
- %d λεπτά
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
Κατάλογος AnkiDroid
Λειτουργία πλήρους οθόνης
- Ανενεργό
+ Ανενεργό
Απόκρυψη γραμμών συστήματος
Hide the system bars and answer buttons
Θέση κουμπιών απάντησης
@@ -219,11 +220,80 @@
Πλοήγηση
Πολυμέσα
Διάφορα
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Επιλογή Ετικετών
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Άνοιγμα ρυθμίσεων
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-eo/02-strings.xml b/AnkiDroid/src/main/res/values-eo/02-strings.xml
index c9e8a4a8d20c..e869fb40e474 100644
--- a/AnkiDroid/src/main/res/values-eo/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-eo/02-strings.xml
@@ -29,7 +29,7 @@
Noto konservita
Kartaro:
- Kartaro:
+ Kartaro:
Tipo:
Etikedoj: %1$s
Kartoj: %1$s
@@ -220,6 +220,8 @@
Grando de tiparo
Montri ilarstrion
+ Show preview
+ Hide preview
Aranĝi kiel grasan
Aranĝi kiel kursivan
Aranĝi kiel substrekitan
diff --git a/AnkiDroid/src/main/res/values-eo/03-dialogs.xml b/AnkiDroid/src/main/res/values-eo/03-dialogs.xml
index d2b632bf8f3e..3b2a825c30f8 100644
--- a/AnkiDroid/src/main/res/values-eo/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-eo/03-dialogs.xml
@@ -49,7 +49,7 @@
(Mal)pliigi (“-3”) hodiaŭan limigon de kartoj por ripeti je
Ripeti kartojn forgesitajn dum tiom da antaŭaj tagoj:
Review cards due in the next:
- Antaŭrigardi novajn kartojn aldonitajn dum tiom da antaŭaj tagoj:
+ Preview new cards added in the last:
Nombro da kartoj por elekti el la kartaro:
Konservejo plena
Konservejo preskaŭ plena
@@ -78,6 +78,7 @@
Anstataŭigi
Ripari
Anstataŭigi
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
La difinita dosier-indiko estas malĝusta
La eneigita teksto ne estas ĝusta atestilo X509 en la PEM-formo
Eraro dum pritrakti atestilon
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Kontrolado de aŭdvidaĵoj…
Aldonado de etikedoj…
- Tagging results
+ Tags added
Forigado de aŭdvidaĵoj…
Rezulto de forigo
diff --git a/AnkiDroid/src/main/res/values-eo/10-preferences.xml b/AnkiDroid/src/main/res/values-eo/10-preferences.xml
index 200705c1249a..2f5bce5dd633 100644
--- a/AnkiDroid/src/main/res/values-eo/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-eo/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Malaktiva
+ Enabled
+ Malaktiva
- %d min
- %d min
@@ -72,7 +73,7 @@
Vidigi dosiernomojn de sonoj en la kampoj “demando”/“respondo” de la kart‑foliumilo
Dosierujo de AnkiDroid
Plenekrana reĝimo
- Malaktiva
+ Malaktiva
Kaŝi striojn de operaciumo
Kaŝi striojn de operaciumo kaj respondobutonojn
Pozicio de respondobutonoj
@@ -213,11 +214,80 @@
Navigi
Aŭdvidaĵo
Aliaj
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Aldoni memorigon
- Aldoni memorigon pri ripetado
- Redakti memorigon pri ripetado
Vi havas neniun memorigon pri ripetado
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Aldoni memorigon pri ripetado
+ Redakti memorigon pri ripetado
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Elekti etikedojn
@@ -362,9 +432,15 @@ Vi povas restarigi el sekurkopio aŭ elporti vian kolekton per la menuo de listo
rimarkojn aŭ raporti erarojn.]]>
Montri movbildon de respondo
Ekrano
- Nur respondi
- Inversigi kaj respondi
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Malfermi agordojn
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml b/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml
index 287fbbe1a030..11f86b3de489 100644
--- a/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml
@@ -29,7 +29,7 @@
Nota guardada
Mazo de tarjetas:
- Mazo:
+ Mazo:
Tipo:
Etiquetas: %1$s
Tarjetas: %1$s
@@ -220,6 +220,8 @@
Tamaño de Letra
Mostrar barra de herramientas
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-es-rAR/03-dialogs.xml b/AnkiDroid/src/main/res/values-es-rAR/03-dialogs.xml
index a8a3e5c42c0d..b7c54cc15b9e 100644
--- a/AnkiDroid/src/main/res/values-es-rAR/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-es-rAR/03-dialogs.xml
@@ -49,7 +49,7 @@
Aumentar/disminuir (“-3”) el límite actual de revisión por
Repasar tarjetas olvidadas en los últimos x días:
Review cards due in the next:
- Previsualizar nuevas tarjetas añadidas en los últimos x días:
+ Preview new cards added in the last:
Selecciona x tarjetas del mazo:
Almacenamiento lleno
Almacenamiento casi lleno
@@ -78,6 +78,7 @@
Sobreescribir
Reparar
Reemplazar
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
La ruta especificada no es un directorio válido
El texto proporcionado no corresponde a un certificado X509 con codificación PEM válido.
Error al procesar el certificado
@@ -86,7 +87,7 @@
Esto puede llevar mucho tiempo con las colecciones de grandes archivos multimedia. ¿Continuar?
Comprobando archivos multimedia…
Añadiendo etiquetas…
- Tagging results
+ Tags added
Eliminando multimedia…
Resultado de la eliminación
diff --git a/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml b/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml
index 77438f9151cd..e83617bbe8f5 100644
--- a/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Deshabilitado
+ Enabled
+ Deshabilitado
- %d minuto
- %d minutos
@@ -72,7 +73,7 @@
Mostrar nombres de archivos de audio en los campos de pregunta/respuesta del navegador de tarjetas
Directorio de AnkiDroid
Pantalla completa
- Apagado
+ Apagado
Ocultar las barras del sistema
Ocultar las barras del sistema y los botones de respuesta
Posición de los botones de respuesta
@@ -218,11 +219,80 @@
Navegación
Multimedia
Varios
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Añadir recordatorio
- Añadir recordatorio de revisión
- Editar recordatorio de revisión
No tienes recordatorios de revisión
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Añadir recordatorio de revisión
+ Editar recordatorio de revisión
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Seleccionar etiquetas
@@ -370,9 +440,15 @@ Puedes restaurar una colección desde una copia de seguridad en el menú de la l
sugerencia o fallo.]]>
Show answer feedback
Pantalla
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Abrir la configuración
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-es-rES/02-strings.xml b/AnkiDroid/src/main/res/values-es-rES/02-strings.xml
index ba6b0efda18c..627a810f8c43 100644
--- a/AnkiDroid/src/main/res/values-es-rES/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-es-rES/02-strings.xml
@@ -29,7 +29,7 @@
Nota guardada
Mazo de tarjetas:
- Mazo:
+ Mazo:
Tipo:
Etiquetas: %1$s
Tarjetas: %1$s
@@ -220,6 +220,8 @@
Tamaño de Letra
Mostrar barra de herramientas
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-es-rES/03-dialogs.xml b/AnkiDroid/src/main/res/values-es-rES/03-dialogs.xml
index 774bc7639d16..7b6c559485cd 100644
--- a/AnkiDroid/src/main/res/values-es-rES/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-es-rES/03-dialogs.xml
@@ -49,7 +49,7 @@
Aumentar/disminuir (“-3”) el límite actual de revisión por
Revisar tarjetas olvidadas en los últimos x días:
Review cards due in the next:
- Previsualizar nuevas tarjetas añadidas en los últimos x días:
+ Preview new cards added in the last:
Selecciona x tarjetas del mazo:
Almacenamiento lleno
Almacenamiento casi lleno
@@ -78,6 +78,7 @@
Sobreescribir
Reparar
Reemplazar
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
La ruta especificada no era un directorio válido
El texto proporcionado no corresponde a un certificado X509 con codificación PEM válido.
Error al procesar el certificado
@@ -86,7 +87,7 @@
Esto puede llevar mucho tiempo con las colecciones de grandes archivos multimedia. ¿Continuar?
Comprobando archivos multimedia…
Añadiendo etiquetas…
- Tagging results
+ Tags added
Eliminando multimedia…
Resultado de la eliminación
diff --git a/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml b/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml
index cfa7a99ca035..1e6bbbbae5c1 100644
--- a/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Deshabilitado
+ Enabled
+ Deshabilitado
- %d min
- %d mins
@@ -72,7 +73,7 @@
Mostrar nombres de archivos de audio en los campos de pregunta/respuesta del navegador de tarjetas
Carpeta de AnkiDroid
Pantalla completa
- Desactivado
+ Desactivado
Ocultar las barras de sistema
Ocultar las barras de sistema y los botones de respuesta
Posición de botones de respuesta
@@ -218,11 +219,80 @@
Navegación
Multimedia
Varios
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Añadir recordatorio
- Añadir recordatorio de revisión
- Editar recordatorio de revisión
No tienes recordatorios de revisión
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Añadir recordatorio de revisión
+ Editar recordatorio de revisión
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Seleccionar etiquetas
@@ -370,9 +440,15 @@ Puedes restaurar una colección desde una copia de seguridad en el menú de la l
sugerencia o fallo.]]>
Show answer feedback
Pantalla
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Abrir la configuración
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-et/02-strings.xml b/AnkiDroid/src/main/res/values-et/02-strings.xml
index e548ea83f83f..889cd3fcacee 100644
--- a/AnkiDroid/src/main/res/values-et/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-et/02-strings.xml
@@ -29,7 +29,7 @@
Märkus salvestatud
Kaardipakk:
- Kaardipakk:
+ Kaardipakk:
Tüüp:
Sildid: %1$s
Kaardid: %1$s
@@ -220,6 +220,8 @@
Teksti suurus
Näita tööriistariba
+ Show preview
+ Hide preview
Rasvane kiri
Kaldkiri
Allajoonitud
diff --git a/AnkiDroid/src/main/res/values-et/03-dialogs.xml b/AnkiDroid/src/main/res/values-et/03-dialogs.xml
index 874b5d4560e8..f0ff89501454 100644
--- a/AnkiDroid/src/main/res/values-et/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-et/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Kirjuta üle
Paranda
Asenda
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Meedia kontrollimine…
Siltide lisamine…
- Tagging results
+ Tags added
Meedia kustutamine…
Tulemuse kustutamine
diff --git a/AnkiDroid/src/main/res/values-et/10-preferences.xml b/AnkiDroid/src/main/res/values-et/10-preferences.xml
index 793369e71e6d..a99985a413e5 100644
--- a/AnkiDroid/src/main/res/values-et/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-et/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Keelatud
+ Enabled
+ Keelatud
- %d minut
- %d minutit
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroidi kaust
Täisekraani režiim
- Väljas
+ Väljas
Hide the system bars
Hide the system bars and answer buttons
Vastuse nuppude asukoht
@@ -219,11 +220,80 @@
Navigatsioon
Meedia
Varia
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Lisa meeldetuletus
- Lisa kordamise meeldetuletus
- Muuda kordamise meeldetuletust
Sul pole ühtegi kordamise meeldetuletust
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Lisa kordamise meeldetuletus
+ Muuda kordamise meeldetuletust
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Vali sildid
@@ -373,9 +443,15 @@
tagasisidet või teata vigadest.]]>
Näita vastuse tagasisidet
Ekraan
- Ainult vastus
- Pööra ja vasta
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Ava seaded
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-eu/02-strings.xml b/AnkiDroid/src/main/res/values-eu/02-strings.xml
index 9fd8d87eb46d..bc4e7ee9a0ef 100644
--- a/AnkiDroid/src/main/res/values-eu/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-eu/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Txartel-sorta:
- Sorta:
+ Sorta:
Mota:
Etiketak: %1$s
Cards: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-eu/03-dialogs.xml b/AnkiDroid/src/main/res/values-eu/03-dialogs.xml
index 27c30547b585..ebe92f5007d6 100644
--- a/AnkiDroid/src/main/res/values-eu/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-eu/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Azkenengo x egunetan ahaztu diren kartak errepasatu:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Gainidatzi
Konpondu
Ordeztu
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-eu/10-preferences.xml b/AnkiDroid/src/main/res/values-eu/10-preferences.xml
index 68358e8124a6..2b0dcd3e2348 100644
--- a/AnkiDroid/src/main/res/values-eu/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-eu/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Desgaituta
+ Enabled
+ Desgaituta
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-fa/02-strings.xml b/AnkiDroid/src/main/res/values-fa/02-strings.xml
index 0a768dfa0241..4e84ddf873e1 100644
--- a/AnkiDroid/src/main/res/values-fa/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-fa/02-strings.xml
@@ -29,7 +29,7 @@
یادداشت ذخیره شد
دسته کارت:
- دسته:
+ دسته:
نوع:
برچسب ها: %1$s
کارتها: %1$s
@@ -222,6 +222,8 @@
اندازه فونت
نمایش نوار ابزار
+ Show preview
+ Hide preview
قالبحروف بهصورت پررنگ
قالب حروف با استایل مورب
قاببندی خطکشی زیر متن
diff --git a/AnkiDroid/src/main/res/values-fa/03-dialogs.xml b/AnkiDroid/src/main/res/values-fa/03-dialogs.xml
index 4fcab2f68acf..cfa7533c3d7c 100644
--- a/AnkiDroid/src/main/res/values-fa/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-fa/03-dialogs.xml
@@ -49,7 +49,7 @@
محدودیت تعداد مرور های امروز را کم/زیاد (\"-۳\") کن به این اندازه
مرور کارت های فراموش شده در x روز گذشته:
Review cards due in the next:
- پیش نمایش کارت های جدید اضافه شده در x روز گذشته:
+ Preview new cards added in the last:
انتخاب x کارت از دسته کارت ها:
حافظه
اوکی
@@ -78,6 +78,7 @@
رونويسي کردن
اصلاح
جایگزین کردن
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
مسیر مشخص شده یک مسیر معتبر نیست
متن ارائه شده به یک گواهی X509 کدگذاری شده با PEM معتبر تبدیل نمیشود
خطا در تجزیه گواهی
@@ -86,7 +87,7 @@
با مجموعههای رسانهای حجیم، این کار ممکن است بسیار زمانبر باشد. آیا ادامه میدهید؟
در حال بررسی رسانه…
در حال اضافه کردن تگ ها…
- Tagging results
+ Tags added
حذف رسانه…
نتیجه حذف
diff --git a/AnkiDroid/src/main/res/values-fa/10-preferences.xml b/AnkiDroid/src/main/res/values-fa/10-preferences.xml
index d67407b5be79..65bd881d64ec 100644
--- a/AnkiDroid/src/main/res/values-fa/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-fa/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- غیر فعال شده
+ Enabled
+ غیر فعال شده
- %d دقیقه
- %d دقیقه
@@ -72,7 +73,7 @@
نمایش نام فایلهای صوتی در کادرهای پرسش/پاسخ مرورگر کارت
مسیر قرارگیری AnkiDroid
حالت تمامصفحه
- خاموش
+ خاموش
پنهان کردن نوار سیستم
پنهان کردن نوارهای سیستم و دکمه های پاسخ
موقعیت دکمه های پاسخ
@@ -217,11 +218,80 @@
هدایت
رسانه
سایر
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
افزودن یادآوری
- ایجاد یادآوری برای مرور
- ویرایش یادآور مرور
شما هیچ یادآوری برای مرور ندارید
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ ایجاد یادآوری برای مرور
+ ویرایش یادآور مرور
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
انتخاب برچسبها
@@ -369,9 +439,15 @@
بازخورد یا مشکل فنی را گزارش دهید.]]>
نشان دادن پاسخ برای بازخورد
صفحهنمایش
- منحصراً جواب
- برگرداندن و جواب
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
تنظیمات را باز کنید
تنظیمات مرتبط
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-fi/02-strings.xml b/AnkiDroid/src/main/res/values-fi/02-strings.xml
index aeca6d16a84e..3772ce4a602e 100644
--- a/AnkiDroid/src/main/res/values-fi/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-fi/02-strings.xml
@@ -29,7 +29,7 @@
Merkintä tallennettu
Korttipakka:
- Pakka:
+ Pakka:
Tyyppi:
Tunnisteet: %1$s
Kortit: %1$s
@@ -220,6 +220,8 @@
Fonttikoko
Näytä työkalupalkki
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-fi/03-dialogs.xml b/AnkiDroid/src/main/res/values-fi/03-dialogs.xml
index 10d405b33e9f..bc3d1a1d4b82 100644
--- a/AnkiDroid/src/main/res/values-fi/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-fi/03-dialogs.xml
@@ -49,7 +49,7 @@
Korota/alenna (\"-3\") tämän päivän kerrattavien kortien rajaa arvolla
Kortteja unohdettu edellisenä x päivänä:
Review cards due in the next:
- Esikatsele viimeisinä x päivänä lisättyjä kortteja:
+ Preview new cards added in the last:
Valitse x korttia satunnaisesti pakasta:
Tallennustila on täynnä
Tallennustila on melkein täynnä
@@ -78,6 +78,7 @@
Korvaa
Korjaa
Korvaa
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Annettu hakemistopolku ei ole pätevä
Annettu teksti ei vastaa kelvollista PEM-koodattua X509-varmentetta.
Varmenteen jäsentäminen epäonnistui
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Tarkistetaan mediaa…
Lisätään tunnisteita…
- Tagging results
+ Tags added
Poistetaan mediaa…
Poiston tulos
diff --git a/AnkiDroid/src/main/res/values-fi/10-preferences.xml b/AnkiDroid/src/main/res/values-fi/10-preferences.xml
index 515cb5a0e930..f102bb8aa88a 100644
--- a/AnkiDroid/src/main/res/values-fi/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-fi/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Ei käytössä
+ Enabled
+ Ei käytössä
- %d min
- %d min
@@ -72,7 +73,7 @@
Näytä äänten tiedostonimet korttiselaimen kysymys-/vastaus-kentissä
AnkiDroid-hakemisto
Koko näytön tila
- Pois päältä
+ Pois päältä
Piilota järjestelmäpalkit
Piilota järjestelmäpalkit ja vastauspainikkeet
Vastauspainikkeiden sijainti
@@ -217,11 +218,80 @@
Liikkuminen
Media
Sekalaiset
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Lisää muistutus
- Lisää läpikäyntimuistutus
- Muokkaa läpikäyntimuistutusta
Sinulla ei ole läpikäyntimuistutuksia
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Lisää läpikäyntimuistutus
+ Muokkaa läpikäyntimuistutusta
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Valitse tunnisteet
@@ -371,9 +441,15 @@
palautetta tai ilmoita virheistä.]]>
Näytä vastauksen palaute
Näyttö
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Avaa asetukset
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-fil/02-strings.xml b/AnkiDroid/src/main/res/values-fil/02-strings.xml
index 09d1a883d9b9..ef347bf4a06a 100644
--- a/AnkiDroid/src/main/res/values-fil/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-fil/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Uri:
Mga Tag:%1$s
Mga Card: %1$s
@@ -220,6 +220,8 @@
Laki ng font
Ipakita ang toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-fil/03-dialogs.xml b/AnkiDroid/src/main/res/values-fil/03-dialogs.xml
index 3eedfe498b75..5887e452c0a3 100644
--- a/AnkiDroid/src/main/res/values-fil/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-fil/03-dialogs.xml
@@ -49,7 +49,7 @@
Dagdagan/bawasan (\"-3\") ang hangganan ng pagrepaso ngayong araw ng
Ang mga review kard na nakalimutan sa huling x araw:
Review cards due in the next:
- I-preview ang mga bagong kjard na naidagdag sa mga huling x araw:
+ Preview new cards added in the last:
Select x cards from the deck:
Puno na ang storage
Malapit nang mapuno ang storage
@@ -78,6 +78,7 @@
I-overwrite
Isaayos
Palitan
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Ang path na tinukoy ay hindi isang wastong direktoryo
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Sinusuri ang media…
Adding tags…
- Tagging results
+ Tags added
Binubura ang media…
Resulta ng pagbura
diff --git a/AnkiDroid/src/main/res/values-fil/10-preferences.xml b/AnkiDroid/src/main/res/values-fil/10-preferences.xml
index b82755a7daf4..f94d3d31f21b 100644
--- a/AnkiDroid/src/main/res/values-fil/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-fil/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Hindi pinapagana
+ Enabled
+ Hindi pinapagana
- %d min
- %d min
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Itago ang mga bar ng system
Itago ang mga system bar at sagutin ang mga pindutan
Sagot na mga pindutan ng mga pindutan
@@ -218,11 +219,80 @@
Nabigasyon
Media
Iba pa
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Piliin ang mga tag
@@ -371,9 +441,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-fr/02-strings.xml b/AnkiDroid/src/main/res/values-fr/02-strings.xml
index 11322ceefcd8..01efc1fe3024 100644
--- a/AnkiDroid/src/main/res/values-fr/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-fr/02-strings.xml
@@ -29,7 +29,7 @@
Note sauvegardée
Paquet de cartes :
- Paquet :
+ Paquet :
Type :
Étiquettes : %1$s
Cartes : %1$s
@@ -220,6 +220,8 @@
Taille de la police
Afficher la barre d\'outils
+ Show preview
+ Hide preview
Formater en gras
Formater en italique
Souligner
diff --git a/AnkiDroid/src/main/res/values-fr/03-dialogs.xml b/AnkiDroid/src/main/res/values-fr/03-dialogs.xml
index 57d0bba31c4b..ad3cfcf44a95 100644
--- a/AnkiDroid/src/main/res/values-fr/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-fr/03-dialogs.xml
@@ -49,7 +49,7 @@
Augmentation/diminution (« -3 ») de la limite de révision d\'aujourd\'hui de
Revoir les cartes oubliées dans les x derniers jours :
Revoir les cartes dues dans le prochain :
- Afficher les nouvelles cartes ajoutées dans les x derniers jours :
+ Preview new cards added in the last:
Sélectionnez x cartes du paquet :
Stockage plein
Il n\'y a presque plus d\'espace libre
@@ -78,6 +78,7 @@
Écraser
Réparer
Remplacer
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Le chemin indiqué ne mène pas à un répertoire valide
Le texte fourni ne correspond pas à un certificat X509 encodé en PEM valide
Erreur lors de l’analyse du certificat
@@ -86,7 +87,7 @@
Cela peut prendre beaucoup de temps avec les grandes collections de médias. Continuer?
Vérification des médias…
Ajout d\'étiquettes…
- Tagging results
+ Tags added
Suppression du média…
Résultat de la suppression
diff --git a/AnkiDroid/src/main/res/values-fr/10-preferences.xml b/AnkiDroid/src/main/res/values-fr/10-preferences.xml
index 4bff6ebc03e8..43d5fa8d51bb 100644
--- a/AnkiDroid/src/main/res/values-fr/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-fr/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Désactivé
+ Enabled
+ Désactivé
- %d min
- %d mins
@@ -72,7 +73,7 @@
Afficher le nom des fichiers audio dans les champs questions/ réponses du navigateur de cartes.
Répertoire de AnkiDroid
Mode plein écran
- Inactif
+ Inactif
Masquer les barres système
Masquer les barres système et les boutons de réponse
Position des boutons de réponse
@@ -218,11 +219,80 @@
Navigation
Média
Divers
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Ajouter un rappel
- Ajouter un rappel de révision
- Modifier le rappel de révision
Vous n\'avez aucun rappel de révision
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Ajouter un rappel de révision
+ Modifier le rappel de révision
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Sélectionner les étiquettes
@@ -373,9 +443,15 @@ Elles ne sont pas destinées à la plupart des utilisateurs et restent en anglai
feedback ou bugs.]]>
Montrer la réaction de réponse
Écran
- Répondre uniquement
- Retourner et répondre
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Aller aux paramètres
Paramètres associés
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-fy/02-strings.xml b/AnkiDroid/src/main/res/values-fy/02-strings.xml
index c5c548502f24..890ff67b9ce0 100644
--- a/AnkiDroid/src/main/res/values-fy/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-fy/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Kaartenset:
- Set:
+ Set:
Type:
Skaaimerken: %1$s
Cards: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-fy/03-dialogs.xml b/AnkiDroid/src/main/res/values-fy/03-dialogs.xml
index 0add619121a9..1b0ee6d99d6f 100644
--- a/AnkiDroid/src/main/res/values-fy/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-fy/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-fy/10-preferences.xml b/AnkiDroid/src/main/res/values-fy/10-preferences.xml
index 22ad3f8f77db..8a8d9d27a880 100644
--- a/AnkiDroid/src/main/res/values-fy/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-fy/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ga/02-strings.xml b/AnkiDroid/src/main/res/values-ga/02-strings.xml
index 406971bfbbf0..29de2baca9ba 100644
--- a/AnkiDroid/src/main/res/values-ga/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ga/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Type:
Tags: %1$s
Cards: %1$s
@@ -229,6 +229,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ga/03-dialogs.xml b/AnkiDroid/src/main/res/values-ga/03-dialogs.xml
index 05b518f1d903..20cad664aced 100644
--- a/AnkiDroid/src/main/res/values-ga/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ga/03-dialogs.xml
@@ -55,7 +55,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -84,6 +84,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -92,7 +93,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-ga/10-preferences.xml b/AnkiDroid/src/main/res/values-ga/10-preferences.xml
index ffeb4b5caf2f..1b3e23bab601 100644
--- a/AnkiDroid/src/main/res/values-ga/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ga/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -75,7 +76,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -225,11 +226,83 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Select tags
@@ -382,9 +455,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-gl/01-core.xml b/AnkiDroid/src/main/res/values-gl/01-core.xml
index e94581a2ea05..623467b556df 100644
--- a/AnkiDroid/src/main/res/values-gl/01-core.xml
+++ b/AnkiDroid/src/main/res/values-gl/01-core.xml
@@ -127,8 +127,8 @@
Ler e escribir na base de datos do AnkiDroid
acceder ás notas, tarxetas, tipos de notas e barallas existentes, así como crear novas
- Card browser
- Anki card
+ Explorador de tarxetas
+ Tarxeta Anki
Non se poden combinar as coleccións.\nQue colección queres manter?
AnkiDroid
diff --git a/AnkiDroid/src/main/res/values-gl/02-strings.xml b/AnkiDroid/src/main/res/values-gl/02-strings.xml
index 68d8ffb5ed0b..5d09b14d71f7 100644
--- a/AnkiDroid/src/main/res/values-gl/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-gl/02-strings.xml
@@ -29,7 +29,7 @@
Nota gardada
Baralla de tarxetas:
- Baralla:
+ Baralla:
Tipo:
Etiquetas: %1$s
Tarxetas: %1$s
@@ -220,6 +220,8 @@
Tamaño da letra
Amosar barra de ferramentas
+ Amosar vista previa
+ Ocultar vista previa
Aplicar letra grosa
Aplicar cursiva
Aplicar subliñado
diff --git a/AnkiDroid/src/main/res/values-gl/03-dialogs.xml b/AnkiDroid/src/main/res/values-gl/03-dialogs.xml
index e0384f894e5f..a092794e11dc 100644
--- a/AnkiDroid/src/main/res/values-gl/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-gl/03-dialogs.xml
@@ -49,7 +49,7 @@
Aumentar/diminuír (\"-3\") o límite de repasos actual en
Repasar as tarxetas esquecidas nos últimos x días:
Repasar as tarxetas pendentes nos vindeiros:
- Amosar as tarxetas novas engadidas nos últimos x días:
+ Preview new cards added in the last:
Seleccionar x tarxetas da baralla:
Almacenamento cheo
Almacenamento case cheo
@@ -78,6 +78,7 @@
Substituír
Reparar
Substituír
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
A ruta especificada non é un cartafol válido
O texto proporcionado non corresponde a un certificado X509 codificado nun formato PEM válido
Erro na análise do certificado
@@ -86,7 +87,7 @@
Isto pode levar moito tempo con coleccións multimedia grandes. Queres continuar?
Verificando multimedia…
Engadindo etiquetas…
- Resultados das etiquetas
+ Tags added
Eliminando multimedia…
Resultado da eliminación
diff --git a/AnkiDroid/src/main/res/values-gl/10-preferences.xml b/AnkiDroid/src/main/res/values-gl/10-preferences.xml
index 625d43d8a8e5..f2e587a8560d 100644
--- a/AnkiDroid/src/main/res/values-gl/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-gl/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Desactivado
+ Activado
+ Desactivado
- %d min
- %d mins
@@ -72,7 +73,7 @@
Amosar os nomes dos ficheiros de son nos campos de pregunta/resposta no explorador de tarxetas
Directorio do AnkiDroid
Modo pantalla completa
- Desactivado
+ Desactivado
Ocultar as barras do sistema
Ocultar as barras do sistema e os botóns de resposta
Posición dos botóns de resposta
@@ -107,7 +108,7 @@
Toque con tres dedos
Toque con catro dedos
Axitar o dispositivo
- ‘%s’ menu
+ Menú «%s»
Activar globalmente o menú contextual ‘%s’
Desprazamento dobre
Duplicar a distancia de desprazamento co eReader
@@ -219,11 +220,80 @@
Navegación
Multimedia
Outros
-
+
+
+ Recordatorios de estudo
+ Xeral da aplicación
+ Por baralla
+ Produciuse un erro ao cargar os teus recordatorios de repaso. Elimináronse os recordatorios danados. Detalles:
+
+ Programar recordatorios
Engadir recordatorio
- Engadir recordatorio de repaso
- Editar recordatorio de repaso
Non tes recordatorios de repaso
+ Solucionar problemas
+ Axustes de notificación
+ Recordatorios de repaso programados
+ Baralla non atopada
+ Os recordatorios non están dispoñíbeis
+ Arranxar
+ Engadiuse o novo recordatorio de repaso correctamente
+ Eliminouse o recordatorio de repaso correctamente
+ Editouse o recordatorio de repaso correctamente
+
+ Engadir recordatorio de repaso
+ Editar recordatorio de repaso
+ Hora:
+ Baralla:
+ Axustes avanzados
+ Límite de tarxetas:
+ Non enviar notificacións se hai menos deste número de tarxetas pendentes
+ Notificar se aínda non se fixeron os repasos diarios
+ Insire un número entero de tarxetas
+ O límite debe ser polo menos 0
+ Eliminar este recordatorio?
+ Esta acción non se pode desfacer.
+
+ Os recordatorios non están dispoñíbeis.
+ Os recordatorios poden funcionar incorrectamente.
+ Os teus recordatorios deberían funcionar correctamente.
+ Os axustes de notificacións varían segundo o dispositivo. Comproba a configuración do sistema ou busca en liña se o problema continúa.
+
+ Permiso de notificacións
+ Canle de notificacións
+ Non molestar
+ Optimización de batería
+ Modo aforro de batería
+ Permiso de alarmas & recordatorios
+
+ Concedido
+ Denegado
+ Si
+ Non
+ Sen restricións
+ Optimizada
+ Restritiva
+
+ A canle de notificacións dos recordatorios de repaso debe estar activada
+ O modo Non molestar pode silenciar as notificacións dos recordatorios
+ A optimización da batería pode atrasar os recordatorios
+ O uso en segundo plano está desactivado. É posible que os recordatorios non se envíen
+ O modo de aforro de batería pode impedir que os recordatorios cheguen a tempo
+ Necesario para programar recordatorios a horas exactas
+
+ Conceder permisos
+ Activar a canlel de notificacións
+ Desactivar optimización da batería
+ Abrir axustes de batería
+
+ É momento de estudar as túas tarxetas
+ É momento de estudar «%s»
+ Adiar %s
+
+ - %d tarxeta pendente
+ - %d tarxetas pendentes
+
Seleccionar etiquetas
@@ -372,9 +442,15 @@
suxestión ou erro]]>
Amosar explicación da resposta
Pantalla
- Só responder
- Voltear e responder
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Abrir configuración
Configuracións relacionadas
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-got/02-strings.xml b/AnkiDroid/src/main/res/values-got/02-strings.xml
index cbd5c6eb5d8a..a971340f88ea 100644
--- a/AnkiDroid/src/main/res/values-got/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-got/02-strings.xml
@@ -29,7 +29,7 @@
𐌼𐌴𐌻𐌴𐌹𐌽𐍃 𐌲𐌰𐍆𐌰𐍃𐍄𐌰𐌽𐌰
𐌺𐌰𐍂𐍄𐌰𐌷𐌿𐌶𐌳:
- 𐌲𐌰𐌻𐌹𐍃𐌰 𐌺𐌰𐍂𐍄𐍉
+ 𐌲𐌰𐌻𐌹𐍃𐌰 𐌺𐌰𐍂𐍄𐍉
𐌺𐌹𐌽𐌸𐍃:
𐍃𐍉𐌺𐌽𐌹𐍅𐌰𐌿𐍂𐌳𐌰: %1$s
𐌺𐌰𐍂𐍄𐍉𐍃: %1$s
@@ -220,6 +220,8 @@
𐌼𐌹𐌺𐌹𐌻𐌴𐌹 𐍆𐌰𐌿𐌽𐍄𐌹𐍃
𐍄𐍉𐌻𐌰-𐌰𐌽𐍃 𐌱𐌰𐌽𐌳𐍅𐌾𐌰𐌽
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-got/03-dialogs.xml b/AnkiDroid/src/main/res/values-got/03-dialogs.xml
index 1cfeec5c6afe..ad4ec4f0a7c2 100644
--- a/AnkiDroid/src/main/res/values-got/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-got/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- 𐌽𐌹𐌿𐌾𐍉𐍃 𐌺𐌰𐍂𐍄𐍉𐍃 𐍆𐌰𐌿𐍂𐌰𐍃𐌰𐌹𐍈𐌰𐌽 𐌹𐌽 𐌰𐍆𐍄𐌿𐌼𐌹𐍃𐍄𐌰𐌹𐌼 𐍇 𐌳𐌰𐌲𐌰𐌼:
+ Preview new cards added in the last:
𐍅𐌰𐌻𐌴𐌹 x 𐌺𐌰𐍂𐍄𐍉𐍃 𐌺𐌰𐍂𐍄𐌰𐌷𐌿𐌶𐌳𐌹𐍃:
𐍂𐌿𐌼 𐌹𐍃𐍄 𐍆𐌿𐌻𐌻
𐌻𐌹𐍃𐌰 𐌽𐌴𐍈𐌰 𐍆𐌿𐌻𐌻𐌰
@@ -78,6 +78,7 @@
𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌾𐌰𐌽
𐌲𐌰𐌱𐍉𐍄𐌾𐌰𐌽
𐌼𐌹𐌸𐍃𐌰𐍄𐌾𐌰𐌽
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
𐌻𐌰𐌹𐌳𐌰 𐍃𐌴𐌹 𐌲𐌰𐍂𐌰𐌹𐌷𐍄𐌹𐌳𐌰 𐌽𐌹 𐍅𐌰𐍃 𐌳𐌹𐍂𐌰𐌹𐌺𐍄𐌰𐌿𐍂𐌹 𐍂𐌰𐌹𐌷𐍄
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
𐌿𐍃𐍃𐍉𐌺𐌾𐌰𐌽𐌳𐌰 𐌼𐌰𐌹𐌳𐌾𐌰…
𐌱𐌹𐌰𐌿𐌺𐌰𐌽𐌳𐌰 𐍃𐍉𐌺𐌽𐌹𐍅𐌰𐌿𐍂𐌳𐌰…
- Tagging results
+ Tags added
𐌿𐍃𐌵𐌹𐍃𐍄𐌾𐌰𐌽𐌳𐌰 𐌼𐌰𐌹𐌳𐌾𐌰𐌼…
𐍄𐌰𐌿𐌹 𐌿𐍃𐌵𐌹𐍃𐍄𐌰𐌹𐍃
diff --git a/AnkiDroid/src/main/res/values-got/10-preferences.xml b/AnkiDroid/src/main/res/values-got/10-preferences.xml
index 48962545c894..d7eabfde978e 100644
--- a/AnkiDroid/src/main/res/values-got/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-got/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- 𐌿𐌽𐌲𐌰𐍄𐌰𐍅𐌹𐌸
+ Enabled
+ 𐌿𐌽𐌲𐌰𐍄𐌰𐍅𐌹𐌸
- %d 𐌼𐌹𐌽
- %d 𐌼𐌹𐌽𐍃
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
𐌰𐌽𐌺𐌴𐌹𐌳𐍂𐌰𐌿𐌳 𐌳𐌹𐍂𐌰𐌹𐌺𐍄𐌰𐌿𐍂𐌹
𐍆𐌿𐌻𐌻𐌰𐍃𐌺𐌰𐌹𐍂𐌼𐌹𐍃 𐌼𐍉𐌳𐌿𐍃
- 𐌿𐍄
+ 𐌿𐍄
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
𐌽𐌰𐌱𐌹𐌲𐌰𐍄𐍃𐌾𐍉
𐌼𐌰𐌹𐌳𐌾𐌰
𐌰𐌽𐌸𐌰𐍂
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
𐌲𐌰𐌼𐌿𐌽𐌳 𐌱𐌹𐌰𐌿𐌺𐌰𐌽
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
𐍃𐍉𐌺𐌽𐌹𐍅𐌰𐌿𐍂𐌳𐌰 𐍅𐌰𐌻𐌾𐌰𐌽
@@ -374,9 +444,15 @@
feedback or bugs.]]>
𐌰𐌽𐌳𐌰𐌷𐌰𐍆𐍄𐌰𐌹𐍃 𐌷𐌹𐌻𐍀𐌰 𐌱𐌰𐌽𐌳𐍅𐌾𐌰𐌽
𐍃𐌺𐌰𐌹𐍂𐌼𐍃
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
𐌻𐌰𐌲𐌴𐌹𐌽𐌹𐌽𐍃 𐌿𐍃𐌻𐌿𐌺𐌰𐌽
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-gu/02-strings.xml b/AnkiDroid/src/main/res/values-gu/02-strings.xml
index 82e91dec792d..c9fa7e32a29f 100644
--- a/AnkiDroid/src/main/res/values-gu/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-gu/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
કાર્ડ ડેક:
- તૂતક:
+ તૂતક:
પ્રકાર:
ટૅગ્સ: %1$s
કાર્ડ્સ: %1$s
@@ -220,6 +220,8 @@
અક્ષર ની જાડાઈ
ટૂલબાર બતાવો
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-gu/03-dialogs.xml b/AnkiDroid/src/main/res/values-gu/03-dialogs.xml
index ebea83e92caf..01a5720cc294 100644
--- a/AnkiDroid/src/main/res/values-gu/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-gu/03-dialogs.xml
@@ -49,7 +49,7 @@
આજની સમીક્ષા મર્યાદામાં વધારો/ઘટાડો (“-3”).
છેલ્લા x દિવસમાં ભૂલી ગયેલા કાર્ડની સમીક્ષા કરો:
Review cards due in the next:
- છેલ્લા x દિવસમાં ઉમેરાયેલા નવા કાર્ડ્સનું પૂર્વાવલોકન કરો:
+ Preview new cards added in the last:
Select x cards from the deck:
સ્ટોરેજ ભરાઈ ગયું
સ્ટોરેજ લગભગ ભરાઈ ગયું છે
@@ -78,6 +78,7 @@
ઓવરરાઇટ કરો
સમારકામ
બદલો
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
ઉલ્લેખિત પાથ માન્ય ડિરેક્ટરી ન હતી
પ્રદાન કરેલ ટેક્સ્ટ માન્ય PEM-એનકોડેડ X509 પ્રમાણપત્રને ઉકેલતું નથી
પ્રમાણપત્રનું પદચ્છેદન કરવામાં ભૂલ
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
મીડિયા તપાસી રહ્યું છે…
Adding tags…
- Tagging results
+ Tags added
મીડિયા ડિલીટ કરી રહ્યાં છીએ…
કાઢી નાખવાનું પરિણામ
diff --git a/AnkiDroid/src/main/res/values-gu/10-preferences.xml b/AnkiDroid/src/main/res/values-gu/10-preferences.xml
index 6d936eedf4ac..200a0fe5574a 100644
--- a/AnkiDroid/src/main/res/values-gu/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-gu/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- અક્ષમ
+ Enabled
+ અક્ષમ
- %d મિનિટ
- %d મિનિટ
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid ડિરેક્ટરી
પૂર્ણસ્ક્રીન મોડ
- બંધ
+ બંધ
સિસ્ટમ બાર છુપાવો
સિસ્ટમ બાર અને જવાબ બટનો છુપાવો
Answer buttons position
@@ -218,11 +219,80 @@
સંશોધક
મીડિયા
વિવિધ
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
ટૅગ્સ પસંદ કરો
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-heb/02-strings.xml b/AnkiDroid/src/main/res/values-heb/02-strings.xml
index 7f9a9ff2df01..eb9130b791b0 100644
--- a/AnkiDroid/src/main/res/values-heb/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-heb/02-strings.xml
@@ -29,7 +29,7 @@
הרשומה נשמרה
חפיסת כרטיסים:
- חפיסה:
+ חפיסה:
סוג:
תגיות: %1$s
כרטיסים: %1$s
@@ -223,6 +223,8 @@
גודל גופן
הצגת סרגל הכלים
+ Show preview
+ Hide preview
עצב כמודגש
עצב כנטוי
עצב עם קו תחתון
diff --git a/AnkiDroid/src/main/res/values-heb/03-dialogs.xml b/AnkiDroid/src/main/res/values-heb/03-dialogs.xml
index 13de7ac93cc6..688ee38d9593 100644
--- a/AnkiDroid/src/main/res/values-heb/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-heb/03-dialogs.xml
@@ -52,7 +52,7 @@
הוספה/הורדה(“-3”) הגבלת חזרות להיום ב
חזרה על כרטיסים נשכחים בx ימים האחרונים
Review cards due in the next:
- צפייה ראשונית בכרטיסים חדשים שנוספו בx ימים האחרונים:
+ Preview new cards added in the last:
בחר x קלפים מהחפיסה:
האיחסון מלא
שטח האחסון כמעט מלא
@@ -81,6 +81,7 @@
שכתוב
תיקון
החלפה
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
הנתיב שצוין אינו תיקייה תקנית
הטקסט שסופק אינו מתאים לאישור X509 תקין מקודד ב-PEM
שגיאה בניתוח האישור
@@ -89,7 +90,7 @@
This may take a long time with large media collections. Proceed?
קבצי המדיה בבדיקה…
הוספת תגיות…
- Tagging results
+ Tags added
המדיה נמחקת…
תוצאת מחיקה
diff --git a/AnkiDroid/src/main/res/values-heb/10-preferences.xml b/AnkiDroid/src/main/res/values-heb/10-preferences.xml
index 67664fd18432..d6dae079d1e8 100644
--- a/AnkiDroid/src/main/res/values-heb/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-heb/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- מבוטל
+ Enabled
+ מבוטל
- דקה אחת
- %d דקות
@@ -73,7 +74,7 @@
הצגת שמות קבצי שמע בשדות השאלות/תשובות של דפדפן הכרטיסים
ספריית AnkiDroid
מצב מסך מלא
- כבוי
+ כבוי
הסתרת שורות המערכת
הסתרת שורות המערכת וכפתורי התשובה
מיקום כפתור המענה
@@ -219,11 +220,81 @@
ניווט
מדיה
אחר
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
הוספת תזכורת
- הוסף תזכורת לסקירה
- תזכורת עריכת סקירה
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ הוסף תזכורת לסקירה
+ תזכורת עריכת סקירה
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+
בחר תגיות
@@ -374,9 +445,15 @@
משוב או באג.]]>
הצג משוב על התשובה
מסך
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
פתח הגדרות
הגדרות קשורים
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-hi/02-strings.xml b/AnkiDroid/src/main/res/values-hi/02-strings.xml
index 03e5140832d0..a974dbadff0f 100644
--- a/AnkiDroid/src/main/res/values-hi/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-hi/02-strings.xml
@@ -29,7 +29,7 @@
टिप्पणी सुरक्षित
पत्ता डेक:
- डेक:
+ डेक:
प्रकार:
टैग: %1$s
पत्ते: %1$s
@@ -220,6 +220,8 @@
फ़ॉन्ट आकार
टूलबार दिखाएं
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-hi/03-dialogs.xml b/AnkiDroid/src/main/res/values-hi/03-dialogs.xml
index 42dcbdb17ddd..e4d7d39a3752 100644
--- a/AnkiDroid/src/main/res/values-hi/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-hi/03-dialogs.xml
@@ -49,7 +49,7 @@
आज की समीक्षा सीमा बढ़ाएँ / घटाएँ (\"-3\")
पिछले x दिनों में भूल गए कार्ड की समीक्षा करें:
Review cards due in the next:
- पिछले x दिनों में जोड़े गए नए कार्ड का पूर्वावलोकन करें:
+ Preview new cards added in the last:
Select x cards from the deck:
स्टोरेज भर गया
स्टोरेज लगभग पूर्ण
@@ -78,6 +78,7 @@
उपरिलेखित करें
सुधारें
की जगह
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
निर्दिष्ट पथ कोई मांय निर्देशिका नहीं है
प्रदान किया गया पाठ वैध PEM-एन्कोडेड X509 प्रमाणपत्र का समाधान नहीं करता है
प्रमाणपत्र पार्स करने में त्रुटि
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
मीडिया जांच कर रहा है.. ।
Adding tags…
- Tagging results
+ Tags added
मीडिया हटा रहा है…
विलोपन का परिणाम
diff --git a/AnkiDroid/src/main/res/values-hi/10-preferences.xml b/AnkiDroid/src/main/res/values-hi/10-preferences.xml
index 09e6d4d88b2a..56728e551b10 100644
--- a/AnkiDroid/src/main/res/values-hi/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-hi/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- अक्षम
+ Enabled
+ अक्षम
- %d मिनट
- %d मिनट
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid निर्देशिका
फुल स्क्रीन मोड
- ऑफ
+ ऑफ
सिस्टम पट्टियां छुपाएं
सिस्टम पट्टियां और उत्तर बटन छुपाएं
उत्तर बटन स्थिति
@@ -214,11 +215,80 @@
नेविगेशन
मिडिया
विविध
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
टैग का चयन करें
@@ -362,9 +432,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-hr/02-strings.xml b/AnkiDroid/src/main/res/values-hr/02-strings.xml
index b907f1c56d27..5386f5e9dd6d 100644
--- a/AnkiDroid/src/main/res/values-hr/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-hr/02-strings.xml
@@ -29,7 +29,7 @@
Bilješka spremljena
Špil karata:
- Špil:
+ Špil:
Tip:
Oznake: %1$s
Karte: %1$s
@@ -223,6 +223,8 @@
Veličina fonta
Pokaži alatnu traku
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-hr/03-dialogs.xml b/AnkiDroid/src/main/res/values-hr/03-dialogs.xml
index 9e500371926c..6aeb066edf66 100644
--- a/AnkiDroid/src/main/res/values-hr/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-hr/03-dialogs.xml
@@ -51,7 +51,7 @@
Povećaj/umanji (“-3”) današnji limit kartica ponavljanja za
Kartice ponavljanja zaboravljene u zadnjih x dana:
Review cards due in the next:
- Pregledaj nove kartice dodane u zadnjih x dana:
+ Preview new cards added in the last:
Odaberi x kartica iz špila:
Pohrana je puna
Pohrana je skoro puna
@@ -80,6 +80,7 @@
Overwrite
Popravi
Zamijeni
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Zadani put nije bio valjana mapa
Navedeni tekst se na razrješava u valjani PEM-kodirani X509 certifikat
Greška u parsiranju certifikata
@@ -88,7 +89,7 @@
This may take a long time with large media collections. Proceed?
Provjera medija…
Dodavanje oznaka…
- Tagging results
+ Tags added
Brisanje medija…
Rezultat brisanja
diff --git a/AnkiDroid/src/main/res/values-hr/10-preferences.xml b/AnkiDroid/src/main/res/values-hr/10-preferences.xml
index 3eccf418ebda..f50e8cc955a6 100644
--- a/AnkiDroid/src/main/res/values-hr/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-hr/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Onemogućeno
+ Enabled
+ Onemogućeno
- %d min
- %d min
@@ -73,7 +74,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid direktorij
Prikaz na cijelom zaslonu
- Ugašeno
+ Ugašeno
Sakrij trake sustava
Sakrij trake sustava i gumbe za odgovore
Pozicije gumba za odgovore
@@ -220,11 +221,81 @@
Navigacija
Mediji
Razno
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Dodaj podsjetnik
- Dodaj podsjetnik za ponavljanje
- Uredi podsjetnik za ponavljanje
Nemate podsjetnika za ponavljanje
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Dodaj podsjetnik za ponavljanje
+ Uredi podsjetnik za ponavljanje
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+
Odaberi oznake
@@ -375,9 +446,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-hu/02-strings.xml b/AnkiDroid/src/main/res/values-hu/02-strings.xml
index 7f1168b0afa6..774f21451d64 100644
--- a/AnkiDroid/src/main/res/values-hu/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-hu/02-strings.xml
@@ -29,7 +29,7 @@
Jegyzet elmentve
Kártyapakli:
- Pakli:
+ Pakli:
Típus:
Címkék: %1$s
Címkék: %1$s
@@ -220,6 +220,8 @@
Betűméret
Eszköztár megjelenítése
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-hu/03-dialogs.xml b/AnkiDroid/src/main/res/values-hu/03-dialogs.xml
index 31ef273d9cfe..e8be4d4e90c7 100644
--- a/AnkiDroid/src/main/res/values-hu/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-hu/03-dialogs.xml
@@ -49,7 +49,7 @@
Növeld/csökkentsd (\"-3\") a mai ismétlődő kártyák korlátját
Az elmúlt x napban elfelejtett kártyák ismétlése:
Review cards due in the next:
- Az elmúlt x nap új kártyáinak előre átnézése:
+ Preview new cards added in the last:
Select x cards from the deck:
Tárhely megtelt
Tárhely majdnem megtelt
@@ -78,6 +78,7 @@
Felülír
Javít
Kicserél
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
A megadott elérési út nem érvényes mappa
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Média ellenőrzése…
Adding tags…
- Tagging results
+ Tags added
Média törlése…
Törlés eredménye
diff --git a/AnkiDroid/src/main/res/values-hu/10-preferences.xml b/AnkiDroid/src/main/res/values-hu/10-preferences.xml
index 4794a8f3dd8b..eecf9875eae1 100644
--- a/AnkiDroid/src/main/res/values-hu/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-hu/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Kikapcsolva
+ Enabled
+ Kikapcsolva
- %d perc
- %d perc
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid könyvtár
Teljes képernyős mód
- Kikapcsol
+ Kikapcsol
Rendszer sávok elrejtése
Rendszer sávok és válasz gombok elrejtése
A válasz gombok elhelyezkedése
@@ -217,11 +218,80 @@
Navigáció
Média
Egyéb
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Válassz címkét
@@ -371,9 +441,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Beállítások megnyitása
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-hy/02-strings.xml b/AnkiDroid/src/main/res/values-hy/02-strings.xml
index 5870717c1a12..98bda81b1161 100644
--- a/AnkiDroid/src/main/res/values-hy/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-hy/02-strings.xml
@@ -29,7 +29,7 @@
Պահպանված չէ
Քարտերի կապուկ՝
- Կապուկ՝
+ Կապուկ՝
Տեսակ՝
Պիտակներ՝ %1$s
Քարտեր՝ %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-hy/03-dialogs.xml b/AnkiDroid/src/main/res/values-hy/03-dialogs.xml
index edcd4d9965e6..dcea7f7fed65 100644
--- a/AnkiDroid/src/main/res/values-hy/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-hy/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Վերանայել վերջին X օրում մոռացված քարտերը`
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Վերագրանցել
Վերականգնել
Փոխարինել
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Նշված ուղին վավեր թղթապանակ չէ
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-hy/10-preferences.xml b/AnkiDroid/src/main/res/values-hy/10-preferences.xml
index 3d40685b0f1b..9f3b545373f7 100644
--- a/AnkiDroid/src/main/res/values-hy/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-hy/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Անջատված
+ Enabled
+ Անջատված
- %d րոպե
- %d րոպե
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid թղթապանակ
Ամբողջական էկրանի ռեժիմ
- Անջատված
+ Անջատված
Hide the system bars
Hide the system bars and answer buttons
Պատասխանի կոճակների տեղադրություն
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ind/02-strings.xml b/AnkiDroid/src/main/res/values-ind/02-strings.xml
index fb0f4795c0f8..9b807cbe8f90 100644
--- a/AnkiDroid/src/main/res/values-ind/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ind/02-strings.xml
@@ -29,7 +29,7 @@
Catatan Disimpan
Dek kartu:
- Dek:
+ Dek:
Tipe:
Tag: %1$s
Kartu: %1$s
@@ -217,6 +217,8 @@
Ukuran font
Tampilkan toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ind/03-dialogs.xml b/AnkiDroid/src/main/res/values-ind/03-dialogs.xml
index 117f6994f719..8e97c1770884 100644
--- a/AnkiDroid/src/main/res/values-ind/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ind/03-dialogs.xml
@@ -47,7 +47,7 @@
Tambahkan/kurangi (“-3”) batas kartu ulasan hari ini sampai
Ulas kartu yang terlupakan dalam x hari terakhir:
Review cards due in the next:
- Pratinjau kartu yang baru ditambahkan dalam x hari terakhir:
+ Preview new cards added in the last:
Pilih x kartu dari dek:
Penyimpanan sudah penuh
Penyimpanan hampir penuh
@@ -76,6 +76,7 @@
Timpa
Perbaiki
Ganti
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Jalur yang dipilih bukan direktori yang valid
Teks yang diberikan tidak menghasilkan sertifikat PEM-encoded X509 yang valid
Error penguraian sertifikat
@@ -84,7 +85,7 @@
This may take a long time with large media collections. Proceed?
Memeriksa media…
Menambah tag…
- Tagging results
+ Tags added
Menghapus media…
Hasil penghapusan
diff --git a/AnkiDroid/src/main/res/values-ind/10-preferences.xml b/AnkiDroid/src/main/res/values-ind/10-preferences.xml
index fb004e2dc205..5686dfd2553a 100644
--- a/AnkiDroid/src/main/res/values-ind/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ind/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Nonaktif
+ Enabled
+ Nonaktif
- %d menit
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
Direktori AnkiDroid
Mode layar penuh
- Mati
+ Mati
Sembunyikan bar sistem
Sembunyikan bar sistem dan tombol jawab
Posisi tombol jawab
@@ -216,11 +217,79 @@
Navigasi
Media
Lainnya
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
Pilih tag
@@ -368,9 +437,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-it/02-strings.xml b/AnkiDroid/src/main/res/values-it/02-strings.xml
index 23cc5bfd9a02..273546ac4ee7 100644
--- a/AnkiDroid/src/main/res/values-it/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-it/02-strings.xml
@@ -29,7 +29,7 @@
Nota salvata
Mazzo:
- Mazzo:
+ Mazzo:
Tipo:
Etichette: %1$s
Carte: %1$s
@@ -220,6 +220,8 @@
Dimensione Carattere
Mostra barra strumenti
+ Show preview
+ Hide preview
Formatta in grassetto
Formata in corsivo
Formatta come sottolineato
diff --git a/AnkiDroid/src/main/res/values-it/03-dialogs.xml b/AnkiDroid/src/main/res/values-it/03-dialogs.xml
index 43052aa2f6c1..cc2b3c4d5cb5 100644
--- a/AnkiDroid/src/main/res/values-it/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-it/03-dialogs.xml
@@ -49,7 +49,7 @@
Aumentare/diminuire (“-3”) il limite odierno di revisioni di
Ripeti carte dimenticate negli ultimi x giorni:
Review cards due in the next:
- Anteprima carte nuove aggiunte negli ultimi x giorni:
+ Preview new cards added in the last:
Seleziona x carte casualmente dal mazzo:
Spazio di archiviazione pieno
Spazio di archiviazione quasi pieno
@@ -78,6 +78,7 @@
Sovrascrivi
Ripara
Sostituisci
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Il percorso specificato non era una cartella valida
Il testo fornito non si risolve con un certificato X509 codificato in PEM valido
Errore durante l\'analisi del certificato
@@ -86,7 +87,7 @@
Potrebbe richiedere molto tempo per collezioni multimediali di grandi dimensioni. Procedere?
Controllo i media…
Aggiungi etichetta
- Tagging results
+ Tags added
Eliminando media…
Eliminazione risultato
diff --git a/AnkiDroid/src/main/res/values-it/10-preferences.xml b/AnkiDroid/src/main/res/values-it/10-preferences.xml
index b48d44193f56..efb83a4f9902 100644
--- a/AnkiDroid/src/main/res/values-it/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-it/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabilitato
+ Enabled
+ Disabilitato
- %d min
- %d min
@@ -72,7 +73,7 @@
Mostra i nomi dei file multimediali nei campi domanda/risposta del browser delle carte
Cartella AnkiDroid
Modalità a schermo intero
- Disabilitato
+ Disabilitato
Nascondi le barre di sistema
Nascondi le barre di sistema e i pulsanti di risposta
Posizione dei pulsanti di risposta
@@ -218,11 +219,80 @@
Navigazione
Media
Miscellanea
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Aggiungi promemoria
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Seleziona etichette
@@ -372,9 +442,15 @@
feedback o bugs.]]>
Mostra feedback risposta
Screen
- Solo risposta
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Apri impostazioni
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-iw/02-strings.xml b/AnkiDroid/src/main/res/values-iw/02-strings.xml
index 7f9a9ff2df01..eb9130b791b0 100644
--- a/AnkiDroid/src/main/res/values-iw/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-iw/02-strings.xml
@@ -29,7 +29,7 @@
הרשומה נשמרה
חפיסת כרטיסים:
- חפיסה:
+ חפיסה:
סוג:
תגיות: %1$s
כרטיסים: %1$s
@@ -223,6 +223,8 @@
גודל גופן
הצגת סרגל הכלים
+ Show preview
+ Hide preview
עצב כמודגש
עצב כנטוי
עצב עם קו תחתון
diff --git a/AnkiDroid/src/main/res/values-iw/03-dialogs.xml b/AnkiDroid/src/main/res/values-iw/03-dialogs.xml
index 13de7ac93cc6..688ee38d9593 100644
--- a/AnkiDroid/src/main/res/values-iw/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-iw/03-dialogs.xml
@@ -52,7 +52,7 @@
הוספה/הורדה(“-3”) הגבלת חזרות להיום ב
חזרה על כרטיסים נשכחים בx ימים האחרונים
Review cards due in the next:
- צפייה ראשונית בכרטיסים חדשים שנוספו בx ימים האחרונים:
+ Preview new cards added in the last:
בחר x קלפים מהחפיסה:
האיחסון מלא
שטח האחסון כמעט מלא
@@ -81,6 +81,7 @@
שכתוב
תיקון
החלפה
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
הנתיב שצוין אינו תיקייה תקנית
הטקסט שסופק אינו מתאים לאישור X509 תקין מקודד ב-PEM
שגיאה בניתוח האישור
@@ -89,7 +90,7 @@
This may take a long time with large media collections. Proceed?
קבצי המדיה בבדיקה…
הוספת תגיות…
- Tagging results
+ Tags added
המדיה נמחקת…
תוצאת מחיקה
diff --git a/AnkiDroid/src/main/res/values-iw/10-preferences.xml b/AnkiDroid/src/main/res/values-iw/10-preferences.xml
index 67664fd18432..d6dae079d1e8 100644
--- a/AnkiDroid/src/main/res/values-iw/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-iw/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- מבוטל
+ Enabled
+ מבוטל
- דקה אחת
- %d דקות
@@ -73,7 +74,7 @@
הצגת שמות קבצי שמע בשדות השאלות/תשובות של דפדפן הכרטיסים
ספריית AnkiDroid
מצב מסך מלא
- כבוי
+ כבוי
הסתרת שורות המערכת
הסתרת שורות המערכת וכפתורי התשובה
מיקום כפתור המענה
@@ -219,11 +220,81 @@
ניווט
מדיה
אחר
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
הוספת תזכורת
- הוסף תזכורת לסקירה
- תזכורת עריכת סקירה
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ הוסף תזכורת לסקירה
+ תזכורת עריכת סקירה
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+
בחר תגיות
@@ -374,9 +445,15 @@
משוב או באג.]]>
הצג משוב על התשובה
מסך
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
פתח הגדרות
הגדרות קשורים
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ja/01-core.xml b/AnkiDroid/src/main/res/values-ja/01-core.xml
index 04962518b3ab..5513eb7a7693 100644
--- a/AnkiDroid/src/main/res/values-ja/01-core.xml
+++ b/AnkiDroid/src/main/res/values-ja/01-core.xml
@@ -122,8 +122,8 @@
AnkiDroidデータベースの読み取りと書き込み
ノート、カード、ノートタイプ、デッキへのアクセスや、それらの新規作成を行います。
- Card browser
- Anki card
+ AnkiDroid検索
+ Ankiカード
コレクションを統合できません。\nどちらのコレクションを保持しますか?
AnkiDroid
diff --git a/AnkiDroid/src/main/res/values-ja/02-strings.xml b/AnkiDroid/src/main/res/values-ja/02-strings.xml
index 02790e213ad7..303ce999cf75 100644
--- a/AnkiDroid/src/main/res/values-ja/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ja/02-strings.xml
@@ -29,7 +29,7 @@
ノートを保存しました
カードデッキ:
- デッキ:
+ デッキ:
ノートタイプ:
タグ: %1$s
カード: %1$s
@@ -169,7 +169,7 @@
既存のタグ「%1$s」を選択しました
この名前のタグがすでに存在します
この名前のデッキがすでに存在します
- Consider backing up your collection from DeckPicker before using this option!
+ この機能を使用する前にバックアップを作成しておくことを強くおすすめします(メイン画面右上の「⋮」→「バックアップを作成」)。
Ankiファイル(*.apkg)の共有中にエラーが発生しました
@@ -211,12 +211,14 @@
カードコンテンツエラー: ’%s’ の読み込みに失敗しました
デッキ名を検索
- Fatal error
+ 致命的なエラー
AnkiDroidはAndroidシステムのWebViewに依存していますが、このWebViewが現在使用できません。この状況は、システムがアップデートプログラムをインストールしている場合に発生する可能性があります。数分後に再試行してください。\n\n%s
Androidのエラーのため、AnkiDroidはストレージにアクセスできません。\n\n%s
フォントサイズ
ツールバーを表示
+ Show preview
+ Hide preview
太字
斜体
下線
diff --git a/AnkiDroid/src/main/res/values-ja/03-dialogs.xml b/AnkiDroid/src/main/res/values-ja/03-dialogs.xml
index b8e69dd2dce8..b6c6c3e856c4 100644
--- a/AnkiDroid/src/main/res/values-ja/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ja/03-dialogs.xml
@@ -47,7 +47,7 @@
上限枚数をいくつ増やしますか?(減らす場合は負の数)
直近 x 日間で「やり直し」を選んだカードを再学習
Review cards due in the next:
- 直近 x 日間で追加した習得未開始の新規カードを予習
+ Preview new cards added in the last:
デッキから抽出する枚数:
ストレージの空き容量がありません
ストレージの空き容量が少なくなっています
@@ -76,6 +76,7 @@
上書きする
修復
上書き
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
指定されたパスは有効なディレクトリではありませんでした
提供されたテキストをPEMエンコードしても有効な X.509証明書になりません
証明書の解析エラー
@@ -84,7 +85,7 @@
メディア(画像・音声・動画)ファイルが多いと、チェックに長い時間がかかる場合があります。続行しますか?
メディアをチェックしています…
タグを追加しています…
- Tagging results
+ Tags added
メディアを削除中…
削除結果
diff --git a/AnkiDroid/src/main/res/values-ja/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-ja/07-cardbrowser.xml
index 2f2072c001dc..32777f4f277d 100644
--- a/AnkiDroid/src/main/res/values-ja/07-cardbrowser.xml
+++ b/AnkiDroid/src/main/res/values-ja/07-cardbrowser.xml
@@ -89,13 +89,13 @@
ソート(並び替え)しない
並べ替え処理をしない分、表示されるまでの時間が短くなります
- Sort by %1$s
- A–Z
- Z–A
- Low to high
- High to low
- Oldest first
- Newest first
+ ソート: %1$s
+ 昇順
+ 降順
+ 低い順
+ 高い順
+ 古い順
+ 新しい順
A
diff --git a/AnkiDroid/src/main/res/values-ja/10-preferences.xml b/AnkiDroid/src/main/res/values-ja/10-preferences.xml
index 7028584cf355..a97ab9518218 100644
--- a/AnkiDroid/src/main/res/values-ja/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ja/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- 無効
+ Enabled
+ 無効
- %d分
@@ -48,13 +49,13 @@
編集
セキュリティ
- Globally allow dangerous JavaScript APIs
- Allows any card or template to read and modify other cards, or your Anki collection data.\nLeave off unless you trust all cards and templates in your collection.
+ JavaScript API を包括的に許可
+ すべてのカードやテンプレートにおいて、他のカードやコレクションデータの読み取りや変更を可能にします。\n他者が作成した悪意や不具合のあるカードなどによる被害を防ぐため、そのような機能を与えるほどには信用していないカードやテンプレートがコレクション内にある場合は、このオプションはオンにしないでください。
Security: Unsafe JavaScript call blocked.\nIf you trust all cards and templates in your collection, this can be unblocked in the advanced settings
- Allow cards to launch other apps
+ カード内で別のアプリの起動を許可
Cards can open other apps on your device, triggering potentially unwanted actions.\nLeave off unless you trust all cards and templates in your collection.
- Security: Card blocked from launching another app.\nIf you trust all cards and templates in your collection, this can be unblocked in the advanced settings
+ 安全を確保するため、別のアプリの起動をブロックしました。\nコレクション内のすべてのカードとテンプレートを信頼している場合、ブロックを解除することができます(「設定」→「高度な設定」→「カード内で別のアプリの起動を許可」)
線の太さ
答えを入力する欄をカード内に表示
@@ -71,7 +72,7 @@
カードブラウザのリスト画面の質問欄と解答欄に、メディア(画像・音声・動画)ファイル名も表示します
AnkiDroidディレクトリ
フルスクリーンモード
- オフ
+ オフ
ステータスバー(通知バー)を隠す
ステータスバー(通知バー)と解答表示ボタン・回答ボタンを隠す
解答表示ボタン・回答ボタンの位置
@@ -216,11 +217,79 @@
画面移動
音声・動画
その他
-
+
+
+ 学習リマインダー
+ コレクション全体
+ 特定のデッキ
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
リマインダーを追加
- 学習リマインダーを追加
- 学習リマインダーを編集
学習のリマインダーがありません
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ 学習リマインダーを追加
+ 学習リマインダーを編集
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ 学習する時間です
+ %s を学習する時間です
+ %s スヌーズ
+
+ - %d枚の課題
+
タグを選択
@@ -261,8 +330,8 @@
ユーザーアクション 8
ユーザーアクション 9
カードの自動送りをオン / オフ
- Enable auto advance
- Disable auto advance
+ カードの自動送りをオン
+ カードの自動送りをオフ
発音チェック機能をオン
発音チェック機能をオフ
@@ -369,9 +438,15 @@
ご意見・ご感想をお寄せください。不具合がある場合もお知らせいただければ幸いです。]]>
回答選択時にフィードバック(目印)を表示
画面枠
- 回答のみ
- 表面では解答を表示
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
設定画面を開く
関連設定
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ka/02-strings.xml b/AnkiDroid/src/main/res/values-ka/02-strings.xml
index 65e2b8f2e3b3..93d4595cf4dd 100644
--- a/AnkiDroid/src/main/res/values-ka/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ka/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
ბარათის დასტა:
- დასტა:
+ დასტა:
ტიპი:
იარლიყები: %1$s
ბარათები: %1$s
@@ -220,6 +220,8 @@
ტექსტის ზომა
ინსტრუმენტების პანელის ჩვენება
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ka/03-dialogs.xml b/AnkiDroid/src/main/res/values-ka/03-dialogs.xml
index fa3a3d7d881a..08a89a5f4294 100644
--- a/AnkiDroid/src/main/res/values-ka/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ka/03-dialogs.xml
@@ -49,7 +49,7 @@
დღევანდელი გადახედვების ლიმიტის გაზრდა/შემცირება (\"-3\") ამდენით:
გასულ x დღეში დავიწყებული ბარათების გადახედვა:
Review cards due in the next:
- ბოლო x დღეში დამატებული ბარათების წინასწარ ნახვა:
+ Preview new cards added in the last:
Select x cards from the deck:
მეხსიერება სავსეა
მეხსიერება თითქმის გავსებულია
@@ -78,6 +78,7 @@
ჩანაცვლება
შეკეთება
ჩანაცვლება
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
შეცდომა სერტიფიკატის პარსინგისას
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
მედიაფაილების შემოწმება…
იარლიყების დამატება…
- Tagging results
+ Tags added
მიმდინარეობს მედიაფაილების წაშლა…
წაშლის შედეგი
diff --git a/AnkiDroid/src/main/res/values-ka/10-preferences.xml b/AnkiDroid/src/main/res/values-ka/10-preferences.xml
index 79aa176bdcfa..8fc83b991684 100644
--- a/AnkiDroid/src/main/res/values-ka/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ka/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- გამორთულია
+ Enabled
+ გამორთულია
- %d წთ
- %d წთ
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid-ის საქაღალდე
სრულეკრანიანი რეჟიმი
- გამორთულია
+ გამორთულია
სისტემის ზოლების დამალვა
სისტემის ზოლების და პასუხის ღილაკების დამალვა
პასუხის ღილაკის ადგილმდებარეობა
@@ -218,11 +219,80 @@
ნავიგაცია
მედიაფაილები
სხვადასხვა
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
იარლიყების მონიშვნა
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
პარამეტრების გახსნა
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-kk/02-strings.xml b/AnkiDroid/src/main/res/values-kk/02-strings.xml
index 49d3076932e4..7cd1529bac88 100644
--- a/AnkiDroid/src/main/res/values-kk/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-kk/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Type:
Tags: %1$s
Cards: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-kk/03-dialogs.xml b/AnkiDroid/src/main/res/values-kk/03-dialogs.xml
index 9c2b92940dd5..a9e280184cec 100644
--- a/AnkiDroid/src/main/res/values-kk/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-kk/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-kk/10-preferences.xml b/AnkiDroid/src/main/res/values-kk/10-preferences.xml
index 5fba3e8c30c5..e4052f7f9b26 100644
--- a/AnkiDroid/src/main/res/values-kk/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-kk/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-km/02-strings.xml b/AnkiDroid/src/main/res/values-km/02-strings.xml
index 55f8c484809d..4b89245b4767 100644
--- a/AnkiDroid/src/main/res/values-km/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-km/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
ជាន់របស់កាត
- ជាន់
+ ជាន់
ប្រភេទ
Tags: %1$s
Cards: %1$s
@@ -217,6 +217,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-km/03-dialogs.xml b/AnkiDroid/src/main/res/values-km/03-dialogs.xml
index c7f0ca5922c0..c3b49cad2704 100644
--- a/AnkiDroid/src/main/res/values-km/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-km/03-dialogs.xml
@@ -47,7 +47,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -76,6 +76,7 @@
Overwrite
ជួសជុល
ជំនួស
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -84,7 +85,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-km/10-preferences.xml b/AnkiDroid/src/main/res/values-km/10-preferences.xml
index 4e7c67606cce..20070b189ec5 100644
--- a/AnkiDroid/src/main/res/values-km/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-km/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- បានបិទ
+ Enabled
+ បានបិទ
- %d mins
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- បិទ
+ បិទ
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -217,11 +218,79 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
ជ្រើសរើសស្លាក
@@ -370,9 +439,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
បើកការកំណត់
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-kn/02-strings.xml b/AnkiDroid/src/main/res/values-kn/02-strings.xml
index 475e4b336c78..21ffb6088742 100644
--- a/AnkiDroid/src/main/res/values-kn/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-kn/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
ಕಾರ್ಡ್ ಡೆಕ್:
- ಡೆಕ್:
+ ಡೆಕ್:
ಮಾದರಿ:
ಟ್ಯಾಗ್ಗಳು:%1$s
ಕಾರ್ಡ್ಗಳು: %1$s
@@ -220,6 +220,8 @@
ಫಾಂಟ್ ಗಾತ್ರ
ಉಪಕರಣಪಟ್ಟಿ ಪ್ರದರ್ಶಿಸಿ
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-kn/03-dialogs.xml b/AnkiDroid/src/main/res/values-kn/03-dialogs.xml
index 9b8dd412ca11..cf1574a31842 100644
--- a/AnkiDroid/src/main/res/values-kn/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-kn/03-dialogs.xml
@@ -49,7 +49,7 @@
ಇಂದಿನ ಚೆಕ್ ಮಿತಿಯನ್ನು ಹೆಚ್ಚಿಸಿ/ಕಡಿಮೆ ಮಾಡಿ (\"-3\").
ಕಳೆದ x ದಿನಗಳಲ್ಲಿ ಮರೆತಿರುವ ರಿವ್ಯೂ ಕಾರ್ಡ್ಗಳು:
Review cards due in the next:
- ಕಳೆದ x ದಿನಗಳಲ್ಲಿ ಸೇರಿಸಲಾದ ಹೊಸ ಕಾರ್ಡ್ಗಳ ಪೂರ್ವವೀಕ್ಷಣೆ:
+ Preview new cards added in the last:
Select x cards from the deck:
ಸಂಗ್ರಹಣೆ ತುಂಬಿದೆ
ಸಂಗ್ರಹಣೆಯು ಬಹುತೇಕ ಪೂರ್ಣವಾಗಿದೆ
@@ -78,6 +78,7 @@
ತಿದ್ದಿ ಬರೆಯಿರಿ
ದುರಸ್ತಿ
ಬದಲಾವಣೆ
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
ನಿರ್ದಿಷ್ಟಪಡಿಸಿದ ಮಾರ್ಗವು ಮಾನ್ಯವಾದ ಡೈರೆಕ್ಟರಿಯಾಗಿರಲಿಲ್ಲ
ಒದಗಿಸಿದ ಪಠ್ಯವು ಮಾನ್ಯವಾದ PEM-ಎನ್ಕೋಡ್ ಮಾಡಿದ X509 ಪ್ರಮಾಣಪತ್ರವನ್ನು ಪರಿಹರಿಸುವುದಿಲ್ಲ
ಪ್ರಮಾಣಪತ್ರವನ್ನು ಪಾರ್ಸಿಂಗ್ ಮಾಡುವಲ್ಲಿ ದೋಷ
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
ಮಾಧ್ಯಮವನ್ನು ಪರಿಶೀಲಿಸಲಾಗುತ್ತಿದೆ…
Adding tags…
- Tagging results
+ Tags added
ಮಾಧ್ಯಮವನ್ನು ಅಳಿಸಲಾಗುತ್ತಿದೆ…
ಅಳಿಸುವಿಕೆ ಫಲಿತಾಂಶ
diff --git a/AnkiDroid/src/main/res/values-kn/10-preferences.xml b/AnkiDroid/src/main/res/values-kn/10-preferences.xml
index 32ffd50ee1e9..249f6af98a7b 100644
--- a/AnkiDroid/src/main/res/values-kn/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-kn/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- ಅಂಗವಿಕಲ
+ Enabled
+ ಅಂಗವಿಕಲ
- %d ನಿಮಿಷ
- %d ನಿಮಿಷಗಳು
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
ಅಂಕಿಡ್ರಾಯ್ಡ್ ಡೈರೆಕ್ಟರಿ
ಪೂರ್ಣಪರದೆ ಮೋಡ್
- ಆಫ್
+ ಆಫ್
ಸಿಸ್ಟಮ್ ಬಾರ್ಗಳನ್ನು ಮರೆಮಾಡಿ
ಸಿಸ್ಟಮ್ ಬಾರ್ಗಳು ಮತ್ತು ಉತ್ತರ ಬಟನ್ಗಳನ್ನು ಮರೆಮಾಡಿ
ಉತ್ತರ ಬಟನ್ಗಳ ಸ್ಥಾನ
@@ -216,11 +217,80 @@
ನ್ಯಾವಿಗೇಷನ್
ಮಾಧ್ಯಮ
ಇತರರು
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
ಟ್ಯಾಗ್ಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ
@@ -371,9 +441,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ko/01-core.xml b/AnkiDroid/src/main/res/values-ko/01-core.xml
index fb42f2076d4d..6aa4ab49f35c 100644
--- a/AnkiDroid/src/main/res/values-ko/01-core.xml
+++ b/AnkiDroid/src/main/res/values-ko/01-core.xml
@@ -122,8 +122,8 @@
AnkiDroid 데이터베이스 읽고 쓰기
기존의 노트, 카드, 노트 유형, 덱에 접근하여 새로운 항목 생성하기
- Card browser
- Anki card
+ Anki에서 찾기
+ Anki 카드 추가
컬렉션을 합칠 수 없습니다.\n어떤 컬렉션을 유지하시겠습니까?
AnkiDroid
diff --git a/AnkiDroid/src/main/res/values-ko/02-strings.xml b/AnkiDroid/src/main/res/values-ko/02-strings.xml
index 95d53a5a5b7f..3afa228db020 100644
--- a/AnkiDroid/src/main/res/values-ko/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ko/02-strings.xml
@@ -29,7 +29,7 @@
노트가 저장되었습니다.
카드 덱:
- 덱 :
+ 덱 :
유형:
태그: %1$s
카드: %1$s
@@ -110,9 +110,9 @@
수평으로 뒤집기
수직으로 뒤집기
- 시간 상자에 도달했습니다
+ 목표 학습 시간에 도달했습니다.
- - %1$d 카드가 %2$s에 학습됨
+ - %2$s분 동안 카드 %1$d개를 복습했습니다
@@ -218,6 +218,8 @@
글꼴 크기
도구 모음 표시
+ Show preview
+ Hide preview
굵게
기울임꼴
밑줄
@@ -367,5 +369,5 @@
Switch profile
프로필 편집
- Delete profile
+ 프로필 삭제
diff --git a/AnkiDroid/src/main/res/values-ko/03-dialogs.xml b/AnkiDroid/src/main/res/values-ko/03-dialogs.xml
index 3ac47a4a9dae..6725a8eec274 100644
--- a/AnkiDroid/src/main/res/values-ko/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ko/03-dialogs.xml
@@ -47,7 +47,7 @@
오늘의 복습 제한을 (“-3”) 만큼 증가/감소시키기
최근 x일 동안 잊혀진 복습 카드:
Review cards due in the next:
- 최근 x일 내에 추가된 카드 미리 학습하기:
+ Preview new cards added in the last:
카드 묶음으로부터 선택된 카드 수:
저장 공간이 가득 찼습니다.
저장공간이 거의 가득 찼습니다.
@@ -76,6 +76,7 @@
덮어쓰기
복구
대체
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
지정된 경로가 올바르지 않습니다.
포함된 텍스트가 올바른 PEM 인코딩된 X509 인증서를 가지고 있지 않습니다
인증서 인식 오류
@@ -84,7 +85,7 @@
미디어가 클 경우 시간이 오래 걸릴 수 있습니다. 계속할까요?
미디어 점검 중…
태그 추가 중…
- Tagging results
+ Tags added
미디어 삭제 중…
삭제 결과
@@ -134,7 +135,7 @@
Too many reminders scheduled. Some will not appear
언어 설정
- Some multilingual keyboards, such as GBoard support changing language when editing text\n\nPlease request the “setImeHintLocales” feature from your keyboard manufacturer if your keyboard does not change language.
+ 일부 다국어 키보드(예: Gboard)는 텍스트를 편집할 때 언어를 자동으로 변경할 수 있습니다.\n\n사용 중인 키보드에서 언어가 변경되지 않는다면, 키보드 제조사에 \"setImeHintLocales\" 기능 지원을 요청해 주세요.
AnkiDroid 사용법
AnkiDroid 매뉴얼
diff --git a/AnkiDroid/src/main/res/values-ko/10-preferences.xml b/AnkiDroid/src/main/res/values-ko/10-preferences.xml
index e23b9b132b70..8295db083ae6 100644
--- a/AnkiDroid/src/main/res/values-ko/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ko/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- 비활성화됨
+ Enabled
+ 비활성화됨
- %d분
@@ -34,7 +35,7 @@
일반
학습
- 시스템 전역 설정
+ 시스템 전역
복습
동기화
스케줄링
@@ -71,14 +72,14 @@
카드 탐색기의 질문/답변 필드에 미디어 파일 이름을 표시합니다.
AnkiDroid 디렉토리
전체 화면 모드
- 끔
+ 끔
시스템 바 숨기기
시스템 바와 답변 버튼 숨기기
답변 버튼 위치
- 상단
- 하단
- - 없음
+ - 숨기기
제스처 사용하기
응답 및 카드 편집 등의 작업에 제스쳐 할당
@@ -106,7 +107,7 @@
세 손가락으로 탭
네 손가락으로 탭
기기 흔들기
- ‘%s’ menu
+ \'%s\' 메뉴
시스템 전역에서 글자를 길게 눌렀을 때 \'%s\' 컨텍스트 메뉴를 보입니다.
이중 스크롤
eReader 사용시 스크롤 간격 두배로 늘리기
@@ -146,7 +147,7 @@
Allows \'Cloze deletion\' context menu when in landscape mode.
가운데 정렬
- 카드의 내용을 세로로 중앙에 배치
+ 카드 내용 새로 중앙 정렬
더블 탭 인식 간격
이 간격보다 짧은 간격의 더블 탭은 무시됩니다. (실수 방지)
정답 보기 버튼 홀드 시간
@@ -165,7 +166,7 @@
남은 시간 표시
새 카드의 카드 묶음
미리 학습하기 시간 설정
- 타임박스 시간 설정
+ 목표 학습 시간(도달 시 알림)
다음 날의 시작
- %d시
@@ -198,7 +199,7 @@
안드로이드에서 미디어 파일을 인식하지 못할 경우
자체 동기화 서버
- 사용되지 않음
+ 사용 안 함
탐색
미디어
기타
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
리마인더 추가
- 복습 리마인더 추가
- 복습 리마인더 편집
복습 리마인더가 없습니다
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ 복습 리마인더 추가
+ 복습 리마인더 편집
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
태그 선택하기
@@ -227,10 +296,10 @@
검색
AnkiDroid를 개선하는데 도움을 주세요!
- 기능 사용 공유하기
+ 사용 정보 공유
개발팀이 사람들이 어떤 기능을 사용하는지 볼 수 있도록 도와주어 AnkiDroid에 기여할 수 있습니다.
줄바꿈을 HTML로 변환
- In the note editor, convert any instances of <br> to newlines when editing a card.
+ 노트 편집기에서 카드를 편집할 때 <br>을 줄바꿈으로 변환합니다.
\'정답 입력\' 포커스
\'정답 입력\' 필드에 자동으로 포커스합니다.
@@ -278,8 +347,8 @@
실험적
AnkiDroid에 더 향상된 TTS(음성 합성) 기능이 도입되었습니다! 이제 다른 Anki 클라이언트와도 호환되며, 더 다양한 음성과 언어 재생 품질이 개선되었습니다.\n\n이 설정은 곧 제거될 예정이니, 가능한 빨리 앱을 업그레이드해 주세요.
새로운 TTS 포맷을 위해 업데이트 해주세요.
- Allow templates to record audio
- This template is trying to record audio
+ 카드 템플릿이 오디오를 녹음하도록 허용
+ 이 템플릿은 오디오를 녹음합니다
학습 화면 로컬 스토리지
학습 화면에서 고정된 서버 포트를 사용하여 자바스크립트 로컬 스토리지를 활성화합니다.
@@ -315,7 +384,7 @@
사용되지 않는 미디어 파일 삭제하기
- Delete images and other media that are not prefixed with _ and are not referenced by any notes
+ 어떤 노트에도 사용되지 않거나, _로 시작하지 않는 이미지나 미디어를 삭제합니다.
사용되지 않는 미디어 파일 삭제하기
백업 삭제하기
@@ -367,9 +436,15 @@
피드백 혹은 버그를 제보해주세요.]]>
정답 피드백 보이기
화면
- 답만
- 뒤집기와 답
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
설정 열기
관련 설정
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ku/02-strings.xml b/AnkiDroid/src/main/res/values-ku/02-strings.xml
index 1d3992800dbb..12c2a8eabfe9 100644
--- a/AnkiDroid/src/main/res/values-ku/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ku/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Cure:
Nîşan: %1$s
Kart: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ku/03-dialogs.xml b/AnkiDroid/src/main/res/values-ku/03-dialogs.xml
index e6f1feb769cd..a391163131d6 100644
--- a/AnkiDroid/src/main/res/values-ku/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ku/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Tamîr bike
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Rêya ku hat rawekirin rêzdarekî rast nîne
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
…Medyayê kontrol dike
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-ku/10-preferences.xml b/AnkiDroid/src/main/res/values-ku/10-preferences.xml
index 3df74e2d5e26..fe965ce1afde 100644
--- a/AnkiDroid/src/main/res/values-ku/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ku/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ky/02-strings.xml b/AnkiDroid/src/main/res/values-ky/02-strings.xml
index 49d3076932e4..7cd1529bac88 100644
--- a/AnkiDroid/src/main/res/values-ky/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ky/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Type:
Tags: %1$s
Cards: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ky/03-dialogs.xml b/AnkiDroid/src/main/res/values-ky/03-dialogs.xml
index df1b0be99047..e37c55819c38 100644
--- a/AnkiDroid/src/main/res/values-ky/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ky/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-ky/10-preferences.xml b/AnkiDroid/src/main/res/values-ky/10-preferences.xml
index 5fba3e8c30c5..e4052f7f9b26 100644
--- a/AnkiDroid/src/main/res/values-ky/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ky/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-lt/02-strings.xml b/AnkiDroid/src/main/res/values-lt/02-strings.xml
index 6da114303785..aed5c19b0ea6 100644
--- a/AnkiDroid/src/main/res/values-lt/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-lt/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Kortelių rinkinys:
- Rinkinys:
+ Rinkinys:
Tipas:
Žymės: %1$s
Kortelės: %1$s
@@ -226,6 +226,8 @@
Šrifto dydis
Rodyti įrankių juostą
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-lt/03-dialogs.xml b/AnkiDroid/src/main/res/values-lt/03-dialogs.xml
index 8cde49722dbe..1912659f1954 100644
--- a/AnkiDroid/src/main/res/values-lt/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-lt/03-dialogs.xml
@@ -53,7 +53,7 @@
Padidinti / sumažinti (\"-3\") šiandienos peržiūros ribą
Peržiūrėti korteles, kurios buvo pamirštos per paskutines x dienų:
Review cards due in the next:
- Peržiūrėti naujas korteles, pridėtas per paskutines x dienų:
+ Preview new cards added in the last:
Iš kortelių rinkinio pasirinkite x kortų:
Saugykla pilna
Saugykla beveik užpildyta
@@ -82,6 +82,7 @@
Perrašyti
Taisyti
Pakeisti
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Nurodytas adresas negalioja
Pateiktas tekstas neatitinka galiojančio PEM koduoto X509 sertifikato
Klaida analizuojant sertifikatą
@@ -90,7 +91,7 @@
This may take a long time with large media collections. Proceed?
Tikrinamos laikmenos…
Žymių pridėjimas…
- Tagging results
+ Tags added
Medijos pašalinimas…
Ištrynimo rezultatas
diff --git a/AnkiDroid/src/main/res/values-lt/10-preferences.xml b/AnkiDroid/src/main/res/values-lt/10-preferences.xml
index 8b5aa9a20c6f..0cd44fdac642 100644
--- a/AnkiDroid/src/main/res/values-lt/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-lt/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Išjungta
+ Enabled
+ Išjungta
- %d min
- %d mins
@@ -74,7 +75,7 @@
Display audio filenames in the card browser question/answer fields
„AnkiDroid“ katalogas
Viso ekrano režimas
- Išjungta
+ Išjungta
Slėpti sistemines juostas
Slėpti sistemos juostas ir atsakymų mygtukus
Atsakymų mygtukų vieta
@@ -223,11 +224,82 @@
Navigacija
Medija
Įvairūs
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Pasirinkite žymes
@@ -377,9 +449,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Atidaryti nustatymus
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-lv/02-strings.xml b/AnkiDroid/src/main/res/values-lv/02-strings.xml
index 48399d23797b..02408933b3e0 100644
--- a/AnkiDroid/src/main/res/values-lv/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-lv/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Kartītes kava:
- Kava:
+ Kava:
Veids:
Birkas: %1$s
Kartītes: %1$s
@@ -223,6 +223,8 @@
Burtu lielums
Rādīt rīkjoslu
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-lv/03-dialogs.xml b/AnkiDroid/src/main/res/values-lv/03-dialogs.xml
index 237f4967211b..a2bea7b83aff 100644
--- a/AnkiDroid/src/main/res/values-lv/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-lv/03-dialogs.xml
@@ -51,7 +51,7 @@
Palielināt/samazināt (\"-3\") šodienas izskatīšanas ierobežojumu par
Parskatīt kartītes, kas ir aizmirstas pēdējās x dienās:
Review cards due in the next:
- Priekškatīt jaunās kartītes, kas pievienotas pēdējās x dienās:
+ Preview new cards added in the last:
Atlasīt x kavas kartītes:
Krātuve pilna
Krātuve gandrīz pilna
@@ -80,6 +80,7 @@
Aizvietot
Labot
Aizstāt
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Norādītais ceļš nenorāda uz derīgu mapi
Norādītais tekstst nesatur derīgu ar PEM šifrētu X509 sertifikātu
Kļūda sertifikāta apstrādāšanā
@@ -88,7 +89,7 @@
Tas var aizņemt ilgu laiku, ja ir lieli informācijas nesēju krājumi. Turpināt?
Pārbauda informācijas nesējus…
Pievieno birkas…
- Tagging results
+ Tags added
Izdzēš informācijas nesējus…
Izdzēšanas iznākums
diff --git a/AnkiDroid/src/main/res/values-lv/10-preferences.xml b/AnkiDroid/src/main/res/values-lv/10-preferences.xml
index 04bfb183953f..1e86558d900f 100644
--- a/AnkiDroid/src/main/res/values-lv/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-lv/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Atspējots/a
+ Enabled
+ Atspējots/a
- %d minūšu
- %d minūte
@@ -73,7 +74,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid mape
Pilnekrāna skats
- Izslēgts
+ Izslēgts
Paslēpt sistēmas joslas
Paslēpt sistēmas joslas un atbilžu pogas
Atbilžu pogu novietojums
@@ -220,11 +221,81 @@
Pārvietošanās
Informācijas nesēji
Dažādi
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+ - %d card due
+ - %d cards due
+
Atlasīt birkas
@@ -374,9 +445,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Atvērt iestatījumus
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-mk/02-strings.xml b/AnkiDroid/src/main/res/values-mk/02-strings.xml
index f9178617b20c..1a4017fd3ea6 100644
--- a/AnkiDroid/src/main/res/values-mk/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-mk/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Картички коло:
- Коло:
+ Коло:
Тип:
Тагови: %1$s
Картички: %1$s
@@ -221,6 +221,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-mk/03-dialogs.xml b/AnkiDroid/src/main/res/values-mk/03-dialogs.xml
index ec61e51d23e1..491e7637edb3 100644
--- a/AnkiDroid/src/main/res/values-mk/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-mk/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Преглед на картички заборавени во последните x денови:
Review cards due in the next:
- Преглед на нови картички додадени во последните x дена:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Пребриши
Поправка
Замени
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Наведената патека не е валиден директориум
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Проверка на медиумит…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-mk/10-preferences.xml b/AnkiDroid/src/main/res/values-mk/10-preferences.xml
index c5be5cd26dbb..68bb71fbafd3 100644
--- a/AnkiDroid/src/main/res/values-mk/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-mk/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Оневозможи
+ Enabled
+ Оневозможи
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Режим на цел екран
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Место на копчиња за одговарање
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ml/02-strings.xml b/AnkiDroid/src/main/res/values-ml/02-strings.xml
index abf035bd8e17..e002ffe7a571 100644
--- a/AnkiDroid/src/main/res/values-ml/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ml/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
കാർഡ് ഡെക്ക്:
- ഡെക്ക്:
+ ഡെക്ക്:
തരം:
ടാഗുകൾ:%1$s
കാർഡുകൾ:%1$s
@@ -220,6 +220,8 @@
അക്ഷര വലിപ്പം
ടൂൾബാർ കാണിക്കുക
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ml/03-dialogs.xml b/AnkiDroid/src/main/res/values-ml/03-dialogs.xml
index 6312b4517fb4..4eec6da4bb91 100644
--- a/AnkiDroid/src/main/res/values-ml/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ml/03-dialogs.xml
@@ -49,7 +49,7 @@
ഇന്നത്തെ അവലോകന പരിധി വർദ്ധിപ്പിക്കുക / കുറയ്ക്കുക (“-3”)
കഴിഞ്ഞ x ദിവസങ്ങളിൽ മറന്ന അവലോകന കാർഡുകൾ:
Review cards due in the next:
- അവസാന x ദിവസങ്ങളിൽ ചേർത്ത പുതിയ കാർഡുകൾ പ്രിവ്യൂ ചെയ്യുക:
+ Preview new cards added in the last:
Select x cards from the deck:
സംഭരണം നിറഞ്ഞു
സംഭരണം ഏതാണ്ട് നിറഞ്ഞു
@@ -78,6 +78,7 @@
തിരുത്തിയെഴുതുക
റിപ്പെയർ ചെയ്യുക
മാറ്റി മറ്റൊന്നുവയ്ക്കുക
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
വ്യക്തമാക്കിയ പാത്ത് സാധുവായ ഒരു ഡയറക്ടറിയായിരുന്നില്ല
The provided text does not resolve to a valid PEM-encoded X509 certificate
സർട്ടിഫിക്കറ്റ് പാഴ്സ് ചെയ്യുന്നതിൽ പിശക്
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
മീഡിയ പരിശോധിക്കുന്നു…
Adding tags…
- Tagging results
+ Tags added
മീഡിയ ഇല്ലാതാക്കുന്നു…
ഇല്ലാതാക്കൽ ഫലം
diff --git a/AnkiDroid/src/main/res/values-ml/10-preferences.xml b/AnkiDroid/src/main/res/values-ml/10-preferences.xml
index 05b7dc05beda..352ab300d7fa 100644
--- a/AnkiDroid/src/main/res/values-ml/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ml/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- പ്രവര്ത്തന രഹിതമാക്കിയത്
+ Enabled
+ പ്രവര്ത്തന രഹിതമാക്കിയത്
- %d മിനിറ്റ്
- %d മിനിറ്റ്
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid ഡയറക്ടറി
പൂർണ്ണസ്ക്രീൻ മോഡ്
- ഓഫ്
+ ഓഫ്
സിസ്റ്റം ബാറുകൾ മറയ്ക്കുക
സിസ്റ്റം ബാറുകളും ഉത്തര ബട്ടണുകളും മറയ്ക്കുക
ഉത്തരം ബട്ടണുകളുടെ സ്ഥാനം
@@ -218,11 +219,80 @@
നാവിഗേഷൻ
മാധ്യമങ്ങൾ
മറ്റുള്ളവ
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
ടാഗ് തിരഞ്ഞെടുക്കുക
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
ക്രമീകരണങ്ങൾ തുറക്കുക
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-mn/02-strings.xml b/AnkiDroid/src/main/res/values-mn/02-strings.xml
index 2d0b51543dd6..09f15e4e5b40 100644
--- a/AnkiDroid/src/main/res/values-mn/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-mn/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Картны Багц
- Багц:
+ Багц:
Төрөл:
Шошгонууд:%1$s
Картнууд: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-mn/03-dialogs.xml b/AnkiDroid/src/main/res/values-mn/03-dialogs.xml
index 4c4f63b3c894..09d2f5c37d8f 100644
--- a/AnkiDroid/src/main/res/values-mn/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-mn/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-mn/10-preferences.xml b/AnkiDroid/src/main/res/values-mn/10-preferences.xml
index 1733669553e7..857ec034a0ef 100644
--- a/AnkiDroid/src/main/res/values-mn/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-mn/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Идэвхгүй
+ Enabled
+ Идэвхгүй
- %d минут
- %d минут
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-mr/02-strings.xml b/AnkiDroid/src/main/res/values-mr/02-strings.xml
index 60427b630399..1090a2965493 100644
--- a/AnkiDroid/src/main/res/values-mr/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-mr/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Kārḍa ḍēka:
- Ḍēka:
+ Ḍēka:
Prakāra:
Ṭĕgja:%1$s
Kārḍa:%1$s
@@ -220,6 +220,8 @@
अक्षराचा आकार
टूलबार दर्शवा
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-mr/03-dialogs.xml b/AnkiDroid/src/main/res/values-mr/03-dialogs.xml
index 5b4780c553ce..2774a2700ea4 100644
--- a/AnkiDroid/src/main/res/values-mr/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-mr/03-dialogs.xml
@@ -49,7 +49,7 @@
आजची पुनरावलोकन मर्यादा वाढवा / कमी करा (“-3”)
गेल्या x दिवसात विसरलेल्या कार्डांचे पुनरावलोकन करा:
Review cards due in the next:
- शेवटच्या x दिवसात जोडलेल्या नवीन कार्डांचे पूर्वावलोकन करा:
+ Preview new cards added in the last:
Select x cards from the deck:
स्टोरेज भरले आहे
स्टोरेज जवळजवळ भरले आहे
@@ -78,6 +78,7 @@
अधिलिखित
दुरुस्ती
पुनर्स्थित करा
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
निर्दिष्ट पथ वैध निर्देशिका नाही
प्रदान केलेला मजकूर वैध PEM-एनकोड केलेल्या X509 प्रमाणपत्राचे निराकरण करत नाही
प्रमाणपत्र पार्स करताना त्रुटी
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
मीडिया तपासत आहे…
Adding tags…
- Tagging results
+ Tags added
मीडिया हटवित आहे…
हटविण्याचा निकाल
diff --git a/AnkiDroid/src/main/res/values-mr/10-preferences.xml b/AnkiDroid/src/main/res/values-mr/10-preferences.xml
index 44e429a52e37..c9a0ca29c13e 100644
--- a/AnkiDroid/src/main/res/values-mr/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-mr/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- डिसेबल केले
+ Enabled
+ डिसेबल केले
- %d मि
- %d मिनिटे
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid निर्देशिका
पूर्णस्क्रीन मोड
- बंद
+ बंद
सिस्टम बार लपवा
सिस्टम बार आणि उत्तर बटणे लपवा
उत्तर बटणे स्थिती
@@ -216,11 +217,80 @@
नेव्हिगेशन
मीडिया
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
टॅग निवडा
@@ -369,9 +439,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ms/02-strings.xml b/AnkiDroid/src/main/res/values-ms/02-strings.xml
index 50280c46b7ef..4706a2b4a32b 100644
--- a/AnkiDroid/src/main/res/values-ms/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ms/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Dek kad:
- Dek:
+ Dek:
Jenis:
Tag: %1$s
Kad: %1$s
@@ -217,6 +217,8 @@
Saiz tulisan
Lihat Bar Alat
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ms/03-dialogs.xml b/AnkiDroid/src/main/res/values-ms/03-dialogs.xml
index bd05ba077554..235e054d23a9 100644
--- a/AnkiDroid/src/main/res/values-ms/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ms/03-dialogs.xml
@@ -47,7 +47,7 @@
Tambahkan/Kurangkan (“-3”) had semakan hari ini sebanyak
Semak kad terlupa dalam x hari:
Review cards due in the next:
- Pratonton kad baru ditambah dalam x hari lepas:
+ Preview new cards added in the last:
Select x cards from the deck:
Storan penuh
Storan hampir penuh
@@ -76,6 +76,7 @@
Tulis ganti
Baiki
Ganti
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Laluan yang dinyatakan bukan direktori yang sah
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -84,7 +85,7 @@
This may take a long time with large media collections. Proceed?
Memeriksa media…
Adding tags…
- Tagging results
+ Tags added
Memadam media…
Keputusan pemadaman
diff --git a/AnkiDroid/src/main/res/values-ms/10-preferences.xml b/AnkiDroid/src/main/res/values-ms/10-preferences.xml
index 1d6cd7e1677d..c788f92981ba 100644
--- a/AnkiDroid/src/main/res/values-ms/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ms/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Dilumpuhkan
+ Enabled
+ Dilumpuhkan
- %d mins
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
Direktori AnkiDroid
Mod skrin penuh
- Matikan
+ Matikan
Sembunyi bar sistem
Sembunyi bar sistem dan butang jawapan
Kedudukan butang jawapan
@@ -217,11 +218,79 @@
Navigasi
Media
Lain-lain
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
Pilih tag
@@ -370,9 +439,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-my/02-strings.xml b/AnkiDroid/src/main/res/values-my/02-strings.xml
index 9346ea150078..18d3e307047a 100644
--- a/AnkiDroid/src/main/res/values-my/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-my/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Type:
Tags: %1$s
Cards: %1$s
@@ -217,6 +217,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-my/03-dialogs.xml b/AnkiDroid/src/main/res/values-my/03-dialogs.xml
index 40d9735fcb9f..4c8ff155e87c 100644
--- a/AnkiDroid/src/main/res/values-my/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-my/03-dialogs.xml
@@ -47,7 +47,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -76,6 +76,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -84,7 +85,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-my/10-preferences.xml b/AnkiDroid/src/main/res/values-my/10-preferences.xml
index b42c976391cb..047d011700d0 100644
--- a/AnkiDroid/src/main/res/values-my/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-my/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d mins
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -217,11 +218,79 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
Select tags
@@ -370,9 +439,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-nl/02-strings.xml b/AnkiDroid/src/main/res/values-nl/02-strings.xml
index 89495a50ff1b..33edd2304344 100644
--- a/AnkiDroid/src/main/res/values-nl/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-nl/02-strings.xml
@@ -29,7 +29,7 @@
Notitie opgeslagen
Kaartenset:
- Set:
+ Set:
Type:
Kenmerken: %1$s
Kaarten: %1$s
@@ -220,6 +220,8 @@
Lettergrootte
Werkbalk weergeven
+ Show preview
+ Hide preview
Maak dikgedrukt
Maak schuingedrukt
Maak onderstreept
diff --git a/AnkiDroid/src/main/res/values-nl/03-dialogs.xml b/AnkiDroid/src/main/res/values-nl/03-dialogs.xml
index 249bc9b33737..9bc2dacb5d87 100644
--- a/AnkiDroid/src/main/res/values-nl/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-nl/03-dialogs.xml
@@ -49,7 +49,7 @@
De nieuwe overhoringslimiet van vandaag verhogen/verlagen (\"-3\") met
Herhaal kaarten vergeten in de laatste x dagen:
Review cards due in the next:
- Voorbeschouw nieuwe kaarten toegevoegd in de laatste x dagen:
+ Preview new cards added in the last:
Selecteer x aantal kaarten van de deck
Opslag vol
Opslag is bijna vol
@@ -78,6 +78,7 @@
Overschrijven
Repareer
Vervangen
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Het opgegeven pad was geen geldige map
De opgegeven tekst wordt niet omgezet in een geldig PEM-gecodeerd X509-certificaat
Fout bij het parseren van certificaat
@@ -86,7 +87,7 @@
Dit kan lang duren bij grote media-collecties. Doorgaan?
Media controleren…
Tags aan het toevoegen…
- Tagging results
+ Tags added
Verwijdering van media…
Resultaat van verwijdering
diff --git a/AnkiDroid/src/main/res/values-nl/10-preferences.xml b/AnkiDroid/src/main/res/values-nl/10-preferences.xml
index a2308f3d7466..c9183f74dd7b 100644
--- a/AnkiDroid/src/main/res/values-nl/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-nl/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Uitgeschakeld
+ Enabled
+ Uitgeschakeld
- %d min
- %d min
@@ -72,7 +73,7 @@
Namen van audiobestanden in de vraag/antwoord velden van de kaartenbrowser weergeven
AnkiDroid directory
Volledig schermmodus
- Uit
+ Uit
De systeembalken verbergen
De systeembalken en antwoordknoppen verbergen
Antwoord knoppen positie
@@ -217,11 +218,80 @@
Navigatie
Media
Diversen
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Herinnering toevoegen
- Overhoringsherinnering toevoegen
- Overhoringsherinnering bewerken
U heeft geen overhoringsherinneringen
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Overhoringsherinnering toevoegen
+ Overhoringsherinnering bewerken
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Selecteer Tags
@@ -370,9 +440,15 @@
feedback or bugs.]]>
Show answer feedback
Scherm
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Instellingen openen
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-nn/02-strings.xml b/AnkiDroid/src/main/res/values-nn/02-strings.xml
index 5a967ae715a3..c2318ca8d5f2 100644
--- a/AnkiDroid/src/main/res/values-nn/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-nn/02-strings.xml
@@ -29,7 +29,7 @@
Notat lagret
Kortleik:
- Kortleik:
+ Kortleik:
Slag:
Etiketter: %1$s
Kort: %1$s
@@ -220,6 +220,8 @@
Skriftstørrelse
Vis verktøylinje
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-nn/03-dialogs.xml b/AnkiDroid/src/main/res/values-nn/03-dialogs.xml
index 8bf69d155d60..e4b18c40241e 100644
--- a/AnkiDroid/src/main/res/values-nn/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-nn/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Lagring fullt
Lagring nesten full
@@ -78,6 +78,7 @@
Overwrite
Reparer
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Sletter media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-nn/10-preferences.xml b/AnkiDroid/src/main/res/values-nn/10-preferences.xml
index 60ceccba6f61..f7ef199c2573 100644
--- a/AnkiDroid/src/main/res/values-nn/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-nn/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Deaktivert
+ Enabled
+ Deaktivert
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullskjermmodus
- Av
+ Av
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Velg emneord
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-no/02-strings.xml b/AnkiDroid/src/main/res/values-no/02-strings.xml
index a8791e55b8c5..7a46131dc929 100644
--- a/AnkiDroid/src/main/res/values-no/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-no/02-strings.xml
@@ -29,7 +29,7 @@
Notat lagret
Kortstokk:
- Kortstokk:
+ Kortstokk:
Type:
Etiketter: %1$s
Kort: %1$s
@@ -220,6 +220,8 @@
Skriftstørrelse
Vis verktøylinje
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-no/03-dialogs.xml b/AnkiDroid/src/main/res/values-no/03-dialogs.xml
index 0480457e7569..75f508953dd7 100644
--- a/AnkiDroid/src/main/res/values-no/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-no/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Lagring fullt
Lagring nesten full
@@ -78,6 +78,7 @@
Overwrite
Reparer
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Sletter media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-no/10-preferences.xml b/AnkiDroid/src/main/res/values-no/10-preferences.xml
index 93d2da0e6e95..35cd2ef3b2ab 100644
--- a/AnkiDroid/src/main/res/values-no/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-no/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Deaktivert
+ Enabled
+ Deaktivert
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullskjermvisning
- Av
+ Av
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Velg emneord
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-or/02-strings.xml b/AnkiDroid/src/main/res/values-or/02-strings.xml
index 1b799f2df48f..0616e0566827 100644
--- a/AnkiDroid/src/main/res/values-or/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-or/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
ପତ୍ର କେରୀ:
- କେରୀ:
+ କେରୀ:
ପ୍ରକାର:
ଟ୍ୟାଗ୍: %1$s
ପତ୍ର: %1$s
@@ -220,6 +220,8 @@
ଫଣ୍ଟ ଆକାର
ଉପକରଣ-ଦଣ୍ଡିକା ଦେଖାଇବା
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-or/03-dialogs.xml b/AnkiDroid/src/main/res/values-or/03-dialogs.xml
index aaad1da1a51a..a593ae697828 100644
--- a/AnkiDroid/src/main/res/values-or/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-or/03-dialogs.xml
@@ -49,7 +49,7 @@
ଆଜିର ସମୀକ୍ଷା ସୀମା ରେ ବୃଦ୍ଧି/ହ୍ରାସ (“-3”) କରନ୍ତୁ
ଗତ x ଦିନରେ ଭୁଲିଯାଇଥିବା ପତ୍ରର ସମୀକ୍ଷା କରନ୍ତୁ:
Review cards due in the next:
- ଗତ x ଦିନରେ ଯୋଡ଼ାଯାଇଥିବା ନୂଆ ପତ୍ରଗୁଡ଼ିକର ପୂର୍ଵାଵଲୋକନ କରିବା:
+ Preview new cards added in the last:
କେରୀରୁ x ଟି ପତ୍ର ଚୟନ କରିବା:
ଷ୍ଟୋରେଜ୍ ଭର୍ତ୍ତି ହେଲାଣି
ଷ୍ଟୋରେଜ୍ ପ୍ରାୟ ଭର୍ତ୍ତି ହେବାକୁ ବସିଲାଣି
@@ -78,6 +78,7 @@
ନବଲିଖନ କରନ୍ତୁ
ସଜାଡନ୍ତୁ
ପ୍ରତିସ୍ଥାପନ
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
ନିର୍ଦ୍ଦିଷ୍ଟ ପଥ ଏକ ବୈଧ ନିର୍ଦେଶିକା ନୁହେଁ
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
ମିଡ଼ିଆ ଯାଞ୍ଚ ଚାଲିଛି…
ଟ୍ୟାଗ୍ ଯୋଡ଼ାଯାଉଛି…
- Tagging results
+ Tags added
ମିଡ଼ିଆ ଵିଲୋପ ହେଉଛି…
ବିଲୋପ ଫଳାଫଳ
diff --git a/AnkiDroid/src/main/res/values-or/10-preferences.xml b/AnkiDroid/src/main/res/values-or/10-preferences.xml
index 319cdbb002f0..9ce15e4f072b 100644
--- a/AnkiDroid/src/main/res/values-or/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-or/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- ଅକ୍ଷମ
+ Enabled
+ ଅକ୍ଷମ
- %d ମି
- %d ମି
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
ପୂର୍ଣ୍ଣସ୍କ୍ରୀନ୍ ମୋଡ୍
- ବନ୍ଦ
+ ବନ୍ଦ
ସିଷ୍ଟମ୍ ଦଣ୍ଡିକାଗୁଡ଼ିକ ଲୁଚାଇବା
ସିଷ୍ଟମ୍ ଦଣ୍ଡିକା ଏଵଂ ଉତ୍ତର ବଟନ୍ ଲୁଚାଇବା
ଉତ୍ତର ବଟନ୍ ଅଵସ୍ଥାନ
@@ -218,11 +219,80 @@
ଦିଗଚଳନ
ମିଡ଼ିଆ
ଵିଵିଧ
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
ସ୍ମାରକ ଯୋଡ଼ିବା
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
ଟ୍ୟାଗ୍ ଚୟନ କରନ୍ତୁ
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
ପରଦା
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-pa/02-strings.xml b/AnkiDroid/src/main/res/values-pa/02-strings.xml
index 929078d6a676..b72bd15de596 100644
--- a/AnkiDroid/src/main/res/values-pa/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-pa/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
ਕਾਰਡ ਡੈੱਕ:
- ਡੈੱਕ:
+ ਡੈੱਕ:
ਕਿਸਮ:
ਟੈਗਸ: %1$s
ਕਾਰਡ: %1$s
@@ -220,6 +220,8 @@
ਫੌਂਟ ਦਾ ਆਕਾਰ
ਟੂਲਬਾਰ ਦਿਖਾਓ
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-pa/03-dialogs.xml b/AnkiDroid/src/main/res/values-pa/03-dialogs.xml
index c4040e88c520..e77ce3eb51ea 100644
--- a/AnkiDroid/src/main/res/values-pa/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-pa/03-dialogs.xml
@@ -49,7 +49,7 @@
ਅੱਜ ਦੀ ਸਮੀਖਿਆ ਸੀਮਾ ਵਧਾਓ/ਘਟਾਓ (“-3”)
ਅੱਜ ਦੀ ਸਮੀਖਿਆ ਸੀਮਾ ਵਧਾਓ/ਘਟਾਓ (“-3”)
Review cards due in the next:
- ਪਿਛਲੇ x ਦਿਨਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤੇ ਗਏ ਨਵੇਂ ਕਾਰਡਾਂ ਦੀ ਸੰਖੇਪ ਜਾਣਕਾਰੀ:
+ Preview new cards added in the last:
Select x cards from the deck:
ਸਟੋਰੇਜ ਭਰੀ ਹੋਈ ਹੈ
ਸਟੋਰੇਜ ਲਗਭਗ ਭਰੀ ਹੋਈ ਹੈ
@@ -78,6 +78,7 @@
ਓਵਰਰਾਈਟ ਕਰੋ
ਮੁਰੰਮਤ
ਤਬਦੀਲੀ
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
ਨਿਰਦਿਸ਼ਟ ਮਾਰਗ ਇੱਕ ਵੈਧ ਡਾਇਰੈਕਟਰੀ ਨਹੀਂ ਸੀ ਨਿਰਦਿਸ਼ਟ ਮਾਰਗ ਇੱਕ ਵੈਧ ਡਾਇਰੈਕਟਰੀ ਨਹੀਂ ਸੀ
The provided text does not resolve to a valid PEM-encoded X509 certificate
ਸਰਟੀਫਿਕੇਟ ਪਾਰਸ ਕਰਨ ਵਿੱਚ ਤਰੁੱਟੀ
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
ਮੀਡੀਆ ਨੂੰ ਮਿਟਾਇਆ ਜਾ ਰਿਹਾ ਹੈ…
ਮਿਟਾਉਣ ਦਾ ਨਤੀਜਾ
diff --git a/AnkiDroid/src/main/res/values-pa/10-preferences.xml b/AnkiDroid/src/main/res/values-pa/10-preferences.xml
index a437cdafa0dd..c8153ae3fd64 100644
--- a/AnkiDroid/src/main/res/values-pa/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-pa/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- ਅਯੋਗ
+ Enabled
+ ਅਯੋਗ
- %d ਮਿੰਟ
- %d ਮਿੰਟ
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid ਡਾਇਰੈਕਟਰੀ
ਪੂਰੀ ਸਕ੍ਰੀਨ ਮੋਡ
- بند
+ بند
ਸਿਸਟਮ ਬਾਰ ਲੁਕਾਓ
ਸਿਸਟਮ ਬਾਰ ਅਤੇ ਜਵਾਬ ਬਟਨਾਂ ਨੂੰ ਓਹਲੇ ਕਰੋ
ਉੱਤਰ ਬਟਨਾਂ ਦੀ ਸਥਿਤੀ
@@ -217,11 +218,80 @@
ਨੈਵੀਗੇਸ਼ਨ
ਮੀਡੀਆ
ਫੁਟਕਲ
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
ਟੈਗ ਚੁਣੋ
@@ -369,9 +439,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-pl/02-strings.xml b/AnkiDroid/src/main/res/values-pl/02-strings.xml
index c77e3aae2331..3021cfceb443 100644
--- a/AnkiDroid/src/main/res/values-pl/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-pl/02-strings.xml
@@ -29,7 +29,7 @@
Zapisano notatkę
Talia kart:
- Talia:
+ Talia:
Typ:
Tagi: %1$s
Karty: %1$s
@@ -226,6 +226,8 @@
Rozmiar czcionki
Pokaż pasek narzędzi
+ Show preview
+ Hide preview
Pogrubienie
Pochylenie
Podkreślenie
diff --git a/AnkiDroid/src/main/res/values-pl/03-dialogs.xml b/AnkiDroid/src/main/res/values-pl/03-dialogs.xml
index f6059739b332..32059bf5fbe0 100644
--- a/AnkiDroid/src/main/res/values-pl/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-pl/03-dialogs.xml
@@ -53,7 +53,7 @@
Zwiększ/zmniejsz (“-3”) dzisiejszy limit powtórek
Przejrzyj zapomniane karty z ostatnich x dni:
Review cards due in the next:
- Podgląd nowych kart dodanych w ostatnich x dniach:
+ Preview new cards added in the last:
Wybierz x kart z talii:
Pamięć jest pełna
Pamięć jest prawie pełna
@@ -82,6 +82,7 @@
Nadpisz
Napraw
Zastąp
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Wybrana ścieżka nie wskazuje prawidłowo katalogu
Podany tekst nie rozwiązuje poprawnego certyfikatu X509 zakodowanego PEM
Błąd podczas parsowania certyfikatu
@@ -90,7 +91,7 @@
To może zająć dużo czasu w przypadku dużych kolekcji multimedialnych. Kontynuować?
Sprawdzanie mediów…
Dodawanie tagów…
- Tagging results
+ Tags added
Usuwanie mediów…
Wynik usuwania
diff --git a/AnkiDroid/src/main/res/values-pl/10-preferences.xml b/AnkiDroid/src/main/res/values-pl/10-preferences.xml
index 93e8eafbc31b..05155fac2429 100644
--- a/AnkiDroid/src/main/res/values-pl/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-pl/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Wyłączone
+ Enabled
+ Wyłączone
- %d min.
- %d min.
@@ -74,7 +75,7 @@
Wyświetlaj nazwy plików dźwiękowych w polach pytanie/odpowiedź w przeglądarce kart
Katalog AnkiDroid
Tryb pełnoekranowy
- Wyłączony
+ Wyłączony
Ukryj paski systemowe
Ukryj paski systemowe i przyciski odpowiedzi
Położenie przycisków odpowiedzi
@@ -222,11 +223,82 @@
Nawigacja
Multimedia
Pozostałe
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Dodaj przypomnienie
- Dodaj przypomnienie o powtórce
- Edytuj przypomnienie o powtórce
Nie masz przypomnień o powtórce
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Dodaj przypomnienie o powtórce
+ Edytuj przypomnienie o powtórce
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Wybierz tagi
@@ -378,9 +450,15 @@
opinii lub błędów.]]>
Pokaż reakcję odpowiedzi
Ekran
- Tylko odpowiedź
- Odwróć i odpowiedz
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Otwórz ustawienia
Powiązane ustawienia
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml b/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml
index 3921e2870112..f885a929159a 100644
--- a/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml
@@ -29,7 +29,7 @@
Nota salva
Baralho de cartões:
- Baralho:
+ Baralho:
Tipo:
Tags: %1$s
Cartões: %1$s
@@ -220,6 +220,8 @@
Tamanho da fonte
Mostrar barra de ferramentas
+ Show preview
+ Hide preview
Formatar como Negrito
Formatar como Itálico
Formatar como Sublinhado
diff --git a/AnkiDroid/src/main/res/values-pt-rBR/03-dialogs.xml b/AnkiDroid/src/main/res/values-pt-rBR/03-dialogs.xml
index 3998cda20c05..b2a6795f0c8c 100644
--- a/AnkiDroid/src/main/res/values-pt-rBR/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-pt-rBR/03-dialogs.xml
@@ -49,7 +49,7 @@
Aumentar/diminuir (\"-3\") o limite de revisões de hoje.
Revisar cards esquecidos nos últimos x dias:
Review cards due in the next:
- Visualizar novos cards adicionados nos últimos x dias:
+ Preview new cards added in the last:
Selecione x cards do baralho:
Armazenamento cheio
Armazenamento quase cheio
@@ -78,6 +78,7 @@
Sobrescrever
Reparar
Substituir
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
O caminho especificado não era um diretório válido
O texto fornecido não corresponde a um certificado X509 codificado em PEM
Erro na análise do certificado.
@@ -86,7 +87,7 @@
Coleções de mídias grandes podem levar muito tempo. Prosseguir?
Verificando mídia…
Adicionando tags…
- Tagging results
+ Tags added
Excluindo mídia…
Resultado da exclusão
diff --git a/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml b/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml
index 001c6b4c6332..8ac597d53598 100644
--- a/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Desativado
+ Enabled
+ Desativado
- %d minuto
- %d minutos
@@ -72,7 +73,7 @@
Exibir os nomes dos arquivos de áudio nos campos de pergunta/resposta do navegador do cartão
Diretório do AnkiDroid
Modo tela cheia
- Desligar
+ Desligar
Ocultar barras de sistema
Ocultar barras de sistema e botões de resposta
Posição dos botões de resposta
@@ -218,11 +219,80 @@
Navegação
Multimídia
Diversos
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Adicionar lembrete
- Adicionar lembrete de revisão
- Editar lembrete de revisão
Você não tem lembretes de revisão
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Adicionar lembrete de revisão
+ Editar lembrete de revisão
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Selecione Tags
@@ -372,9 +442,15 @@
sugestão ou relato de erro.]]>
Mostre feedback da resposta
Tela
- Somente responder
- Virar e responder
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Abrir configurações
Configurações relacionadas
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml b/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml
index b521cff48afb..83bc465127f8 100644
--- a/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Baralho de fichas:
- Baralho:
+ Baralho:
Tipo:
Etiquetas: %1$s
Fichas: %1$s
@@ -220,6 +220,8 @@
Tamanho da letra
Mostrar Barra de Ferramentas
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-pt-rPT/03-dialogs.xml b/AnkiDroid/src/main/res/values-pt-rPT/03-dialogs.xml
index aa9179b1ac67..0ed89c9f6e03 100644
--- a/AnkiDroid/src/main/res/values-pt-rPT/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-pt-rPT/03-dialogs.xml
@@ -49,7 +49,7 @@
Aumentar/diminuir (\"-3\") o limite de fichas a rever, de hoje, em
Rever fichas esquecidas nos últimos x dias:
Review cards due in the next:
- Pré-visualizar novas fichas adicionadas nos últimos x dias:
+ Preview new cards added in the last:
Seleccione x fichas do baralho:
Armazenamento cheio
Armazenamento quase cheio
@@ -78,6 +78,7 @@
Substituir
Reparar
Substituir
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
O caminho especificado não era uma diretoria válida
O texto introduzido não é um certificado X509 no formato PEM.
Erro ao analisar certificado
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
A verificar multimédia…
A adicionar etiquetas…
- Tagging results
+ Tags added
A apagar ficheiros de multimédia…
Resultado da remoção
diff --git a/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml b/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml
index af95d2feb056..e5612916a553 100644
--- a/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Desativado
+ Enabled
+ Desativado
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
Diretoria do AnkiDroid
Modo de ecrã completo
- Desligado
+ Desligado
Ocultar as barras do sistema
Ocultar as barras do sistema e botões de resposta
Posição dos botões de resposta
@@ -219,11 +220,80 @@
Navegação
Multimédia
Outros
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Selecione as etiquetas
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Abrir as definições
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ro/02-strings.xml b/AnkiDroid/src/main/res/values-ro/02-strings.xml
index d222f68c2708..f18e08142d12 100644
--- a/AnkiDroid/src/main/res/values-ro/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ro/02-strings.xml
@@ -29,7 +29,7 @@
Notiță salvată
Pachet de cartonașe:
- Pachet:
+ Pachet:
Tip:
Etichete: %1$s
Cartonașe: %1$s
@@ -223,6 +223,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ro/03-dialogs.xml b/AnkiDroid/src/main/res/values-ro/03-dialogs.xml
index bd3d942ded89..fcc707949414 100644
--- a/AnkiDroid/src/main/res/values-ro/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ro/03-dialogs.xml
@@ -51,7 +51,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -80,6 +80,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -88,7 +89,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-ro/10-preferences.xml b/AnkiDroid/src/main/res/values-ro/10-preferences.xml
index a0cca83cbf9f..e4822f76030b 100644
--- a/AnkiDroid/src/main/res/values-ro/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ro/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Dezactivat
+ Enabled
+ Dezactivat
- %d min
- %d mins
@@ -73,7 +74,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -221,11 +222,81 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+
Selectaţi Tag-uri
@@ -376,9 +447,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ru/01-core.xml b/AnkiDroid/src/main/res/values-ru/01-core.xml
index f98e5fe8eca4..84ad8acc7958 100644
--- a/AnkiDroid/src/main/res/values-ru/01-core.xml
+++ b/AnkiDroid/src/main/res/values-ru/01-core.xml
@@ -137,7 +137,7 @@
Чтение и запись в базу данных AnkiDroid
доступ к колодам, карточкам, записям, их типам, а также создание новых
- Card browser
+ Обозреватель карточек
Anki card
Коллекции не могут быть объединены.\nКакую коллекцию вы хотите оставить?
diff --git a/AnkiDroid/src/main/res/values-ru/02-strings.xml b/AnkiDroid/src/main/res/values-ru/02-strings.xml
index 70d983a87854..895e268a3adf 100644
--- a/AnkiDroid/src/main/res/values-ru/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ru/02-strings.xml
@@ -29,7 +29,7 @@
Запись сохранена
Колода:
- Колода:
+ Колода:
Тип:
Метки: %1$s
Карточки: %1$s
@@ -226,6 +226,8 @@
Размер шрифта
Показать панель инструментов
+ Show preview
+ Hide preview
Жирный
Курсив
Подчёркнутый
diff --git a/AnkiDroid/src/main/res/values-ru/03-dialogs.xml b/AnkiDroid/src/main/res/values-ru/03-dialogs.xml
index c5ec8b15af36..c5b99c9451c5 100644
--- a/AnkiDroid/src/main/res/values-ru/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ru/03-dialogs.xml
@@ -53,7 +53,7 @@
Увеличить или уменьшить («-3») лимит повторяемых на сегодня
Забытые за сколько дней повторить:
Review cards due in the next:
- Посмотреть новые за сколько дней:
+ Preview new cards added in the last:
Выберите количество карточек:
Хранилище заполнено
Хранилище почти заполнено
@@ -82,6 +82,7 @@
Перезаписать
Исправить
Заменить
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Указанный путь не является допустимой папкой
Предоставленный текст не преобразуется в допустимый X509 сертификат PEM
Ошибка при обработке сертификата
@@ -90,7 +91,7 @@
Это может занять много времени с большими коллекциями медиа. Продолжить?
Медиафайлы проверяются…
Добавление меток…
- Tagging results
+ Tags added
Медиафайлы
удаляются…
Результат удаления
diff --git a/AnkiDroid/src/main/res/values-ru/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-ru/07-cardbrowser.xml
index 96f28f5abd2f..e3df3ef96126 100644
--- a/AnkiDroid/src/main/res/values-ru/07-cardbrowser.xml
+++ b/AnkiDroid/src/main/res/values-ru/07-cardbrowser.xml
@@ -127,5 +127,5 @@
Selected
Unavailable
Card content can\'t be sorted. Use Sort Field
- Only available in cards mode
+ Доступно только в режиме карточек
diff --git a/AnkiDroid/src/main/res/values-ru/10-preferences.xml b/AnkiDroid/src/main/res/values-ru/10-preferences.xml
index 6e7af8956f89..1a1e275573f2 100644
--- a/AnkiDroid/src/main/res/values-ru/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ru/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Выкл.
+ Enabled
+ Выкл.
- %d мин.
- %d мин.
@@ -74,7 +75,7 @@
Отображать имена аудио файлов в полях вопроса или ответа в списке карточек
Папка AnkiDroid
Полноэкранный режим
- Выкл.
+ Выкл.
Скрыть системную панель
Скрыть системную панель и кнопки ответа
Положение кнопок ответа
@@ -221,11 +222,82 @@
Навигация
Медиа
Разное
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Добавить напоминание
- Добавить напоминание о повторении
- Редактировать напоминания о повторении
У Вас нет напоминаний о повторении
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Добавить напоминание о повторении
+ Редактировать напоминания о повторении
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Выберите метки
@@ -376,9 +448,15 @@
отзывах или ошибках.]]>
Показать обратную связь по ответу
Экран
- Только ответ
- Перевернуть и ответить
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Открыть настройки
Связанные настройки
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-sat/02-strings.xml b/AnkiDroid/src/main/res/values-sat/02-strings.xml
index 021157fb966e..709e5e7e3f11 100644
--- a/AnkiDroid/src/main/res/values-sat/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-sat/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
ᱠᱟᱰ ᱰᱮᱠᱺ
- ᱰᱮᱠᱺ
+ ᱰᱮᱠᱺ
ᱯᱨᱚᱠᱟᱨᱺ
ᱴᱮᱜᱥᱺ%1$s
ᱠᱟᱰᱺ%1$s
@@ -220,6 +220,8 @@
ᱪᱤᱠᱤ ᱢᱟᱯ
ᱴᱩᱞᱵᱟᱨ ᱫᱮᱠᱷᱟᱣᱢᱮ
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-sat/03-dialogs.xml b/AnkiDroid/src/main/res/values-sat/03-dialogs.xml
index 416a884bd00f..02cf111f4799 100644
--- a/AnkiDroid/src/main/res/values-sat/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-sat/03-dialogs.xml
@@ -49,7 +49,7 @@
ᱵᱚᱰᱷᱟᱣ/ᱠᱚᱢᱢᱮ (“-3”) ᱛᱮᱦᱚᱸᱧᱟᱜᱽ ᱨᱤᱵᱷᱤᱣ ᱞᱤᱢᱤᱴ
ᱨᱤᱵᱷᱭᱩ ᱠᱟᱰ x ᱫᱤᱱ ᱨᱮ ᱦᱤᱲᱤᱧᱟᱜ ᱠᱚᱺ
Review cards due in the next:
- ᱢᱟᱲᱟᱝ xᱫᱤᱱ ᱨᱮ ᱥᱮᱞᱮᱫ ᱠᱟᱱ ᱠᱟᱰ ᱠᱚ preview ᱢᱮ
+ Preview new cards added in the last:
Select x cards from the deck:
ᱥᱴᱳᱨᱮᱡ ᱯᱩᱨᱤ
ᱥᱴᱳᱨᱮᱡ ᱜᱮ ᱯᱩᱨᱤ
@@ -78,6 +78,7 @@
ᱚᱞ ᱪᱮᱛᱟᱱ ᱚᱞ
ᱡᱩᱛᱨᱟᱹᱣ
ᱥᱟᱦᱟ
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
ᱦᱚᱨ ᱨᱮ ᱢᱮᱱᱟᱜ ᱩᱫᱩᱫᱩᱜ ᱵᱟᱭ ᱴᱷᱤᱠ ᱟᱟ
ᱮᱢ ᱟᱠᱟᱱ ᱴᱮᱠᱥᱴ ᱫᱚ PEM-encoded X509 ᱥᱟᱨᱴᱤᱯᱷᱤᱠᱮᱴ ᱞᱟᱹᱜᱤᱫ ᱵᱟᱭ ᱦᱟᱹᱴᱤᱧ ᱟᱠᱟᱱᱟ
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
ᱢᱮᱰᱤᱭᱟ ᱧᱮᱞ ᱵᱤᱰᱟᱣᱜ ᱠᱟᱱᱟ…
Adding tags…
- Tagging results
+ Tags added
ᱢᱤᱰᱤᱭᱟ ᱜᱮᱫ ᱜᱤᱰᱤᱜ ᱠᱟᱱᱟ…
ᱜᱮᱫ ᱜᱤᱰᱤ ᱛᱮᱞᱟ
diff --git a/AnkiDroid/src/main/res/values-sat/10-preferences.xml b/AnkiDroid/src/main/res/values-sat/10-preferences.xml
index 1b28a1c24217..aebf068a3691 100644
--- a/AnkiDroid/src/main/res/values-sat/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-sat/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- ᱵᱚᱸᱫ
+ Enabled
+ ᱵᱚᱸᱫ
- %d ᱢᱤᱱᱤᱴ
- %d ᱢᱤᱱᱤᱴ
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid ᱩᱫᱩᱜ ᱯᱩᱛᱷᱤ
ᱯᱩᱨᱟᱹᱥᱠᱨᱤᱱ ᱢᱚᱰ
- ᱵᱚᱸᱫ
+ ᱵᱚᱸᱫ
ᱥᱭᱥᱴᱚᱢ ᱵᱟᱨ ᱩᱠᱩᱭ ᱢᱮ
ᱥᱭᱥᱴᱚᱢ ᱵᱟᱨ ᱟᱨ ᱛᱮᱞᱟ ᱵᱚᱴᱚᱱ ᱩᱠᱩᱭᱢᱮ
ᱛᱮᱞᱟ ᱵᱚᱴᱚᱱ ᱡᱟᱭᱜᱟ
@@ -218,11 +219,80 @@
ᱱᱮᱵᱤᱜᱮᱥᱚᱱ
ᱢᱤᱰᱤᱭᱟ
ᱢᱤᱥᱠ
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
ᱚᱯᱮᱱ ᱥᱮᱴᱤᱝ
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-sc/02-strings.xml b/AnkiDroid/src/main/res/values-sc/02-strings.xml
index 7ac6e4839226..4810d605b88b 100644
--- a/AnkiDroid/src/main/res/values-sc/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-sc/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Matzu de cartas:
- Matzu:
+ Matzu:
Casta:
Etichetas: %1$s
Cartas: %1$s
@@ -222,6 +222,8 @@
Mannària de su caràtere
Ammustra sa barra de sas ainas
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-sc/03-dialogs.xml b/AnkiDroid/src/main/res/values-sc/03-dialogs.xml
index 377626b4cd13..36962b87940a 100644
--- a/AnkiDroid/src/main/res/values-sc/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-sc/03-dialogs.xml
@@ -54,7 +54,7 @@
Ismànnia/mìnima (“-3”) su lìmite de revisiones de oe de
Revisiona sas cartas ismentigadas in sas ùrtimas x dies:
Review cards due in the next:
- Ammustra s\'antiprima de sas cartas noas annànghidas in sas ùrtimas x dies:
+ Preview new cards added in the last:
Select x cards from the deck:
Memòria prena
Memòria belle prena
@@ -83,6 +83,7 @@
Subraiscrie
Acontza
Remplasa
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
S\'àndala dislindada non fiat una cartella vàlida
Su testu frunidu non currispondet a unu tzertificadu X509 codificadu in PEM vàlidu
Faddina analizende su tzertificadu
@@ -91,7 +92,7 @@
This may take a long time with large media collections. Proceed?
Vetifichende sos mèdios…
Adding tags…
- Tagging results
+ Tags added
Iscantzellende mèdios…
Resurtadu de s\'iscantzelladura
diff --git a/AnkiDroid/src/main/res/values-sc/10-preferences.xml b/AnkiDroid/src/main/res/values-sc/10-preferences.xml
index 88f42d75e941..7955989b2cdf 100644
--- a/AnkiDroid/src/main/res/values-sc/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-sc/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabilitadu
+ Enabled
+ Disabilitadu
- %d min
- %d minutos
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
Cartella de AnkiDroid
Modalidade a ischermu intreu
- Istudadu
+ Istudadu
Cua sas barras de sistema
Cua sas barras de sistema e sos butones de risposta
Positzione de su butone de sas rispostas
@@ -218,11 +219,80 @@
Navigatzione
Mèdios
Vàrias
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Seletziona sas etichetas
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Aberi sas impostatziones
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-sk/02-strings.xml b/AnkiDroid/src/main/res/values-sk/02-strings.xml
index 6dc5a1a5f74d..faabb2d0419b 100644
--- a/AnkiDroid/src/main/res/values-sk/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-sk/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Balíček kartičky:
- Balíček:
+ Balíček:
Typ:
Štítky: %1$s
Kartičky: %1$s
@@ -226,6 +226,8 @@
Veľkosť fontu
Zobraziť lištu nástrojov
+ Show preview
+ Hide preview
Formátovať ako tučné
Formátovať ako kurzíva
Formátovať ako podčiarknutie
diff --git a/AnkiDroid/src/main/res/values-sk/03-dialogs.xml b/AnkiDroid/src/main/res/values-sk/03-dialogs.xml
index 1cc5ed864431..ebffb7dde89f 100644
--- a/AnkiDroid/src/main/res/values-sk/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-sk/03-dialogs.xml
@@ -53,7 +53,7 @@
Zvýšiť/znížiť („-3”) dnešný limit kartičiek na zopakovanie o
Zopakovať si kartičky zabudnuté za posledných x dní:
Zopakovať kartičky na skúšanie za:
- Ukážka nových kartičiek pridaných za posledných x dní:
+ Preview new cards added in the last:
Vybrať x kartičiek z balíčka:
Úložisko je plné
Úložisko je takmer plné
@@ -82,6 +82,7 @@
Prepísať
Opraviť
Nahradiť
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Špecifikovaná cesta nebola platným adresárom
Zadaný text neodpovedá požadovanému certifikátu X509 kódovanom vo formáte PEM.
Chyba pri dekódovaní certifikátu
@@ -90,7 +91,7 @@
This may take a long time with large media collections. Proceed?
Kontrola multimédií…
Adding tags…
- Tagging results
+ Tags added
Médiá sa vymazávajú…
Výsledok vymazania
diff --git a/AnkiDroid/src/main/res/values-sk/10-preferences.xml b/AnkiDroid/src/main/res/values-sk/10-preferences.xml
index 91c8e5159415..7ec9a48b9025 100644
--- a/AnkiDroid/src/main/res/values-sk/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-sk/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Vypnuté
+ Enabled
+ Vypnuté
- %d minúta
- %d minúty
@@ -74,7 +75,7 @@
Zobraziť názvy audio súborov v poliach otázka/odpoveď prehliadača kartičiek
Adresár AnkiDroid
Celoobrazovkový režim
- Vypnúť
+ Vypnúť
Skryť systémové panely
Skryť systémové panely a tlačidlá s odpoveďami
Pozícia tlačidla pre odpoveď
@@ -223,11 +224,82 @@
Navigácia
Médiá
Rôzne
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Zvoliť štítky
@@ -379,9 +451,15 @@
spätnú väzbu alebo chyby.]]>
Ukázať odozvu odpovede
Obrazovka
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Súvisiace nastavenia
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-sl/02-strings.xml b/AnkiDroid/src/main/res/values-sl/02-strings.xml
index b326182ef0fa..04f6442b2bff 100644
--- a/AnkiDroid/src/main/res/values-sl/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-sl/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Komplet kartic:
- Komplet:
+ Komplet:
Vrsta:
Oznake: %1$s
Kartice: %1$s
@@ -226,6 +226,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-sl/03-dialogs.xml b/AnkiDroid/src/main/res/values-sl/03-dialogs.xml
index a11dd9936706..d39d9c0f91c0 100644
--- a/AnkiDroid/src/main/res/values-sl/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-sl/03-dialogs.xml
@@ -53,7 +53,7 @@
Increase/decrease (“-3”) today’s review limit by
Preglej kartice, ki so bile v zadnjih x-ih dneh pozabljene:
Review cards due in the next:
- Preglej nove kartice dodane v zadnjih x-ih dneh:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -82,6 +82,7 @@
Prepiši
Popravi
Nadomesti
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Navedena pot ni veljavna mapa
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -90,7 +91,7 @@
This may take a long time with large media collections. Proceed?
Preverjanje predstavnosti …
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-sl/10-preferences.xml b/AnkiDroid/src/main/res/values-sl/10-preferences.xml
index d6c3256bf9d0..9be3094b4a61 100644
--- a/AnkiDroid/src/main/res/values-sl/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-sl/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Onemogočeno
+ Enabled
+ Onemogočeno
- %d min
- %d mins
@@ -74,7 +75,7 @@
Display audio filenames in the card browser question/answer fields
Mapa AnkiDroid
Celozaslonski način
- Izklopljen
+ Izklopljen
Skrij sistemske vrstice
Skrij sistemske vrstice in gumbe za odgovor
Položaj gumbov za odgovarjanje
@@ -223,11 +224,82 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Izberite oznake
@@ -379,9 +451,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-sq/02-strings.xml b/AnkiDroid/src/main/res/values-sq/02-strings.xml
index 9f9f2b39ec1e..f11af2156c59 100644
--- a/AnkiDroid/src/main/res/values-sq/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-sq/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Kuverta e kartave:
- Kuvertë:
+ Kuvertë:
Lloji:
Etiketa: %1$s
Kartat: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-sq/03-dialogs.xml b/AnkiDroid/src/main/res/values-sq/03-dialogs.xml
index 496adbae4a06..aea0f6d16223 100644
--- a/AnkiDroid/src/main/res/values-sq/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-sq/03-dialogs.xml
@@ -49,7 +49,7 @@
Rrit/zvogëlo (“-3”) kufirin e sotëm të rishikimit me
Rishikoni kartat e harruara në x ditët e fundit:
Review cards due in the next:
- Shikoni paraprakisht kartat e reja të shtuara në x ditët e fundit:
+ Preview new cards added in the last:
Select x cards from the deck:
Magazinimi plot
Magazinimi pothuajse i mbushur
@@ -78,6 +78,7 @@
Mbishkruaj
Riparim
Zëvendëso
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Rruga e specifikuar nuk ishte një drejtori e vlefshme
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Po kontrollon median…
Adding tags…
- Tagging results
+ Tags added
Po fshin median…
Rezultati i fshirjes
diff --git a/AnkiDroid/src/main/res/values-sq/10-preferences.xml b/AnkiDroid/src/main/res/values-sq/10-preferences.xml
index 835edd57643c..3afa08a1ced7 100644
--- a/AnkiDroid/src/main/res/values-sq/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-sq/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- E çaktivizuar
+ Enabled
+ E çaktivizuar
- %d min
- %d minuta
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-sr/02-strings.xml b/AnkiDroid/src/main/res/values-sr/02-strings.xml
index 148f686a2bac..10ab206c1e13 100644
--- a/AnkiDroid/src/main/res/values-sr/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-sr/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Шпил карата:
- Шпил:
+ Шпил:
Тип:
Ознаке: %1$s
Карте: %1$s
@@ -223,6 +223,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-sr/03-dialogs.xml b/AnkiDroid/src/main/res/values-sr/03-dialogs.xml
index e61eef533b60..75ad76c00ad7 100644
--- a/AnkiDroid/src/main/res/values-sr/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-sr/03-dialogs.xml
@@ -51,7 +51,7 @@
Increase/decrease (“-3”) today’s review limit by
Понови заборављено за последњих х дана:
Review cards due in the next:
- Нове картице, додате за последњих х дана:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -80,6 +80,7 @@
Пресними
Обнављање
Замени
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Путању коју сте унели није правилна фасцикла
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -88,7 +89,7 @@
This may take a long time with large media collections. Proceed?
Проверавање медијске датотеке…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-sr/10-preferences.xml b/AnkiDroid/src/main/res/values-sr/10-preferences.xml
index 4af3b5744a39..5d739e5340ea 100644
--- a/AnkiDroid/src/main/res/values-sr/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-sr/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -73,7 +74,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Пуноекрански режим
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -221,11 +222,81 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+
Изаберите ознаке
@@ -376,9 +447,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-sv/02-strings.xml b/AnkiDroid/src/main/res/values-sv/02-strings.xml
index 908cafdc73c7..322ce619c611 100644
--- a/AnkiDroid/src/main/res/values-sv/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-sv/02-strings.xml
@@ -29,7 +29,7 @@
Anteckning sparad!
Kortlek:
- Kortlek:
+ Kortlek:
Typ:
Etiketter: %1$s
Kort: %1$s
@@ -220,6 +220,8 @@
Teckenstorlek
Visa verktygsfält
+ Show preview
+ Hide preview
Formatera som fet
Formatera som kursiv
Formatera som understruken
diff --git a/AnkiDroid/src/main/res/values-sv/03-dialogs.xml b/AnkiDroid/src/main/res/values-sv/03-dialogs.xml
index 735abf1b3b13..43540b076141 100644
--- a/AnkiDroid/src/main/res/values-sv/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-sv/03-dialogs.xml
@@ -49,7 +49,7 @@
Öka/minska (“-3”) dagens repetitionsgräns med
Repetera kort som är glömda under de senaste x dagarna:
Review cards due in the next:
- Förhandsgranska nya kort som lagts till de senaste x dagarna:
+ Preview new cards added in the last:
Välj x kort från kortleken:
Lagring full
Lagring nästan full
@@ -78,6 +78,7 @@
Skriv över
Reparera
Ersätt
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Den valda sökvägen är ogiltig
The provided text does not resolve to a valid PEM-encoded X509 certificate
Kunde inte tolka certifikatet
@@ -86,7 +87,7 @@
Detta kan ta lång tid med stora mediasamlingar. Fortsätt?
Kontrollerar media…
Lägger till taggar…
- Tagging results
+ Tags added
Tar bort media…
Raderingsresultat
diff --git a/AnkiDroid/src/main/res/values-sv/10-preferences.xml b/AnkiDroid/src/main/res/values-sv/10-preferences.xml
index 08fa1397d470..f8c360fac321 100644
--- a/AnkiDroid/src/main/res/values-sv/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-sv/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Inaktiverad
+ Enabled
+ Inaktiverad
- %d min
- %d min
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid sökväg
Fullskärmsläge
- Av
+ Av
Dölj systemets statusfält
Dölj systemets statusfält och svarsknapparna
Svarsknapparnas placering
@@ -218,11 +219,80 @@
Navigering
Media
Övrigt
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Lägg till påminnelse
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Välj etiketter
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Öppna inställningar
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ta/02-strings.xml b/AnkiDroid/src/main/res/values-ta/02-strings.xml
index f26f74f8ab6b..0e13bf8e9bb9 100644
--- a/AnkiDroid/src/main/res/values-ta/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ta/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
அட்டை தளம்:
- தளம்:
+ தளம்:
வகை:
குறிச்சொற்கள்: %1$s
கார்டுகள்: %1$s
@@ -220,6 +220,8 @@
எழுத்துரு அளவு
கருவிப்பட்டியைக் காட்டு
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ta/03-dialogs.xml b/AnkiDroid/src/main/res/values-ta/03-dialogs.xml
index e9fb0f6cbdcd..02e2f50297ea 100644
--- a/AnkiDroid/src/main/res/values-ta/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ta/03-dialogs.xml
@@ -49,7 +49,7 @@
இன்றைய மதிப்பாய்வு வரம்பை அதிகரிக்கவும்/குறைக்கவும் (\"-3\").
கடந்த x நாட்களில் மறக்கப்பட்ட மதிப்பாய்வு அட்டைகள்:
Review cards due in the next:
- கடந்த x நாட்களில் சேர்க்கப்பட்ட புதிய கார்டுகளின் மாதிரிக்காட்சி:
+ Preview new cards added in the last:
Select x cards from the deck:
சேமிப்பு நிரம்பியது
சேமிப்பு கிட்டத்தட்ட நிரம்பிவிட்டது
@@ -78,6 +78,7 @@
மேலெழுதவும்
பழுது
மாற்றம்
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
குறிப்பிடப்பட்ட பாதை சரியான கோப்பகமாக இல்லை
வழங்கப்பட்ட உரை சரியான PEM-குறியீடு செய்யப்பட்ட X509 சான்றிதழில் தீர்க்கப்படாது
சான்றிதழைப் பாகுபடுத்துவதில் பிழை
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
மீடியாவைச் சரிபார்க்கிறது…
Adding tags…
- Tagging results
+ Tags added
மீடியாவை நீக்குகிறது…
நீக்குதல் முடிவு
diff --git a/AnkiDroid/src/main/res/values-ta/10-preferences.xml b/AnkiDroid/src/main/res/values-ta/10-preferences.xml
index 28a8562420a7..87518c1632d4 100644
--- a/AnkiDroid/src/main/res/values-ta/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ta/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- முடக்கப்பட்டது
+ Enabled
+ முடக்கப்பட்டது
- %d நிமிடங்கள்
- %d நிமிடங்கள்
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid அடைவு
முழு திரை பயன்முறை
- ஆஃப்
+ ஆஃப்
சிஸ்டம் பார்களை மறை
கணினி பார்கள் மற்றும் பதில் பொத்தான்களை மறைக்கவும்
பதில் பொத்தான்களின் நிலை
@@ -218,11 +219,80 @@
வழிசெலுத்தல்
ஊடகம்
மற்றவை
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
குறிச்சொற்களைத் தேர்ந்தெடுக்கவும்
@@ -372,9 +442,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
அமைப்புகளைத் திறக்கவும்
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-te/02-strings.xml b/AnkiDroid/src/main/res/values-te/02-strings.xml
index 1f47a2a3f4ef..ba57be640244 100644
--- a/AnkiDroid/src/main/res/values-te/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-te/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
కార్డ్ డెక్:
- డెక్:
+ డెక్:
రకం:
టాగ్లు:%1$s
కార్డులు:%1$s
@@ -220,6 +220,8 @@
ఫాంట్ పరిమాణం
టూల్ బార్ చూపించు
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-te/03-dialogs.xml b/AnkiDroid/src/main/res/values-te/03-dialogs.xml
index 6f53f2635692..12d75bff7b95 100644
--- a/AnkiDroid/src/main/res/values-te/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-te/03-dialogs.xml
@@ -49,7 +49,7 @@
నేటి సమీక్ష పరిమితిని పెంచడం/తగ్గించడం (\"-3\").
గత x రోజుల్లో రిజిస్టర్డ్ కార్డులను మర్చిపోయారు:
Review cards due in the next:
- గత x రోజులలో జోడించిన కొత్త కార్డులను ప్రివ్యూ చేయండి:
+ Preview new cards added in the last:
Select x cards from the deck:
నిల్వ నిండింది
నిల్వ దాదాపు నిండింది
@@ -78,6 +78,7 @@
తిరిగి వ్రాయండి
బాగు చేయు
పునఃస్థాపించుము
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
పేర్కొన్న మార్గం చెల్లుబాటు అయ్యే డైరెక్టరీ కాదు
అందించిన వచనం చెల్లుబాటు అయ్యే PEM-ఎన్కోడ్ చేసిన X509 ప్రమాణపత్రానికి పరిష్కారం కాదు
సర్టిఫికెట్ని అన్వయించడంలో లోపం
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
మీడియాను తనిఖీ చేస్తోంది…
Adding tags…
- Tagging results
+ Tags added
మీడియాను తీసివేస్తోంది…
తొలగింపు ఫలితం
diff --git a/AnkiDroid/src/main/res/values-te/10-preferences.xml b/AnkiDroid/src/main/res/values-te/10-preferences.xml
index 79709b9da69f..b0d08e52baf9 100644
--- a/AnkiDroid/src/main/res/values-te/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-te/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- నిలిపివేయబడింది
+ Enabled
+ నిలిపివేయబడింది
- %d నిమి
- %d నిమిషాలు
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid డైరెక్టరీ
పూర్తి స్క్రీన్ మోడ్
- ఆఫ్
+ ఆఫ్
సిస్టమ్ బార్లను దాచు
సిస్టమ్ బార్లను దాచిపెట్టి, సమాధానం బటన్లను దాచిపెట్టు
సమాధానం బటన్లు స్థానం
@@ -216,11 +217,80 @@
నావిగేషన్
Media
ఇతర
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
ట్యాగ్లను ఎంచుకోండి
@@ -371,9 +441,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
సెట్టింగ్లను తెరవండి
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-tgl/02-strings.xml b/AnkiDroid/src/main/res/values-tgl/02-strings.xml
index bc3f5b989ba7..7728d7f1ccb7 100644
--- a/AnkiDroid/src/main/res/values-tgl/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-tgl/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Deck ng baraha:
- Deck:
+ Deck:
Uri:
Tags: %1$s
Mga baraha: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-tgl/03-dialogs.xml b/AnkiDroid/src/main/res/values-tgl/03-dialogs.xml
index 38bcc66cbbb2..ea1411450e68 100644
--- a/AnkiDroid/src/main/res/values-tgl/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-tgl/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Ang mga baraha na sinusuri ay nakalimutan sa huling x na mga araw:
Review cards due in the next:
- I-prebiyu ang mga bagong baraha na idinagdag sa huling x na mga araw:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
I-overwrite
Ayusin
Palitan
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Ang path na tinukoy ay hindi isang wastong direktoryo
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Sinusuri ang media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-tgl/10-preferences.xml b/AnkiDroid/src/main/res/values-tgl/10-preferences.xml
index 6b55d1dc43ca..4bf367512f81 100644
--- a/AnkiDroid/src/main/res/values-tgl/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-tgl/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- I-disable
+ Enabled
+ I-disable
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
Direktoryo ng AnkiDroid
Fullscreen mode
- Off
+ Off
Itago ang mga bar ng system
Itago ang mga bar ng system at sagutin ang mga pindutan
Posisyon ng sagot sa mga pindutan
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Piliin ang mga tag
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-th/02-strings.xml b/AnkiDroid/src/main/res/values-th/02-strings.xml
index 720421e56d43..2a564772ac6e 100644
--- a/AnkiDroid/src/main/res/values-th/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-th/02-strings.xml
@@ -29,7 +29,7 @@
บันทึกโน้ตเรียบร้อยแล้ว
สำรับบัตรคำ:
- สำรับ:
+ สำรับ:
ชนิด:
ป้ายกำกับ: %1$s
การ์ด: %1$s
@@ -217,6 +217,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-th/03-dialogs.xml b/AnkiDroid/src/main/res/values-th/03-dialogs.xml
index 10004113e9ea..7a5ec3b6bd2a 100644
--- a/AnkiDroid/src/main/res/values-th/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-th/03-dialogs.xml
@@ -47,7 +47,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -76,6 +76,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -84,7 +85,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-th/10-preferences.xml b/AnkiDroid/src/main/res/values-th/10-preferences.xml
index f9681201b59a..d8bbe0dec24b 100644
--- a/AnkiDroid/src/main/res/values-th/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-th/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- ปิดใช้งาน
+ Enabled
+ ปิดใช้งาน
- %d นาที
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- ปิด
+ ปิด
Hide the system bars
Hide the system bars and answer buttons
ตำแหน่งปุ่มคำตอบ
@@ -217,11 +218,79 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
Select tags
@@ -370,9 +439,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ti/02-strings.xml b/AnkiDroid/src/main/res/values-ti/02-strings.xml
index 49d3076932e4..7cd1529bac88 100644
--- a/AnkiDroid/src/main/res/values-ti/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ti/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Card deck:
- Deck:
+ Deck:
Type:
Tags: %1$s
Cards: %1$s
@@ -220,6 +220,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ti/03-dialogs.xml b/AnkiDroid/src/main/res/values-ti/03-dialogs.xml
index 4c4f63b3c894..09d2f5c37d8f 100644
--- a/AnkiDroid/src/main/res/values-ti/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ti/03-dialogs.xml
@@ -49,7 +49,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -78,6 +78,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-ti/10-preferences.xml b/AnkiDroid/src/main/res/values-ti/10-preferences.xml
index 5fba3e8c30c5..e4052f7f9b26 100644
--- a/AnkiDroid/src/main/res/values-ti/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ti/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d min
- %d mins
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Off
+ Off
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-tr/02-strings.xml b/AnkiDroid/src/main/res/values-tr/02-strings.xml
index c53d46b86b44..ce5a8fd3b101 100644
--- a/AnkiDroid/src/main/res/values-tr/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-tr/02-strings.xml
@@ -29,7 +29,7 @@
Not kaydedildi
Kart destesi:
- Deste:
+ Deste:
Tür:
Etiketler: %1$s
Kartlar: %1$s
@@ -220,6 +220,8 @@
Yazı tipi boyutu
Araç çubuğunu göster
+ Show preview
+ Hide preview
Kalın olarak şekillendir
İtalik olarak şekillendir
Altı çizili olarak şekillendir
diff --git a/AnkiDroid/src/main/res/values-tr/03-dialogs.xml b/AnkiDroid/src/main/res/values-tr/03-dialogs.xml
index 32d789138b6c..f0f510a169f8 100644
--- a/AnkiDroid/src/main/res/values-tr/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-tr/03-dialogs.xml
@@ -49,7 +49,7 @@
Bugünün gözden geçirme sınırını şu kadar arttırın/azaltın (“-3”)
Son x gündeki unutulan kartları izle:
Şu kadar zaman içinde sırası gelecek kartları çalış:
- Son x günlerdeki eklenen yeni kartları önizle:
+ Preview new cards added in the last:
Desteden x kart seç:
Depolama alanı dolu
Depolama alanı nerdeyse dolu
@@ -78,6 +78,7 @@
Üzerine Yaz
Onar
Değiştir
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Belirtilen konum geçerli bir dizin değildi.
Verilen metin geçerli bir PEM-kodlanmış X509 sertifikasına çözümlenmiyor
Sertifika çözümlemesinde hata
@@ -86,7 +87,7 @@
Bu, büyük medya koleksiyonlarıyla uzun zaman alabilir. Devam edilsin mi?
Medya kontrol ediliyor…
Etiketler ekleniyor…
- Etiketleme sonuçları
+ Tags added
Medya siliniyor…
Silme sonuçları
diff --git a/AnkiDroid/src/main/res/values-tr/10-preferences.xml b/AnkiDroid/src/main/res/values-tr/10-preferences.xml
index c06f2a442f63..c3205ec3c8fb 100644
--- a/AnkiDroid/src/main/res/values-tr/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-tr/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Devredışı
+ Enabled
+ Devredışı
- %d dk
- %d dk
@@ -72,7 +73,7 @@
Kart gözatıcısı soru/cevap alanlarında ses dosyası isimlerini göster
AnkiDroid dizini
Tam ekran modu
- Kapalı
+ Kapalı
Sistem çubuklarını gizle
Sistem çubuklarını ve cevap butonlarını gizle
Cevap düğmelerinin yeri
@@ -217,11 +218,80 @@
Navigasyon
Medya
Diğer
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Hatırlatıcı ekle
- Çalışma hatırlatıcısı ekle
- Çalışma hatırlatıcısını değiştir
Çalışma hatırlatıcınız yok
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Çalışma hatırlatıcısı ekle
+ Çalışma hatırlatıcısını değiştir
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Etiketleri Seçin
@@ -370,9 +440,15 @@
geri bildiriminizi veya bulduğunuz hataları bildirin.]]>
Cevap geribildirimini göster
Ekran
- Sadece cevap ver
- Çevir ve cevap ver
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Ayarları aç
İlgili ayarlar
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-tt/02-strings.xml b/AnkiDroid/src/main/res/values-tt/02-strings.xml
index c945d3858dd5..96876eb71813 100644
--- a/AnkiDroid/src/main/res/values-tt/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-tt/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
Кәрт колодасы:
- Колода:
+ Колода:
Төр:
Тамгалар: %1$s
Кәртләр: %1$s
@@ -218,6 +218,8 @@
Шрифт үлчәме
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml
index db1a3cbdd300..e6bcc66b3096 100644
--- a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml
@@ -47,7 +47,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
Storage almost full
@@ -76,6 +76,7 @@
Яңадан язу
Төзәтү
Алмаштыру
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
Error parsing certificate
@@ -84,7 +85,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values-tt/10-preferences.xml b/AnkiDroid/src/main/res/values-tt/10-preferences.xml
index 0fa15a5f122f..302838d3284c 100644
--- a/AnkiDroid/src/main/res/values-tt/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-tt/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Disabled
+ Enabled
+ Disabled
- %d mins
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid directory
Fullscreen mode
- Сүнгән
+ Сүнгән
Hide the system bars
Hide the system bars and answer buttons
Answer buttons position
@@ -217,11 +218,79 @@
Навигация
Медиа
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
Select tags
@@ -370,9 +439,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ug/02-strings.xml b/AnkiDroid/src/main/res/values-ug/02-strings.xml
index 1273edf9b435..3c3dfc822322 100644
--- a/AnkiDroid/src/main/res/values-ug/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ug/02-strings.xml
@@ -29,7 +29,7 @@
خاتىرە ساقلاندى
كارتا دەستىسى:
- دەستە:
+ دەستە:
تۈرى:
بەلگە: %1$s
كارتا: %1$s
@@ -220,6 +220,8 @@
خەت چوڭلۇقى
قورال بالداقنى كۆرسەت
+ Show preview
+ Hide preview
توم پىچىم
يانتۇ پىچىم
ئاستى سىزىق پىچىم
diff --git a/AnkiDroid/src/main/res/values-ug/03-dialogs.xml b/AnkiDroid/src/main/res/values-ug/03-dialogs.xml
index a295733b7fb0..601708e3a98d 100644
--- a/AnkiDroid/src/main/res/values-ug/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ug/03-dialogs.xml
@@ -49,7 +49,7 @@
بۈگۈنكى تەكرارلايدىغان كارتا چېكىنى («3-»)ئاشۇرىدۇ/كېمەيتىدۇ
ئۆتكەن x كۈندە ئۇنتۇلغان كارتىنى تەكرارلايدۇ:
كېيىنكى كارتىنى تەكرارلاش مۆھلىتى:
- يېقىنقى x كۈندە قوشقان يېڭى كارتىنى ئالدىن كۆزىتىدۇ:
+ Preview new cards added in the last:
دەستىدىن x كارتا تاللىنىدۇ:
ساقلىغۇچ لىق
ساقلىغۇچ ئاساسەن لىق
@@ -78,6 +78,7 @@
قاپلا
تۈزەت
ئالماشتۇر
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
بەلگىلەنگەن يول ئىناۋەتلىك مۇندەرىجە ئەمەس
تەمىنلەنگەن تېكىستنى ئىناۋەتلىك PEM كودىنىڭ X509 گۇۋاھنامىسىنى بىر تەرەپ قىلالمىدى
گۇۋاھنامە يېشىش خاتالىقى
@@ -86,7 +87,7 @@
چوڭ ۋاسىتە توپلىمىغا ئۇزۇن ۋاقىت كېتىشى مۇمكىن. داۋاملاشتۇرامدۇ؟
ۋاسىتە تەكشۈرۈۋاتىدۇ…
بەلگە قوش…
- بەلگە قويۇش نەتىجىسى
+ Tags added
ۋاسىتە ئۆچۈرۈۋاتىدۇ…
ئۆچۈرۈش نەتىجىسى
diff --git a/AnkiDroid/src/main/res/values-ug/10-preferences.xml b/AnkiDroid/src/main/res/values-ug/10-preferences.xml
index 203da91ef4de..8c590afe6a10 100644
--- a/AnkiDroid/src/main/res/values-ug/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ug/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- چەكلەنگەن
+ Enabled
+ چەكلەنگەن
- %d مىنۇت
- %d مىنۇت
@@ -72,7 +73,7 @@
كارتا كۆرگۈچتىكى سوئال/جاۋاب بۆلىكىدە ئاۋاز ھۆججەت ئاتىنى كۆرسىتىدۇ
AnkiDroid مۇندەرىجىسى
پۈتۈن ئېكران ھالىتى
- تاقاق
+ تاقاق
سىستېما بالداقنى يوشۇر
سىستېما بالداق ۋە جاۋاب توپچەنى يوشۇرىدۇ
جاۋاب توپچەنىڭ ئورنى
@@ -219,11 +220,80 @@
يولباشچى
ۋاسىتە
ئارىلاش
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
ئەسكەرتىش قوش
- تەكرارلاش ئەسكەرتىشى قوش
- تەكرارلاش ئەسكەرتىشى تەھرىر
تەكرارلاش ئەسكەرتىشىڭىز يوق
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ تەكرارلاش ئەسكەرتىشى قوش
+ تەكرارلاش ئەسكەرتىشى تەھرىر
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
بەلگە تاللىنىدۇ
@@ -373,9 +443,15 @@
قايتۇرما ئىنكاس ياكى كەمتۈك دوكلاتى يوللاڭ.]]>
جاۋاب قايتۇرما ئىنكاسىنى كۆرسىتىدۇ
ئېكران
- جاۋابلا
- ئۆرۈ ۋە جاۋاب
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
تەڭشەكنى ئاچ
مۇناسىۋەتلىك تەڭشەك
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-uk/02-strings.xml b/AnkiDroid/src/main/res/values-uk/02-strings.xml
index ac7809bf5edc..35034b83cd25 100644
--- a/AnkiDroid/src/main/res/values-uk/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-uk/02-strings.xml
@@ -29,7 +29,7 @@
Запис збережено
Колода карток:
- Колода:
+ Колода:
Тип:
Теги: %1$s
Картки: %1$s
@@ -226,6 +226,8 @@
Розмір шрифту
Показувати панель інструментів
+ Show preview
+ Hide preview
Форматувати як жирний
Форматувати як курсив
Форматувати як підкреслений
diff --git a/AnkiDroid/src/main/res/values-uk/03-dialogs.xml b/AnkiDroid/src/main/res/values-uk/03-dialogs.xml
index 6ffdb626bf97..5b63e69f28bb 100644
--- a/AnkiDroid/src/main/res/values-uk/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-uk/03-dialogs.xml
@@ -53,7 +53,7 @@
Збільшення/зменшення («-3») сьогоднішнього ліміту на повторення
Повторити картки забуті за останні x днів:
Review cards due in the next:
- Переглянути нові картки додані за останні x днів:
+ Preview new cards added in the last:
Вибрати x карток з колоди:
Сховище заповнено
Сховище майже заповнено
@@ -82,6 +82,7 @@
Перезаписати
Виправити
Замінити
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Вказаний шлях не є дійсним
Наданий текст не перетворюється на дійсний сертифікат X509 з PEM-кодуванням
Помилка парсингу сертифікату
@@ -90,7 +91,7 @@
У випадку великих медіа-колекцій це може забрати багато часу. Продовжити?
Перевіряємо мультимедіа…
Додавання тегів…
- Tagging results
+ Tags added
Видалення медіа…
Результат видалення
diff --git a/AnkiDroid/src/main/res/values-uk/10-preferences.xml b/AnkiDroid/src/main/res/values-uk/10-preferences.xml
index 9324663d56e6..2da28a4fc07d 100644
--- a/AnkiDroid/src/main/res/values-uk/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-uk/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Вимкнено
+ Enabled
+ Вимкнено
- %d хв.
- %d хв.
@@ -74,7 +75,7 @@
Показувати назви аудіофайлів в переглядачі карток в полях запитання/відповіді
Шлях до папки AnkiDroid
Повноекранний режим
- Вимкнено
+ Вимкнено
Приховати системну панель
Приховати системну панель і панель кнопок відповіді
Положення кнопок відповіді
@@ -222,11 +223,82 @@
Навігація
Медіафайли
Різне
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Додати нагадування
- Додати нагадування про навчання
- Редагувати нагадування про навчання
У вас немає нагадувань про навчання
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Додати нагадування про навчання
+ Редагувати нагадування про навчання
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+ - %d cards due
+ - %d cards due
+
Оберіть теги
@@ -378,9 +450,15 @@
відгуки або помилки.]]>
Показувати фідбек відповіді
Екран
- Лише відповідь
- Перевернути та відповісти
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Відкрити налаштування
Пов\'язані налаштування
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-ur/02-strings.xml b/AnkiDroid/src/main/res/values-ur/02-strings.xml
index 6f24fc696048..9d053df4b36a 100644
--- a/AnkiDroid/src/main/res/values-ur/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-ur/02-strings.xml
@@ -29,7 +29,7 @@
Note saved
کارڈ ڈیک:
- ڈیک:
+ ڈیک:
قسم:
ٹیگز: %1$s
کارڈ: %1$s
@@ -221,6 +221,8 @@
حرف کا سائز
ٹول بار دکھائیں
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-ur/03-dialogs.xml b/AnkiDroid/src/main/res/values-ur/03-dialogs.xml
index 4769c796da3b..161fa7a60e06 100644
--- a/AnkiDroid/src/main/res/values-ur/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-ur/03-dialogs.xml
@@ -49,7 +49,7 @@
آج کی نظرثانی کی حد میں اضافہ/کمی (\"-3\")
پچھلے دنوں میں بھولے گئے کارڈز کا جائزہ لیں:
Review cards due in the next:
- پچھلے x دنوں میں شامل کیے گئے نئے کارڈز کا جائزہ لیں:
+ Preview new cards added in the last:
Select x cards from the deck:
اسٹوریج بھرا ہوا ہے
اسٹوریج تقریباً بھر گیا ہے
@@ -78,6 +78,7 @@
اووررائٹ
مرمت
بدل دیں
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
بیان کردہ راستہ درست ڈائریکٹری نہیں تھی
فراہم کردہ متن ایک درست PEM-انکوڈ شدہ X509 سرٹیفکیٹ کو حل نہیں کرتا ہے
سرٹیفکیٹ کو پارس کرنے میں خرابی
@@ -86,7 +87,7 @@
This may take a long time with large media collections. Proceed?
میڈیا کو چیک کیا جا رہا ہے…
Adding tags…
- Tagging results
+ Tags added
میڈیا کو حذف کیا جا رہا ہے…
حذف کرنے کا نتیجہ
diff --git a/AnkiDroid/src/main/res/values-ur/10-preferences.xml b/AnkiDroid/src/main/res/values-ur/10-preferences.xml
index c14ae771dc94..907231caca1b 100644
--- a/AnkiDroid/src/main/res/values-ur/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-ur/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- غیر فعال
+ Enabled
+ غیر فعال
- %d منٹ
- %d منٹ
@@ -72,7 +73,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid ڈائریکٹری
فل سکرین موڈ
- بند
+ بند
سسٹم کی سلاخوں کو چھپائیں
سسٹم بارز اور جوابی بٹن چھپائیں
جواب بٹن کی پوزیشن
@@ -219,11 +220,80 @@
Navigation
Media
Misc
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Select tags
@@ -373,9 +443,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-uz/02-strings.xml b/AnkiDroid/src/main/res/values-uz/02-strings.xml
index 11f164e1e348..5b779bc38ada 100644
--- a/AnkiDroid/src/main/res/values-uz/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-uz/02-strings.xml
@@ -29,7 +29,7 @@
Qayt saqlandi
Karta dastai:
- Dasta:
+ Dasta:
Turi:
Teglar: %1$s
Kartalar: %1$s
@@ -220,6 +220,8 @@
Shrift oʻlchami
Asboblar panelini koʻrsatish
+ Show preview
+ Hide preview
Qalin qilib formatlash
Kursiv sifatida formatlash
Tagiga chiziq tortib formatlash
diff --git a/AnkiDroid/src/main/res/values-uz/03-dialogs.xml b/AnkiDroid/src/main/res/values-uz/03-dialogs.xml
index c7b17bcf3b62..f882da29787d 100644
--- a/AnkiDroid/src/main/res/values-uz/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-uz/03-dialogs.xml
@@ -49,7 +49,7 @@
Bugungi takrorlanadigan kartalar limitini oshirish/kamaytirish (“-3”)
Oʻtgan x kun ichida unutilgan kartalarni takrorlash:
Review cards due in the next:
- Oxirgi x kunda qoʻshilgan yangi kartalarni koʻrib chiqish:
+ Preview new cards added in the last:
Dastadan x ta karta tanlash:
Saqlash xotirasi toʻla
Saqlash xotirasi deyarli toʻla
@@ -78,6 +78,7 @@
Ustiga yozish
Tuzatish
Almashtirish
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Koʻrsatilgan yoʻl yaroqli katalog emas
Taʼminlangan matn yaroqli PEM bilan kodlangan X509 sertifikatiga mos kelmaydi
Sertifikatni tekshirishda xatolik
@@ -86,7 +87,7 @@
Juda katta media kolleksiya uchun uzoq vaqt olishi mumkin. Davom etmoqchimisiz?
Media fayllar tekshirilmoqda…
Teglar qoʻshilmoqda
- Tagging results
+ Tags added
Media fayllar oʻchirilmoqda…
Oʻchirish natijasi
diff --git a/AnkiDroid/src/main/res/values-uz/10-preferences.xml b/AnkiDroid/src/main/res/values-uz/10-preferences.xml
index e0c9473ae575..50c6218ffd86 100644
--- a/AnkiDroid/src/main/res/values-uz/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-uz/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Oʻchirilgan
+ Enabled
+ Oʻchirilgan
- %d daqiqa
- %d daqiqa
@@ -72,7 +73,7 @@
Audio fayl nomlarini karta brauzeri savol/javob maydonlarida ko\'rsatish
AnkiDroid katalogi
Butun ekran rejimi
- Oʻchiq
+ Oʻchiq
Tizim panellarini yashirish
Tizim panellarini va javob tugmalarini yashirish
Javob tugmalari joylashuvi
@@ -217,11 +218,80 @@
Navigatsiya
Media fayllar
Boshqa
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Bildirishnoma qoʻshish
- Takrorlash bildirishnomasini qoʻshish
- Takrorlash bildirishnomasini tahrirlash
Siza hech qanday takrorlash bildirishnomalari yoʻq
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Takrorlash bildirishnomasini qoʻshish
+ Takrorlash bildirishnomasini tahrirlash
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d card due
+ - %d cards due
+
Teglarni tanlash
@@ -369,9 +439,15 @@
fikr-mulohazalaringiz yoki baglar haqida xabar bering.]]>
Javob firk-mulohazalarini koʻrsatish
Ekran
- Faqat javob
- Oʻgirish va javob berish
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Sozlamalarni ochish
Aloqador sozlamalar
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-vi/02-strings.xml b/AnkiDroid/src/main/res/values-vi/02-strings.xml
index 7fca57c6a95a..cc3ed11f69e8 100644
--- a/AnkiDroid/src/main/res/values-vi/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-vi/02-strings.xml
@@ -29,7 +29,7 @@
Ghi chú đã được lưu
Bộ thẻ:
- Bộ:
+ Bộ:
Loại:
Các đuôi: %1$s
Các thẻ: %1$s
@@ -217,6 +217,8 @@
Cỡ chữ
Hiển thị thanh công cụ
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-vi/03-dialogs.xml b/AnkiDroid/src/main/res/values-vi/03-dialogs.xml
index c9dedf448d5a..f9a5fdbc93f6 100644
--- a/AnkiDroid/src/main/res/values-vi/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-vi/03-dialogs.xml
@@ -47,7 +47,7 @@
Tăng / giảm (“-3”) giới hạn ôn tập của ngày hôm nay lên
Ôn lại các thẻ đã quên trong x ngày trước:
Review cards due in the next:
- Xem trước thẻ mới được thêm vào x ngày trước:
+ Preview new cards added in the last:
Chọn x thẻ từ bộ thẻ:
Bộ nhớ đầy
Bộ nhớ gần đầy
@@ -76,6 +76,7 @@
Ghi đè
Sửa chữa
Thay thế
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
Đường dẫn đã chỉ định không phải thư mục hợp lệ
Dòng văn bản được cung cấp không phải là chứng chỉ X509 được mã hóa PEM hợp lệ
Lỗi phân tích chứng chỉ
@@ -84,7 +85,7 @@
This may take a long time with large media collections. Proceed?
Đang kiểm tra tệp đa phương tiện…
Adding tags…
- Tagging results
+ Tags added
Đang xóa phương tiện…
Kết quả xóa
diff --git a/AnkiDroid/src/main/res/values-vi/10-preferences.xml b/AnkiDroid/src/main/res/values-vi/10-preferences.xml
index 3d9951c0872a..febb090715c3 100644
--- a/AnkiDroid/src/main/res/values-vi/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-vi/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- Đã tắt
+ Enabled
+ Đã tắt
- %d phút
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
Thư mục AnkiDroid
Chế độ toàn màn hình
- Tắt
+ Tắt
Ẩn các thanh hệ thống
Ẩn các thanh hệ thống và các nút trả lời
Vị trí các nút trả lời
@@ -214,11 +215,79 @@
Điều hướng
Đa phương tiện
Hỗn hợp
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Thêm lời nhắc
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
Chọn thẻ
@@ -366,9 +435,15 @@
phản hồi hoặc báo lỗi nếu có.]]>
Show answer feedback
Screen
- Chỉ trả lời
- Lật và trả lời
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Mở cài đặt
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml b/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml
index 87492a198afa..6501ecb6deae 100644
--- a/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml
@@ -29,7 +29,7 @@
已保存附注
牌组:
- 牌组:
+ 牌组:
笔记模板:
标签:%1$s
卡片:%1$s
@@ -217,6 +217,8 @@
字体大小
显示工具栏
+ Show preview
+ Hide preview
粗体格式
斜体格式
下划线格式
diff --git a/AnkiDroid/src/main/res/values-zh-rCN/03-dialogs.xml b/AnkiDroid/src/main/res/values-zh-rCN/03-dialogs.xml
index 13603d846049..9e7d4be1fbbb 100644
--- a/AnkiDroid/src/main/res/values-zh-rCN/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-zh-rCN/03-dialogs.xml
@@ -47,7 +47,7 @@
增加/减少(“-3”)今天的复习卡片限制
复习过去 x 天中忘记的卡片:
复习接下去多久到期的卡片:
- 预览过去 x 天中新增的卡片:
+ Preview new cards added in the last:
从牌组中选择 x 张卡片:
存储空间已满
存储空间快满了
@@ -76,6 +76,7 @@
覆盖
修复
替换
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
指定的路径不是一个有效的目录
提供的文本无法解析为有效的 PEM 编码的 X509 证书
解析证书时发生错误
@@ -84,7 +85,7 @@
对于大型媒体集合,这可能需要很长时间。继续吗?
正在检查媒体文件…
正在添加标签…
- 标记结果
+ Tags added
正在删除媒体…
填空结果
diff --git a/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml b/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml
index 7d487a11c9ab..a89e5a4598e9 100644
--- a/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- 禁用
+ Enabled
+ 禁用
- %d 分钟
@@ -71,7 +72,7 @@
在卡牌浏览器问题/回答字段中显示音频文件名
AnkiDroid 路径
全屏显示
- 关闭
+ 关闭
隐藏系统状态栏
隐藏系统状态栏和回答按钮
显示答案按钮位置
@@ -216,11 +217,79 @@
导航
多媒体
杂项
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
添加提醒
- 添加复习提醒
- 编辑复习提醒
您没有复习提醒
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ 添加复习提醒
+ 编辑复习提醒
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
选择标签
@@ -369,9 +438,15 @@
反馈或故障。]]>
显示回答反馈
屏幕
- 仅回答
- 翻转和回答
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
打开设置
相关设置
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml b/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml
index fd9ebb048ee4..923f7ada4718 100644
--- a/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml
+++ b/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml
@@ -29,7 +29,7 @@
筆記已儲存
卡片牌組:
- 牌組:
+ 牌組:
種類:
標籤: %1$s
卡片:%1$s
@@ -217,6 +217,8 @@
字型大小
顯示工具列
+ Show preview
+ Hide preview
Format as bold
Format as italic
Format as underline
diff --git a/AnkiDroid/src/main/res/values-zh-rTW/03-dialogs.xml b/AnkiDroid/src/main/res/values-zh-rTW/03-dialogs.xml
index d0a701db248e..45cc2408ed22 100644
--- a/AnkiDroid/src/main/res/values-zh-rTW/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values-zh-rTW/03-dialogs.xml
@@ -47,7 +47,7 @@
將今天的複習卡片上限新增/減少(“-3”)
過去 x 天內忘記的複習卡:
Review cards due in the next:
- 預覽過去 x 天新增的卡片:
+ Preview new cards added in the last:
在牌組中選擇 x 張卡
存儲已滿
存儲空間幾乎滿了
@@ -76,6 +76,7 @@
覆蓋
修復
取代
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
所選的路徑不是有效路徑
提供的文字無法解析為有效的 PEM 編碼 X509 憑證
證書解析錯誤
@@ -84,7 +85,7 @@
由於較大的媒體檔案,這可能會花比較久的時間。確定繼續?
正在檢查媒體…
添加標籤…
- Tagging results
+ Tags added
刪除媒體中…
克漏字結果
diff --git a/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml b/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml
index c77ee9a51afe..f76a4c218665 100644
--- a/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml
@@ -24,7 +24,8 @@
-->
- 已停用
+ Enabled
+ 已停用
- %d分鐘
@@ -71,7 +72,7 @@
Display audio filenames in the card browser question/answer fields
AnkiDroid 路徑
全螢幕模式
- 關閉
+ 關閉
隱藏系統狀態欄
隱藏系統狀態欄和回答按鈕
顯示答案按鈕位置
@@ -216,11 +217,79 @@
瀏覽
媒體
雜項
-
+
+
+ Review reminders
+ App-wide
+ Deck-specific
+ An error occurred while loading your review reminders, corrupted reminders have been deleted. Details:
+
+ Schedule reminders
Add reminder
- Add review reminder
- Edit review reminder
You have no review reminders
+ Troubleshoot
+ Notification settings
+ List of scheduled review reminders
+ Deck not found
+ Reminders are unavailable
+ Fix
+ Successfully added new review reminder
+ Successfully deleted review reminder
+ Successfully edited review reminder
+
+ Add review reminder
+ Edit review reminder
+ Time:
+ Deck:
+ Advanced settings
+ Card threshold:
+ Do not send me notifications if there are less than this many cards due
+ Only notify me if no reviews have been done today
+ Please enter a whole number of cards
+ The threshold must be at least 0
+ Delete this reminder?
+ This action cannot be undone.
+
+ Reminders are unavailable.
+ Reminders may not work correctly.
+ Your reminders should work as expected.
+ Different devices have different notification restriction settings. Check your system settings or search online if issues persist.
+
+ Notification permission
+ Notification channel
+ Do not disturb
+ Battery optimization
+ Power saving mode
+ Alarms & reminders permission
+
+ Granted
+ Denied
+ On
+ Off
+ Unrestricted
+ Optimized
+ Restricted
+
+ The review reminder notification channel must be enabled
+ Do Not Disturb may mute reminder notifications
+ Battery optimization may delay reminders
+ Background usage is disabled. Reminders may not be delivered
+ Power saving mode may prevent timely delivery of reminders
+ Required to schedule reminders at exact times
+
+ Grant permission
+ Enable notification channel
+ Disable battery optimization
+ Open battery settings
+
+ It\'s time to study your cards
+ It\'s time to study \"%s\"
+ Snooze %s
+
+ - %d cards due
+
選擇標籤
@@ -369,9 +438,15 @@
feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/values/02-strings.xml b/AnkiDroid/src/main/res/values/02-strings.xml
index 90f9c52bbcfc..271234bce2d1 100644
--- a/AnkiDroid/src/main/res/values/02-strings.xml
+++ b/AnkiDroid/src/main/res/values/02-strings.xml
@@ -241,6 +241,8 @@
Font size
Show toolbar
+ Show preview
+ Hide preview
Format as bold
Format as italic
diff --git a/AnkiDroid/src/main/res/values/03-dialogs.xml b/AnkiDroid/src/main/res/values/03-dialogs.xml
index 732873170dd7..d028635b97ae 100644
--- a/AnkiDroid/src/main/res/values/03-dialogs.xml
+++ b/AnkiDroid/src/main/res/values/03-dialogs.xml
@@ -37,7 +37,7 @@
Increase/decrease (“-3”) today’s review limit by
Review cards forgotten in last x days:
Review cards due in the next:
- Preview new cards added in the last x days:
+ Preview new cards added in the last:
Select x cards from the deck:
Storage full
@@ -69,6 +69,7 @@
Overwrite
Repair
Replace
+ %1$s\n\nCorrect the date and time, or enable automatic date and time, then sync again.\n\nYour device’s current time is: %2$s
The path specified wasn’t a valid directory
The provided text does not resolve to a valid PEM-encoded X509 certificate
@@ -79,7 +80,7 @@
This may take a long time with large media collections. Proceed?
Checking media…
Adding tags…
- Tagging results
+ Tags added
Deleting media…
Deletion result
diff --git a/AnkiDroid/src/main/res/values/10-preferences.xml b/AnkiDroid/src/main/res/values/10-preferences.xml
index 4a052bc521d8..0be7067c3945 100644
--- a/AnkiDroid/src/main/res/values/10-preferences.xml
+++ b/AnkiDroid/src/main/res/values/10-preferences.xml
@@ -311,7 +311,7 @@
Replace newlines with HTML
In the note editor, convert any instances of <br> to newlines when editing a card.
-
+
Focus ‘type in answer’
Automatically focus the ‘type in answer’ field in the study screen
@@ -539,12 +539,20 @@ this formatter is used if the bind only applies to the answer">A: %s
>feedback or bugs.]]>
Show answer feedback
Screen
- Only answer
- Flip and answer
+ Also assign to ‘%1$s’?
+ Skip
+ Assign
Open settings
Related settings
+
+
+ Not set
+ Custom path
+ Enter custom path
+ Directory is not writable
diff --git a/AnkiDroid/src/main/res/xml/preferences_advanced.xml b/AnkiDroid/src/main/res/xml/preferences_advanced.xml
index e27c5c88fbc0..99172960d890 100644
--- a/AnkiDroid/src/main/res/xml/preferences_advanced.xml
+++ b/AnkiDroid/src/main/res/xml/preferences_advanced.xml
@@ -11,14 +11,12 @@
-
+ android:title="@string/col_path"/>
().baseContext
+
+ private val profileRegistry: SharedPreferences
+ get() = frameworkBase.getSharedPreferences(PROFILE_REGISTRY_FILENAME, Context.MODE_PRIVATE)
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
@Test
fun `terminating application unregisters its lifecycle observer`() {
val application = ApplicationProvider.getApplicationContext()
@@ -46,4 +71,62 @@ class AnkiDroidAppTest {
//noinspection ConstantConditions
assertDoesNotThrow { sendExceptionReport(message, "AnkiDroidAppTest") }
}
+
+ @Test
+ fun `application keeps the framework base context`() {
+ val app = ApplicationProvider.getApplicationContext()
+
+ assertNull(ProfileManager.attachError)
+ assertEquals(
+ "android.app.ContextImpl",
+ app.baseContext.javaClass.name,
+ "ActivityThread.handleReceiver casts the application's base context to ContextImpl",
+ )
+ }
+
+ @Test
+ fun `storage follows a non-default profile`() {
+ profileRegistry.edit(commit = true) { putString(KEY_LAST_ACTIVE_PROFILE_ID, "p_routed") }
+
+ val app = startApplication()
+ app.getSharedPreferences("settings", Context.MODE_PRIVATE).edit(commit = true) {
+ putString("key", "profile value")
+ }
+
+ val profileDir = File(frameworkBase.dataDir, "p_routed")
+ assertTrue(File(frameworkBase.dataDir, "shared_prefs/profile_p_routed_settings.xml").exists())
+ listOf(
+ app.filesDir,
+ app.cacheDir,
+ app.codeCacheDir,
+ app.noBackupFilesDir,
+ app.getDatabasePath("collection.db"),
+ app.getDir("textures", Context.MODE_PRIVATE),
+ ).forEach { assertTrue("$it is outside $profileDir", it.startsWith(profileDir)) }
+ }
+
+ @Test
+ fun `ACRA process skips the profile environment`() {
+ mockkStatic(::isAcraSenderProcess)
+ every { isAcraSenderProcess() } returns true
+ profileRegistry.edit(commit = true) { clear() }
+
+ val app = startApplication()
+
+ assertNull(profileRegistry.getString(KEY_LAST_ACTIVE_PROFILE_ID, null))
+ assertEquals(frameworkBase.filesDir, app.filesDir)
+ }
+
+ private fun startApplication(): AnkiDroidApp =
+ AttachableApp().apply {
+ attach(
+ object : ContextWrapper(frameworkBase) {
+ override fun getApplicationContext(): Context? = null
+ },
+ )
+ }
+
+ private class AttachableApp : AnkiDroidApp() {
+ fun attach(base: Context) = attachBaseContext(base)
+ }
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/BottomNavigationAnalyticsTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/BottomNavigationAnalyticsTest.kt
new file mode 100644
index 000000000000..d3dd57da9d3b
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/BottomNavigationAnalyticsTest.kt
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: Copyright (c) 2026 Ashish Yadav
+
+package com.ichi2.anki
+
+import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.BottomNavController.NavigationItem
+import com.ichi2.anki.common.analytics.Analytics
+import com.ichi2.testutils.withBooleanPreference
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockkObject
+import io.mockk.runs
+import io.mockk.unmockkObject
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.empty
+import org.hamcrest.Matchers.equalTo
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.android.controller.ActivityController
+
+@RunWith(AndroidJUnit4::class)
+class BottomNavigationAnalyticsTest : RobolectricTest() {
+ private val screenViews = mutableListOf()
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+ mockkObject(Analytics)
+ every { Analytics.sendAnalyticsScreenView(capture(screenViews)) } just runs
+ }
+
+ @After
+ override fun tearDown() {
+ super.tearDown()
+ unmockkObject(Analytics)
+ }
+
+ @Test
+ fun `each tab is reported under its screen name`() =
+ withBottomNavigation {
+ mapOf(
+ NavigationItem.BROWSER to "CardBrowser",
+ NavigationItem.STATS to "Statistics",
+ NavigationItem.MORE to "MoreFragment",
+ NavigationItem.HOME to "DeckPicker",
+ ).forEach { (tab, screenName) ->
+ assertThat(screenViewsDuring { select(tab) }, equalTo(listOf(screenName)))
+ }
+ }
+
+ @Test
+ fun `selecting the open tab again is not reported`() =
+ withBottomNavigation {
+ select(NavigationItem.BROWSER)
+
+ assertThat(screenViewsDuring { select(NavigationItem.BROWSER) }, empty())
+ }
+
+ @Test
+ fun `resuming reports the open tab, not the deck picker`() =
+ withBottomNavigation {
+ select(NavigationItem.BROWSER)
+
+ assertThat(screenViewsDuring { pause().resume() }, equalTo(listOf("CardBrowser")))
+ }
+
+ @Test
+ fun `recreating on a tab reports it once`() =
+ withBottomNavigation {
+ select(NavigationItem.BROWSER)
+
+ assertThat(screenViewsDuring { recreate() }, equalTo(listOf("CardBrowser")))
+ }
+
+ private fun withBottomNavigation(test: ActivityController.() -> Unit) =
+ withBooleanPreference(R.string.dev_bottom_nav_key, true) {
+ setIntroductionSlidesShown(true)
+ startActivityControllerNormallyOpenCollectionWithIntent(DeckPicker::class.java, Intent()).test()
+ }
+
+ private fun screenViewsDuring(action: () -> Unit): List {
+ screenViews.clear()
+ action()
+ return screenViews.toList()
+ }
+
+ private fun ActivityController.select(tab: NavigationItem) {
+ get().binding.bottomNavigation!!.selectedItemId = tab.id
+ advanceRobolectricLooper()
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.kt
index 74654b969e72..6d430cfa4bf8 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.kt
@@ -1762,6 +1762,7 @@ class CardBrowserTest : RobolectricTest() {
R.id.action_capitalize to true,
R.id.action_show_toolbar to true,
R.id.action_scroll_toolbar to true,
+ R.id.action_toggle_previewer to false,
)
assertMenusEqual(expectedMenuItems, menu)
@@ -1805,6 +1806,7 @@ class CardBrowserTest : RobolectricTest() {
R.id.action_capitalize to true,
R.id.action_show_toolbar to true,
R.id.action_scroll_toolbar to true,
+ R.id.action_toggle_previewer to false,
)
assertMenusEqual(expectedMenuItems, menu)
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/CardTemplateEditorTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/CardTemplateEditorTest.kt
index 1466d178d9a7..c47a942a5ce6 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/CardTemplateEditorTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/CardTemplateEditorTest.kt
@@ -225,7 +225,7 @@ class CardTemplateEditorTest : RobolectricTest() {
advanceRobolectricLooper()
assertEquals(
"Did not show dialog about deleting only card?",
- getResourceString(R.string.card_template_editor_cant_delete),
+ getResourceString(CommonString.card_template_editor_cant_delete),
getAlertDialogText(true),
)
assertEquals(
@@ -373,7 +373,7 @@ class CardTemplateEditorTest : RobolectricTest() {
advanceRobolectricLooper()
assertEquals(
"Did not show dialog about deleting only card?",
- getResourceString(R.string.orphan_note_message),
+ getResourceString(CommonString.orphan_note_message),
getAlertDialogText(true),
)
clickAlertDialogButton(DialogInterface.BUTTON_POSITIVE, true)
@@ -459,7 +459,7 @@ class CardTemplateEditorTest : RobolectricTest() {
advanceRobolectricLooper()
assertEquals(
"Did not show dialog about deleting template and it's card?",
- getQuantityString(R.plurals.card_template_editor_confirm_delete, 1, 1, "Card 1"),
+ getQuantityString(CommonPlurals.card_template_editor_confirm_delete, 1, 1, "Card 1"),
getAlertDialogText(true),
)
clickAlertDialogButton(DialogInterface.BUTTON_NEGATIVE, true)
@@ -539,7 +539,7 @@ class CardTemplateEditorTest : RobolectricTest() {
advanceRobolectricLooper()
assertEquals(
"Did not show dialog about deleting template and it's card?",
- getQuantityString(R.plurals.card_template_editor_confirm_delete, 1, 1, "Card 1"),
+ getQuantityString(CommonPlurals.card_template_editor_confirm_delete, 1, 1, "Card 1"),
getAlertDialogText(true),
)
clickAlertDialogButton(DialogInterface.BUTTON_POSITIVE, true)
@@ -550,7 +550,7 @@ class CardTemplateEditorTest : RobolectricTest() {
advanceRobolectricLooper()
assertEquals(
"Did not show dialog about deleting template and it's card?",
- getQuantityString(R.plurals.card_template_editor_confirm_delete, 1, 1, "Card 2"),
+ getQuantityString(CommonPlurals.card_template_editor_confirm_delete, 1, 1, "Card 2"),
getAlertDialogText(true),
)
clickAlertDialogButton(DialogInterface.BUTTON_POSITIVE, true)
@@ -641,7 +641,7 @@ class CardTemplateEditorTest : RobolectricTest() {
advanceRobolectricLooper()
assertEquals(
"Did not show dialog about deleting template and it's card?",
- getQuantityString(R.plurals.card_template_editor_confirm_delete, 1, 1, "Card 2"),
+ getQuantityString(CommonPlurals.card_template_editor_confirm_delete, 1, 1, "Card 2"),
getAlertDialogText(true),
)
clickAlertDialogButton(DialogInterface.BUTTON_POSITIVE, true)
@@ -675,7 +675,7 @@ class CardTemplateEditorTest : RobolectricTest() {
advanceRobolectricLooper()
assertEquals(
"Did not show dialog about deleting template and it's card?",
- getQuantityString(R.plurals.card_template_editor_confirm_delete, 0, 0, CollectionManager.TR.cardTemplatesCard(2)),
+ getQuantityString(CommonPlurals.card_template_editor_confirm_delete, 0, 0, CollectionManager.TR.cardTemplatesCard(2)),
getAlertDialogText(true),
)
clickAlertDialogButton(DialogInterface.BUTTON_POSITIVE, true)
@@ -1158,7 +1158,7 @@ class CardTemplateEditorTest : RobolectricTest() {
}
assertEquals(
"Did not show dialog about adding template and it's card?",
- getQuantityString(R.plurals.card_template_editor_confirm_add, numAffectedCards, numAffectedCards),
+ getQuantityString(CommonPlurals.card_template_editor_confirm_add, numAffectedCards, numAffectedCards),
getAlertDialogText(true),
)
clickAlertDialogButton(DialogInterface.BUTTON_POSITIVE, true)
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt
index dc826b5c5191..2d4c5a3959e7 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt
@@ -45,11 +45,13 @@ import com.ichi2.anki.deckpicker.heatmap.ReviewHeatmapAdapter
import com.ichi2.anki.deckpicker.heatmap.ReviewHeatmapViewModel
import com.ichi2.anki.dialogs.DatabaseErrorDialog
import com.ichi2.anki.dialogs.DatabaseErrorDialog.DatabaseErrorDialogType
+import com.ichi2.anki.dialogs.DeckPickerConfirmDeleteDeckDialog
import com.ichi2.anki.dialogs.DeckPickerContextMenu.DeckPickerContextMenuOption
import com.ichi2.anki.dialogs.DeckPickerContextMenuResult
import com.ichi2.anki.dialogs.DeckSelectionDialog
import com.ichi2.anki.dialogs.setDeckPickerContextMenuResult
import com.ichi2.anki.dialogs.utils.input
+import com.ichi2.anki.dialogs.utils.message
import com.ichi2.anki.dialogs.utils.performPositiveClick
import com.ichi2.anki.dialogs.utils.title
import com.ichi2.anki.libanki.DeckId
@@ -109,6 +111,7 @@ import org.robolectric.shadows.ShadowDialog
import org.robolectric.shadows.ShadowLooper
import timber.log.Timber
import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.seconds
@@ -324,6 +327,40 @@ class DeckPickerTest : RobolectricTest() {
}
}
+ @Test
+ fun `Delete shortcut requests confirmation only in tablet layout`() {
+ val deckName = "Deck to delete"
+ val deckId = addDeck(deckName, setAsSelected = true)
+ col.setDeck(listOf(addBasicNote().firstCard().id), deckId)
+
+ deckPicker {
+ val handled = onKeyUp(KeyEvent.KEYCODE_DEL, KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL))
+ if (!fragmented) {
+ assertFalse(handled)
+ assertTrue(supportFragmentManager.fragments.none { it is DeckPickerConfirmDeleteDeckDialog })
+ return@deckPicker
+ }
+
+ assertTrue(handled)
+ advanceRobolectricLooperUntil {
+ supportFragmentManager.fragments.any { it is DeckPickerConfirmDeleteDeckDialog && it.dialog?.isShowing == true }
+ }
+ val dialog =
+ supportFragmentManager.fragments
+ .filterIsInstance()
+ .single()
+ .requireDialog() as AlertDialog
+ assertEquals(getString(R.string.delete_deck_title), dialog.title)
+ assertThat(dialog.message, containsString(deckName))
+ assertEquals(deckId, col.decks.byName(deckName)?.id)
+ assertEquals(1, col.cardCount())
+
+ dialog.performPositiveClick()
+ advanceRobolectricLooperUntil { col.decks.byName(deckName) == null }
+ assertEquals(0, col.cardCount())
+ }
+ }
+
@Test
fun databaseLockedTest() {
// don't call .onCreate
@@ -983,14 +1020,14 @@ class DeckPickerTest : RobolectricTest() {
advanceRobolectricLooper()
val binding = floatingActionButtonBinding
- assertThat(binding.fabMain.text.toString(), equalTo(getString(R.string.menu_add)))
+ assertThat(binding.fabMain.text.toString(), equalTo(getString(CommonString.menu_add)))
assertThat(
binding.addSharedButton.text.toString(),
- equalTo(getString(R.string.menu_get_shared_decks)),
+ equalTo(getString(CommonString.menu_get_shared_decks)),
)
assertThat(
binding.addFilteredDeckButton.text.toString(),
- equalTo(getString(R.string.new_dynamic_deck)),
+ equalTo(getString(CommonString.new_dynamic_deck)),
)
// 'Create deck' uses a backend string rather than an android:text resource
assertThat(
@@ -1314,13 +1351,13 @@ class DeckPickerTest : RobolectricTest() {
.build()
ViewCompat.performReceiveContent(findViewById(R.id.pull_to_sync_wrapper), payload)
- val snackbar = showSnackbar(getString(R.string.import_log_no_apkg))
+ val snackbar = showSnackbar(getString(CommonString.import_log_no_apkg))
assertThat("snackbar must be shown for unsupported file drop", snackbar, notNullValue())
val snackbarText = snackbar?.text
assertThat(
snackbarText,
- equalTo(getString(R.string.import_log_no_apkg)),
+ equalTo(getString(CommonString.import_log_no_apkg)),
)
}
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorDeckSelectionTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorDeckSelectionTest.kt
new file mode 100644
index 000000000000..dc45b4c46ddb
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorDeckSelectionTest.kt
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.common.destinations.NoteEditorDestination
+import com.ichi2.anki.common.destinations.toIntent
+import com.ichi2.anki.common.utils.ext.AddingDefaultsMode
+import com.ichi2.anki.common.utils.ext.addingDefaultsMode
+import com.ichi2.anki.libanki.DeckId
+import com.ichi2.anki.model.SelectableDeck
+import com.ichi2.anki.noteeditor.getNoteEditorFragment
+import com.ichi2.anki.noteeditor.openNoteEditorWithArgs
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+class NoteEditorDeckSelectionTest : RobolectricTest() {
+ private fun openAdd(deckId: DeckId? = null) =
+ startRegularActivity(NoteEditorDestination.AddNote(deckId).toIntent()).getNoteEditorFragment()
+
+ private fun useAddingDefaults(mode: AddingDefaultsMode) {
+ ensureCollectionLoadIsSynchronous()
+ col.config.addingDefaultsMode = mode
+ }
+
+ private fun share(text: String) =
+ openNoteEditorWithArgs(
+ Bundle().apply { putString(Intent.EXTRA_TEXT, text) },
+ Intent.ACTION_SEND,
+ )
+
+ private fun NoteEditorFragment.select(did: DeckId) {
+ onDeckSelected(SelectableDeck.Deck(did, col.decks.name(did)))
+ }
+
+ @Test
+ fun `successive shares use the study deck in current-deck mode`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.USE_CURRENT_DECK)
+ val a = addDeck("Study A", setAsSelected = true)
+ val b = addDeck("Add B")
+ val first = share("first share")
+ val activity = first.requireActivity()
+ first.select(b)
+ first.saveNote()
+ advanceRobolectricLooper()
+ assertTrue(activity.isFinishing)
+ assertEquals(listOf(b), col.findCards("").map { col.getCard(it).did })
+ assertEquals(a, col.decks.selected())
+ assertEquals(a, share("second share").deckId)
+ }
+
+ @Test
+ fun `successive shares remember the destination in note-type mode`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.DECIDE_BY_NOTE_TYPE)
+ val a = addDeck("Study A", setAsSelected = true)
+ val b = addDeck("Add B")
+ val first = share("first share")
+ val ntid = first.editorNote!!.noteTypeId
+ val activity = first.requireActivity()
+ first.select(b)
+ first.saveNote()
+ advanceRobolectricLooper()
+ assertTrue(activity.isFinishing)
+ assertEquals(listOf(b), col.findCards("").map { col.getCard(it).did })
+ assertEquals(a, col.decks.selected())
+ val second = share("second share")
+ assertEquals(ntid, second.editorNote!!.noteTypeId)
+ assertEquals(b, second.deckId)
+ }
+
+ @Test
+ fun `explicit Default deck takes precedence over the remembered deck`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.DECIDE_BY_NOTE_TYPE)
+ val b = addDeck("Remembered B", setAsSelected = true)
+ col.addNote(col.newNote(col.notetypes.basic).apply { fields[0] = "saved" }, b)
+ assertEquals(b, col.defaultsForAdding().deckId)
+ assertEquals(1L, openAdd(1L).deckId)
+ }
+
+ // Preserve Android's existing launch validation. Anki's legacy desktop chooser instead uses
+ // Default for an invalid explicit destination; this is not an upstream parity assertion.
+ @Test
+ fun `invalid explicit destination falls back to the remembered deck`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.DECIDE_BY_NOTE_TYPE)
+ addDeck("Study", setAsSelected = true)
+ val rememberedDeck = addDeck("Remembered")
+ col.addNote(col.newNote(col.notetypes.basic).apply { fields[0] = "existing" }, rememberedDeck)
+ val filteredDeck = addDynamicDeck("Filtered")
+ val missingDeck = addDeck("Deleted")
+ col.decks.remove(listOf(missingDeck))
+
+ for (invalidDeck in listOf(filteredDeck, missingDeck)) {
+ val editor = openAdd(invalidDeck)
+ assertEquals(rememberedDeck, editor.deckId)
+ editor.requireActivity().finish()
+ }
+ }
+
+ @Test
+ fun `switching to a note type without history preserves the chosen deck`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.DECIDE_BY_NOTE_TYPE)
+ addDeck("Study A", setAsSelected = true)
+ val b = addDeck("Add B")
+ val reversed = col.notetypes.basicAndReversed
+ assertNull(col.defaultDeckForNoteType(reversed.id))
+ val editor = openAdd()
+ editor.select(b)
+ editor.setCurrentlySelectedNoteType(reversed.id)
+ advanceRobolectricLooper()
+ assertEquals(reversed.id, editor.editorNote!!.noteTypeId)
+ assertEquals(b, editor.deckId)
+ }
+
+ @Test
+ fun `deleted remembered deck does not replace the editor destination on a note-type change`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.DECIDE_BY_NOTE_TYPE)
+ addDeck("Study", setAsSelected = true)
+ val destination = addDeck("Destination")
+ val deletedDeck = addDeck("Deleted")
+ val reversed = col.notetypes.basicAndReversed
+ col.addNote(col.newNote(reversed).apply { fields[0] = "existing" }, deletedDeck)
+ col.decks.remove(listOf(deletedDeck))
+ col.notetypes.setCurrent(col.notetypes.basic)
+ assertNull(col.defaultDeckForNoteType(reversed.id))
+
+ val editor = openAdd()
+ editor.select(destination)
+ editor.setCurrentlySelectedNoteType(reversed.id)
+ advanceRobolectricLooper()
+
+ assertEquals(reversed.id, editor.editorNote!!.noteTypeId)
+ assertEquals(destination, editor.deckId)
+ }
+
+ @Test
+ fun `filtered remembered deck does not replace the editor destination on a note-type change`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.DECIDE_BY_NOTE_TYPE)
+ val filteredDeck = addDynamicDeck("Filtered")
+ val studyDeck = addDeck("Study", setAsSelected = true)
+ val destination = addDeck("Destination")
+ val reversed = col.notetypes.basicAndReversed
+ // addNote records the requested destination even when card generation uses a fallback.
+ col.addNote(col.newNote(reversed).apply { fields[0] = "existing" }, filteredDeck)
+ col.notetypes.setCurrent(col.notetypes.basic)
+ assertNull(col.defaultDeckForNoteType(reversed.id))
+
+ val editor = openAdd()
+ editor.select(destination)
+ editor.setCurrentlySelectedNoteType(reversed.id)
+ advanceRobolectricLooper()
+
+ assertEquals(reversed.id, editor.editorNote!!.noteTypeId)
+ assertEquals(destination, editor.deckId)
+ assertEquals(studyDeck, col.decks.selected())
+ }
+
+ @Test
+ fun `switching note types in current-deck mode preserves the chosen deck despite remembered history`() =
+ runTest {
+ useAddingDefaults(AddingDefaultsMode.DECIDE_BY_NOTE_TYPE)
+ val studyDeck = addDeck("Study", setAsSelected = true)
+ val destination = addDeck("Destination")
+ val rememberedDeck = addDeck("Remembered")
+ val reversed = col.notetypes.basicAndReversed
+ col.addNote(col.newNote(reversed).apply { fields[0] = "existing" }, rememberedDeck)
+ assertEquals(rememberedDeck, col.defaultDeckForNoteType(reversed.id))
+ col.config.addingDefaultsMode = AddingDefaultsMode.USE_CURRENT_DECK
+ col.notetypes.setCurrent(col.notetypes.basic)
+
+ val editor = openAdd()
+ editor.select(destination)
+ editor.setCurrentlySelectedNoteType(reversed.id)
+ advanceRobolectricLooper()
+
+ assertEquals(reversed.id, editor.editorNote!!.noteTypeId)
+ assertEquals(destination, editor.deckId)
+ assertEquals(studyDeck, col.decks.selected())
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt
index 2a3c83e8ce94..7c5b153bb1cb 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/NoteEditorTest.kt
@@ -211,7 +211,7 @@ class NoteEditorTest : RobolectricTest() {
// .withThirdField("C")
// .build()
// val actualResourceId = noteEditor.addNoteErrorResource
-// assertThat(actualResourceId, equalTo(R.string.note_editor_no_cards_created_all_fields))
+// assertThat(actualResourceId, equalTo(CommonString.note_editor_no_cards_created_all_fields))
// }
//
// @Test
@@ -222,7 +222,7 @@ class NoteEditorTest : RobolectricTest() {
// .withThirdField("C")
// .build()
// val actualResourceId = noteEditor.addNoteErrorResource
-// assertThat(actualResourceId, equalTo(R.string.note_editor_no_cards_created))
+// assertThat(actualResourceId, equalTo(CommonString.note_editor_no_cards_created))
// }
@Test
@@ -261,7 +261,7 @@ class NoteEditorTest : RobolectricTest() {
.build()
noteEditor.saveNote()
val actualResourceId = noteEditor.snackbarErrorText
- assertThat(actualResourceId, equalTo(getString(R.string.note_editor_no_cards_created)))
+ assertThat(actualResourceId, equalTo(getString(CommonString.note_editor_no_cards_created)))
}
@Test
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/SyncClockErrorScreenshotTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/SyncClockErrorScreenshotTest.kt
new file mode 100644
index 000000000000..306732ee1077
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/SyncClockErrorScreenshotTest.kt
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki
+
+import com.ichi2.anki.common.time.MockTime
+import com.ichi2.anki.common.time.TimeManager
+import com.ichi2.anki.dialogs.SyncErrorDialog
+import org.junit.Test
+
+/**
+ * `./gradlew :AnkiDroid:verifyRoborazziPlayDebug -Pscreenshot --tests "com.ichi2.anki.SyncClockErrorScreenshotTest"`
+ */
+class SyncClockErrorScreenshotTest : ScreenshotTest() {
+ @Test
+ fun `clock error dialog`() =
+ withDeckPicker(deckCount = 0) { deckPicker ->
+ TimeManager.resetWith(MockTime(1_600_000_000_000))
+ deckPicker.showSyncErrorDialog(
+ SyncErrorDialog.Type.DIALOG_SYNC_CLOCK_OFF,
+ CollectionManager.TR.syncClockOff(),
+ )
+ advanceRobolectricLooper()
+
+ captureScreen("clock_error_dialog")
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/SyncTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/SyncTest.kt
new file mode 100644
index 000000000000..16b09a839a7d
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/SyncTest.kt
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki
+
+import android.content.DialogInterface
+import android.provider.Settings
+import android.view.View
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import anki.backend.backendError
+import anki.sync.SyncAuth
+import anki.sync.SyncCollectionResponse
+import com.ichi2.anki.common.time.MockTime
+import com.ichi2.anki.common.time.TimeManager
+import com.ichi2.anki.dialogs.SyncErrorDialog
+import com.ichi2.anki.settings.Prefs
+import kotlinx.coroutines.runBlocking
+import net.ankiweb.rsdroid.Backend
+import net.ankiweb.rsdroid.BackendFactory
+import net.ankiweb.rsdroid.exceptions.BackendSyncException
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.Shadows.shadowOf
+import org.robolectric.shadows.ShadowDialog
+import java.text.DateFormat
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+class SyncTest : RobolectricTest() {
+ private lateinit var syncFailure: BackendSyncException
+
+ @Before
+ fun setUpFailingSync() {
+ runBlocking { CollectionManager.discardBackend() }
+ BackendFactory.setOverride {
+ object : Backend() {
+ override fun syncCollection(
+ auth: SyncAuth,
+ syncMedia: Boolean,
+ ): SyncCollectionResponse = throw syncFailure
+ }
+ }
+ }
+
+ @After
+ fun resetBackendFactory() {
+ BackendFactory.setOverride(null)
+ }
+
+ @Test
+ fun `clock errors use sync recovery dialog and open date settings`() =
+ deckPicker {
+ TimeManager.resetWith(MockTime(1_600_000_000_000))
+ val currentTime = DateFormat.getDateTimeInstance().format(TimeManager.time.currentDate)
+ val exception = BackendSyncException(backendError { message = CollectionManager.TR.syncClockOff() })
+
+ val dialog = failSync(exception)
+
+ assertTrue(supportFragmentManager.fragments.any { it is SyncErrorDialog })
+ assertEquals(
+ getString(CommonString.sync_clock_off_with_current_time, exception.localizedMessage, currentTime),
+ dialog.findViewById(android.R.id.message)?.text?.toString(),
+ )
+ assertEquals(getString(CommonString.dialog_cancel), dialog.getButton(DialogInterface.BUTTON_NEGATIVE).text)
+ assertEquals(getString(CommonString.open_settings), dialog.getButton(DialogInterface.BUTTON_POSITIVE).text)
+ assertEquals(View.GONE, dialog.getButton(DialogInterface.BUTTON_NEUTRAL).visibility)
+ assertEquals("test", Prefs.hkey)
+
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
+ advanceRobolectricLooper()
+
+ val intent = assertNotNull(shadowOf(this).nextStartedActivity)
+ assertEquals(Settings.ACTION_DATE_SETTINGS, intent.action)
+ assertFalse(dialog.isShowing)
+ }
+
+ @Test
+ fun `cancelling a clock error does not open settings`() =
+ deckPicker {
+ val dialog = failSync(BackendSyncException(backendError { message = CollectionManager.TR.syncClockOff() }))
+
+ dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
+ advanceRobolectricLooper()
+
+ assertFalse(dialog.isShowing)
+ assertNull(shadowOf(this).nextStartedActivity)
+ }
+
+ @Test
+ fun `clock error remains visible when date settings are unavailable`() =
+ deckPicker {
+ val dialog = failSync(BackendSyncException(backendError { message = CollectionManager.TR.syncClockOff() }))
+ val applicationShadow = shadowOf(application)
+ applicationShadow.checkActivities(true)
+ try {
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
+ advanceRobolectricLooper()
+
+ assertTrue(dialog.isShowing)
+ assertNull(shadowOf(this).nextStartedActivity)
+
+ dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
+ advanceRobolectricLooper()
+ assertFalse(dialog.isShowing)
+ } finally {
+ applicationShadow.checkActivities(false)
+ }
+ }
+
+ @Test
+ fun `other sync errors keep their message and OK button`() =
+ deckPicker {
+ val message = "other sync error"
+ val dialog = failSync(BackendSyncException(backendError { this.message = message }))
+
+ assertEquals(message, dialog.findViewById(android.R.id.message)?.text?.toString())
+ assertEquals(getString(CommonString.dialog_ok), dialog.getButton(DialogInterface.BUTTON_POSITIVE).text)
+ assertEquals(View.GONE, dialog.getButton(DialogInterface.BUTTON_NEGATIVE).visibility)
+ assertEquals(View.GONE, dialog.getButton(DialogInterface.BUTTON_NEUTRAL).visibility)
+ assertEquals("test", Prefs.hkey)
+
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
+ advanceRobolectricLooper()
+
+ assertFalse(dialog.isShowing)
+ assertNull(shadowOf(this).nextStartedActivity)
+ }
+
+ @Test
+ fun `authentication errors still log out`() =
+ deckPicker {
+ Prefs.username = "test user"
+ val message = "authentication failed"
+ val dialog = failSync(BackendSyncException.BackendSyncAuthFailedException(backendError { this.message = message }))
+
+ assertEquals("", Prefs.hkey)
+ assertEquals("", Prefs.username)
+ assertEquals(message, dialog.findViewById(android.R.id.message)?.text?.toString())
+ }
+
+ private fun DeckPicker.failSync(exception: BackendSyncException): AlertDialog {
+ syncFailure = exception
+ Prefs.hkey = "test"
+ Prefs.lastSyncTime = 0
+ throwOnShowError = false
+
+ handleNewSync(conflict = null, syncMedia = false)
+ advanceRobolectricLooperUntil { Prefs.lastSyncTime != 0L }
+
+ return assertNotNull(ShadowDialog.getLatestDialog() as? AlertDialog)
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ViewerResourceHandlerTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ViewerResourceHandlerTest.kt
index 39be328952fe..2f1d8a232b65 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ViewerResourceHandlerTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ViewerResourceHandlerTest.kt
@@ -38,6 +38,28 @@ class ViewerResourceHandlerTest : RobolectricTest() {
assertNull(response, "a request escaping the media directory must not be served")
}
+ @Test
+ fun `MathJax configuration is available at the backend URL`() {
+ val response = ViewerResourceHandler(targetContext).shouldInterceptRequest(request("/_anki/js/mathjax.js"))
+
+ val expected = readAssetText("backend/js/mathjax.js")
+ assertEquals(expected, assertNotNull(response).data.bufferedReader().use { it.readText() })
+ }
+
+ @Test
+ fun `MathJax implementation is available at the backend URL`() {
+ val response = ViewerResourceHandler(targetContext).shouldInterceptRequest(request("/_anki/js/vendor/mathjax/tex-chtml-full.js"))
+
+ val expected = readAssetText("backend/js/vendor/mathjax/tex-chtml-full.js")
+ assertEquals(expected, assertNotNull(response).data.bufferedReader().use { it.readText() })
+ }
+
+ private fun readAssetText(path: String): String =
+ targetContext.assets
+ .open(path)
+ .bufferedReader()
+ .use { it.readText() }
+
private fun request(path: String): WebResourceRequest =
mock {
on { url } doReturn "http://127.0.0.1$path".toUri()
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt
index 23fca6425617..e82396faa9af 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/browser/CardBrowserViewModelTest.kt
@@ -70,6 +70,7 @@ import com.ichi2.anki.noteeditor.toIntent
import com.ichi2.anki.observability.ensureNoOpsExecuted
import com.ichi2.anki.observability.ensureOpWithHandler
import com.ichi2.anki.observability.ensureOpsExecuted
+import com.ichi2.anki.progress.ViewModelProgress
import com.ichi2.anki.servicelayer.NoteService
import com.ichi2.anki.setFlagFilterSync
import com.ichi2.anki.settings.Prefs
@@ -82,12 +83,15 @@ import com.ichi2.testutils.ext.reopenWithLanguage
import com.ichi2.testutils.mockIt
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.setMain
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.contains
import org.hamcrest.Matchers.containsInAnyOrder
@@ -109,6 +113,7 @@ import java.io.File
import kotlin.io.path.createTempDirectory
import kotlin.io.path.pathString
import kotlin.test.assertEquals
+import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
@@ -872,6 +877,107 @@ class CardBrowserViewModelTest : JvmTest() {
ensureOpWithHandler(this) { deleteSelectedNotes() }
}
+ @Test
+ fun `toggle mark - handler passed to undoableOp prevents double refresh`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ ensureOpWithHandler(this) { toggleMark() }
+ }
+
+ @Test
+ fun `flag - handler passed to undoableOp prevents double refresh`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ ensureOpWithHandler(this) { updateSelectedCardsFlag(Flag.RED) }
+ }
+
+ @Test
+ fun `deleteSelectedNotes reports progress`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ assertProgressAround(CardBrowserProgress.DELETING_NOTES) { deleteSelectedNotes() }
+ }
+
+ @Test
+ fun `toggleMark reports progress`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ assertProgressAround { toggleMark() }
+ }
+
+ @Test
+ fun `toggleSuspendCards reports progress`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ assertProgressAround { toggleSuspendCards().join() }
+ }
+
+ @Test
+ fun `updateSelectedCardsFlag reports progress`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ assertProgressAround { updateSelectedCardsFlag(Flag.RED) }
+ }
+
+ @Test
+ fun `toggleBury reports progress`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ assertProgressAround { toggleBury() }
+ }
+
+ @Test
+ fun `moveSelectedCardsToDeck reports progress`() =
+ runViewModelTest(notes = 2) {
+ val newDeck = addDeck("World")
+ selectRowsWithPositions(0)
+
+ assertProgressAround { moveSelectedCardsToDeck(newDeck).await() }
+ }
+
+ @Test
+ fun `repositionSelectedRows reports progress`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+
+ assertProgressAround { repositionSelectedRows(position = 100, step = 1, shuffle = false, shift = false) }
+ }
+
+ @Test
+ fun `findAndReplace reports progress`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+ val result =
+ FindReplaceResult(
+ search = "Front",
+ replacement = "Replaced",
+ field = FindAndReplaceDialogFragment.ALL_FIELDS_AS_FIELD,
+ onlyOnSelectedNotes = true,
+ matchCase = false,
+ regex = false,
+ )
+
+ assertProgressAround { findAndReplace(result).await() }
+ }
+
+ @Test
+ fun `editSelectedCardsTags reports progress and tags the selected note`() =
+ runViewModelTest(notes = 2) {
+ selectRowsWithPositions(0)
+ val noteId = queryAllSelectedNoteIds().single()
+
+ assertProgressAround { editSelectedCardsTags(selectedTags = listOf("tagged"), indeterminateTags = emptyList()) }
+
+ assertThat(col.getNote(noteId).tags, equalTo(listOf("tagged")))
+ }
+
/** @see #20556 */
@Test
fun `delete note - no crash when cardIdToBeScrolledTo is deleted in NOTES mode`() {
@@ -2200,6 +2306,25 @@ fun runCardBrowserViewModelTest(
testBody: suspend CardBrowserViewModel.() -> Unit,
) = runViewModelTest(notes, manualInit, savedStateHandle, testBody)
+/**
+ * Asserts [op] moves progress Idle, Active, Idle. Main becomes a [StandardTestDispatcher] so an op
+ * launched in `viewModelScope` is dispatched rather than run inline, which would hide Active.
+ */
+private suspend fun CardBrowserViewModel.assertProgressAround(
+ message: CardBrowserProgress? = null,
+ op: suspend () -> Unit,
+) {
+ Dispatchers.setMain(StandardTestDispatcher(currentCoroutineContext()[TestCoroutineScheduler]))
+ progressManager.progress.test {
+ assertIs(awaitItem())
+ op()
+ val active = assertIs>(awaitItem())
+ assertEquals(message, active.message)
+ assertIs(awaitItem())
+ expectNoEvents()
+ }
+}
+
@Suppress("SameParameterValue")
private fun CardBrowserViewModel.selectRowsWithPositions(vararg positions: Int) {
for (pos in positions) {
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt
index b61a14b6f3a8..6238e54bd57f 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt
@@ -10,6 +10,7 @@ import anki.card_rendering.emptyCardsReport
import app.cash.turbine.test
import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.RobolectricTest
+import com.ichi2.anki.deckpicker.DeckPickerViewModel.DeleteDeckConfirmationRequest
import com.ichi2.anki.libanki.Consts
import com.ichi2.anki.libanki.DeckId
import com.ichi2.anki.libanki.Note
@@ -28,6 +29,68 @@ import kotlin.test.assertEquals
class DeckPickerViewModelTest : RobolectricTest() {
private val viewModel = DeckPickerViewModel()
+ @Test
+ fun `delete confirmation uses focused deck and includes cards in subdecks`() =
+ runTest {
+ val deckId = addDeck("Parent")
+ val childDeckId = addDeck("Parent::Child")
+ val parentCard = addBasicNote("Parent card").firstCard()
+ val childCard = addBasicNote("Child card").firstCard()
+ addBasicNote("Unrelated card")
+ col.setDeck(listOf(parentCard.id), deckId)
+ col.setDeck(listOf(childCard.id), childDeckId)
+ selectDefaultDeck()
+ viewModel.focusedDeck = deckId
+
+ viewModel.flowOfDeleteDeckConfirmation.test {
+ viewModel.requestDeleteDeckConfirmation().join()
+
+ assertEquals(DeleteDeckConfirmationRequest(deckId, "Parent", 2, false), awaitItem())
+ assertEquals(deckId, col.decks.byName("Parent")?.id)
+ assertEquals(childDeckId, col.decks.byName("Parent::Child")?.id)
+ assertEquals(3, col.cardCount())
+ }
+ }
+
+ @Test
+ fun `delete confirmation identifies filtered decks without returning their cards`() =
+ runTest {
+ val note = addBasicNote()
+ val deckId = moveAllCardsToFilteredDeck(note)
+ viewModel.focusedDeck = deckId
+
+ viewModel.flowOfDeleteDeckConfirmation.test {
+ viewModel.requestDeleteDeckConfirmation().join()
+
+ assertEquals(DeleteDeckConfirmationRequest(deckId, "Filtered", 1, true), awaitItem())
+ assertEquals(deckId, col.decks.byName("Filtered")?.id)
+ assertEquals(deckId, note.firstCard().did)
+ }
+ }
+
+ @Test
+ fun `delete confirmation supports an empty deck`() =
+ runTest {
+ val deckId = addDeck("Empty")
+ viewModel.focusedDeck = deckId
+
+ viewModel.flowOfDeleteDeckConfirmation.test {
+ viewModel.requestDeleteDeckConfirmation().join()
+
+ assertEquals(DeleteDeckConfirmationRequest(deckId, "Empty", 0, false), awaitItem())
+ }
+ }
+
+ @Test
+ fun `delete confirmation without a focused deck emits nothing`() =
+ runTest {
+ viewModel.flowOfDeleteDeckConfirmation.test {
+ viewModel.requestDeleteDeckConfirmation().join()
+
+ expectNoEvents()
+ }
+ }
+
@Test
fun `empty cards - flow`() =
runTest {
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialogInsetsTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialogInsetsTest.kt
new file mode 100644
index 000000000000..56b23328cff0
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialogInsetsTest.kt
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.dialogs
+
+import android.os.Bundle
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.fragment.app.testing.launchFragment
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.RobolectricTest
+import com.ichi2.anki.dialogs.ChangeNoteTypeDialog.Companion.ARG_NOTE_IDS
+import com.ichi2.anki.settings.enums.DayTheme
+import com.ichi2.anki.settings.enums.NightTheme
+import com.ichi2.anki.settings.enums.Theme
+import com.ichi2.testutils.windowInsetsOf
+import com.ichi2.themes.Themes
+import com.ichi2.utils.dp
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ChangeNoteTypeDialogInsetsTest : RobolectricTest() {
+ @Test
+ fun `dialog clears system bars and cutouts`() =
+ withDialog { dialog ->
+ val insets =
+ with(targetContext) {
+ windowInsetsOf(navBarBottom = 48.dp, navBarRight = 48.dp, cutoutLeft = 32.dp)
+ }
+ ViewCompat.dispatchApplyWindowInsets(dialog.requireDialog().window!!.decorView, insets)
+
+ val root = dialog.requireView()
+ assertThat(root.paddingLeft, equalTo(32.dp.toPx(targetContext)))
+ assertThat(root.paddingTop, equalTo(24.dp.toPx(targetContext)))
+ assertThat(root.paddingRight, equalTo(48.dp.toPx(targetContext)))
+ assertThat(root.paddingBottom, equalTo(48.dp.toPx(targetContext)))
+ }
+
+ @Test
+ fun `status bar icons are dark over a light theme`() =
+ withDialog(DayTheme.LIGHT) { dialog ->
+ assertThat(dialog.hasLightStatusBars, equalTo(true))
+ }
+
+ @Test
+ fun `status bar icons stay light over a dark theme`() =
+ withDialog(NightTheme.DARK) { dialog ->
+ assertThat(dialog.hasLightStatusBars, equalTo(false))
+ }
+
+ private val ChangeNoteTypeDialog.hasLightStatusBars: Boolean
+ get() {
+ val window = requireDialog().window!!
+ return WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars
+ }
+
+ private fun withDialog(
+ theme: Theme = DayTheme.LIGHT,
+ block: (ChangeNoteTypeDialog) -> Unit,
+ ) {
+ val previousTheme = Themes.currentTheme
+ Themes.currentTheme = theme
+ try {
+ launchFragment(
+ fragmentArgs = Bundle().apply { putLongArray(ARG_NOTE_IDS, longArrayOf(addBasicNote().id)) },
+ themeResId = theme.styleResId,
+ ).use { scenario -> scenario.onFragment(block) }
+ } finally {
+ Themes.currentTheme = previousTheme
+ }
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialogScreenshotTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialogScreenshotTest.kt
new file mode 100644
index 000000000000..04188c335e58
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ChangeNoteTypeDialogScreenshotTest.kt
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.dialogs
+
+import androidx.core.view.WindowInsetsCompat
+import com.ichi2.anki.ScreenshotTest
+import com.ichi2.anki.withCardBrowser
+import com.ichi2.testutils.simulateSystemBars
+import com.ichi2.testutils.windowInsetsOf
+import com.ichi2.utils.dp
+import org.junit.Test
+import org.robolectric.RuntimeEnvironment
+
+class ChangeNoteTypeDialogScreenshotTest : ScreenshotTest() {
+ @Test
+ fun portrait() =
+ captureChangeNoteType(
+ "portrait",
+ with(targetContext) { windowInsetsOf(navBarBottom = 48.dp) },
+ )
+
+ @Test
+ fun gestureNavigation() =
+ captureChangeNoteType(
+ "gesture_navigation",
+ with(targetContext) { windowInsetsOf(navBarBottom = 24.dp) },
+ )
+
+ @Test
+ fun portraitCutout() =
+ captureChangeNoteType(
+ "portrait_cutout",
+ with(targetContext) { windowInsetsOf(navBarBottom = 48.dp, cutoutTop = 40.dp) },
+ )
+
+ @Test
+ fun landscape() {
+ RuntimeEnvironment.setQualifiers("+land")
+ captureChangeNoteType(
+ "landscape",
+ with(targetContext) { windowInsetsOf(navBarRight = 48.dp, cutoutLeft = 32.dp) },
+ )
+ }
+
+ private fun captureChangeNoteType(
+ name: String,
+ insets: WindowInsetsCompat,
+ ) = withCardBrowser(noteCount = 1) { browser ->
+ val dialog = ChangeNoteTypeDialog.newInstance(col.findNotes(""))
+ dialog.showNow(browser.supportFragmentManager, "change_note_type")
+ advanceRobolectricLooper()
+ try {
+ dialog.requireDialog().window!!.simulateSystemBars(insets)
+ captureScreen(name)
+ } finally {
+ dialog.dismissNow()
+ }
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt
index ae6c9861e072..68b06361b4b7 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CreateDeckDialogTest.kt
@@ -13,9 +13,9 @@ import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.CollectionManager.withCol
+import com.ichi2.anki.CommonString
import com.ichi2.anki.DeckPicker
import com.ichi2.anki.IntroductionActivity
-import com.ichi2.anki.R
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.dialogs.CreateDeckDialog.DeckDialogType
import com.ichi2.anki.dialogs.utils.input
@@ -335,7 +335,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"error is displayed",
getInputTextLayout().error?.toString(),
- equalTo(getResourceString(R.string.error_name_exists)),
+ equalTo(getResourceString(CommonString.error_name_exists)),
)
assertThat("rename is disabled", positiveButton.isEnabled, equalTo(false))
}
@@ -442,7 +442,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Snackbar should confirm deck creation for valid name",
activity.latestSnackbarText(),
- equalTo(getResourceString(R.string.deck_created)),
+ equalTo(getResourceString(CommonString.deck_created)),
)
}
}
@@ -459,7 +459,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Snackbar should show invalid name error for blank name",
activity.latestSnackbarText(),
- equalTo(getResourceString(R.string.invalid_deck_name)),
+ equalTo(getResourceString(CommonString.invalid_deck_name)),
)
}
}
@@ -475,7 +475,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Toast should confirm deck creation for valid name",
ShadowToast.getTextOfLatestToast(),
- equalTo(getResourceString(R.string.deck_created)),
+ equalTo(getResourceString(CommonString.deck_created)),
)
}
}
@@ -491,7 +491,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Toast should show invalid name error for blank name",
ShadowToast.getTextOfLatestToast(),
- equalTo(getResourceString(R.string.invalid_deck_name)),
+ equalTo(getResourceString(CommonString.invalid_deck_name)),
)
}
}
@@ -508,7 +508,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Snackbar should confirm rename for valid name",
activity.latestSnackbarText(),
- equalTo(getResourceString(R.string.deck_renamed)),
+ equalTo(getResourceString(CommonString.deck_renamed)),
)
}
}
@@ -526,7 +526,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Snackbar should show invalid name error for blank name",
activity.latestSnackbarText(),
- equalTo(getResourceString(R.string.invalid_deck_name)),
+ equalTo(getResourceString(CommonString.invalid_deck_name)),
)
}
}
@@ -549,7 +549,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Toast should confirm rename for valid name",
ShadowToast.getTextOfLatestToast(),
- equalTo(getResourceString(R.string.deck_renamed)),
+ equalTo(getResourceString(CommonString.deck_renamed)),
)
}
}
@@ -572,7 +572,7 @@ class CreateDeckDialogTest : RobolectricTest() {
assertThat(
"Toast should show invalid name error for blank name",
ShadowToast.getTextOfLatestToast(),
- equalTo(getResourceString(R.string.invalid_deck_name)),
+ equalTo(getResourceString(CommonString.invalid_deck_name)),
)
}
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/DeckPickerContextMenuTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/DeckPickerContextMenuTest.kt
index f1a4ef237a66..9f9d114ea6d0 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/DeckPickerContextMenuTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/DeckPickerContextMenuTest.kt
@@ -11,6 +11,7 @@ import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.CollectionManager.TR
+import com.ichi2.anki.CommonString
import com.ichi2.anki.IntroductionActivity
import com.ichi2.anki.R
import com.ichi2.anki.common.preferences.sharedPrefs
@@ -96,12 +97,12 @@ class DeckPickerContextMenuTest : JvmTest() {
fun `Shows standard options`() {
launch(withArguments()).onFragment { fragment ->
with(fragment.requireContext()) {
- fragment.assertOptionPresent(R.string.menu_add)
- fragment.assertOptionPresent(R.string.browse_cards)
+ fragment.assertOptionPresent(CommonString.menu_add)
+ fragment.assertOptionPresent(CommonString.browse_cards)
fragment.assertOptionPresent(TR.sentenceCase.renameDeck)
fragment.assertOptionPresent(TR.sentenceCase.deckOptions)
- fragment.assertOptionPresent(R.string.export_deck)
- fragment.assertOptionPresent(R.string.create_shortcut)
+ fragment.assertOptionPresent(CommonString.export_deck)
+ fragment.assertOptionPresent(CommonString.create_shortcut)
fragment.assertOptionPresent(TR.sentenceCase.deleteDeck)
}
}
@@ -135,7 +136,7 @@ class DeckPickerContextMenuTest : JvmTest() {
fun `Shows options to empty and rebuild when deck is dynamic`() {
launch(withArguments(isDynamic = true)).onFragment { fragment ->
assertTrue(
- fragment.foundOptions().contains(fragment.getString(R.string.empty_cram_label)),
+ fragment.foundOptions().contains(fragment.getString(CommonString.empty_cram_label)),
"'Empty' should be present when deck is dynamic",
)
assertTrue(
@@ -149,7 +150,7 @@ class DeckPickerContextMenuTest : JvmTest() {
fun `Shows option to create subdeck when deck is not dynamic`() {
launch(withArguments()).onFragment { fragment ->
assertTrue(
- fragment.foundOptions().contains(fragment.getString(R.string.create_subdeck)),
+ fragment.foundOptions().contains(fragment.getString(CommonString.create_subdeck)),
"'Create subdeck' should be present when deck is not dynamic",
)
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ImportFileSelectionFragmentTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ImportFileSelectionFragmentTest.kt
index 563f84b6726c..4be298f2fec3 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ImportFileSelectionFragmentTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/ImportFileSelectionFragmentTest.kt
@@ -6,7 +6,7 @@ package com.ichi2.anki.dialogs
import android.content.Intent
import android.widget.ListView
import androidx.appcompat.app.AlertDialog
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.dialogs.ImportFileSelectionFragment.Companion.buildImportFilePickerIntent
import com.ichi2.anki.dialogs.ImportFileSelectionFragment.ImportOptions
@@ -71,7 +71,7 @@ class ImportFileSelectionFragmentTest : RobolectricTest() {
dialog.listView.itemLabels(),
equalTo(
listOf(
- targetContext.getString(R.string.import_deck_package),
+ targetContext.getString(CommonString.import_deck_package),
importCollectionPackageLabel,
importCsvLabel,
),
@@ -99,7 +99,7 @@ class ImportFileSelectionFragmentTest : RobolectricTest() {
ImportOptions(importColpkg = false, importApkg = true, importTextFile = false),
) { dialog ->
assertThat(dialog.listView.count, equalTo(1))
- assertThat(dialog.listView.itemLabels(), equalTo(listOf(targetContext.getString(R.string.import_deck_package))))
+ assertThat(dialog.listView.itemLabels(), equalTo(listOf(targetContext.getString(CommonString.import_deck_package))))
}
}
@@ -133,9 +133,9 @@ class ImportFileSelectionFragmentTest : RobolectricTest() {
}
}
- private val importCollectionPackageLabel = targetContext.getString(R.string.import_collection_package)
+ private val importCollectionPackageLabel = targetContext.getString(CommonString.import_collection_package)
- private val importCsvLabel = targetContext.getString(R.string.import_csv)
+ private val importCsvLabel = targetContext.getString(CommonString.import_csv)
private fun ListView.itemLabels(): List = (0 until adapter.count).map { adapter.getItem(it).toString() }
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/InsertFieldDialogInsetsTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/InsertFieldDialogInsetsTest.kt
new file mode 100644
index 000000000000..3837f2959d5e
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/InsertFieldDialogInsetsTest.kt
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.dialogs
+
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.fragment.app.testing.launchFragment
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.EmptyApplicationCategory
+import com.ichi2.anki.RobolectricTest
+import com.ichi2.anki.cardviewer.SingleCardSide
+import com.ichi2.anki.settings.enums.DayTheme
+import com.ichi2.anki.settings.enums.NightTheme
+import com.ichi2.anki.settings.enums.Theme
+import com.ichi2.testutils.EmptyApplication
+import com.ichi2.testutils.windowInsetsOf
+import com.ichi2.themes.Themes
+import com.ichi2.utils.dp
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.junit.Test
+import org.junit.experimental.categories.Category
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+@RunWith(AndroidJUnit4::class)
+@Config(application = EmptyApplication::class)
+@Category(EmptyApplicationCategory::class)
+class InsertFieldDialogInsetsTest : RobolectricTest() {
+ @Test
+ fun `dialog clears system bars and cutouts`() =
+ withDialog { dialog ->
+ val insets =
+ with(targetContext) {
+ windowInsetsOf(navBarBottom = 48.dp, navBarRight = 48.dp, cutoutLeft = 32.dp)
+ }
+ ViewCompat.dispatchApplyWindowInsets(dialog.requireDialog().window!!.decorView, insets)
+
+ val root = dialog.requireView()
+ assertThat(root.paddingLeft, equalTo(32.dp.toPx(targetContext)))
+ assertThat(root.paddingTop, equalTo(24.dp.toPx(targetContext)))
+ assertThat(root.paddingRight, equalTo(48.dp.toPx(targetContext)))
+ assertThat(root.paddingBottom, equalTo(48.dp.toPx(targetContext)))
+ }
+
+ @Test
+ fun `status bar icons are dark over a light theme`() =
+ withDialog(DayTheme.LIGHT) { dialog ->
+ assertThat(dialog.hasLightStatusBars, equalTo(true))
+ }
+
+ @Test
+ fun `status bar icons stay light over a dark theme`() =
+ withDialog(NightTheme.DARK) { dialog ->
+ assertThat(dialog.hasLightStatusBars, equalTo(false))
+ }
+
+ private val InsertFieldDialog.hasLightStatusBars: Boolean
+ get() {
+ val window = requireDialog().window!!
+ return WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars
+ }
+
+ private fun withDialog(
+ theme: Theme = DayTheme.LIGHT,
+ block: (InsertFieldDialog) -> Unit,
+ ) {
+ val metadata =
+ InsertFieldMetadata(
+ side = SingleCardSide.FRONT,
+ cardTemplateName = "Card 1",
+ noteTypeName = "Basic",
+ tags = null,
+ flag = null,
+ cardId = null,
+ deck = null,
+ )
+ val previousTheme = Themes.currentTheme
+ Themes.currentTheme = theme
+ try {
+ launchFragment(
+ fragmentArgs = InsertFieldDialog.newInstance(listOf("Front", "Back"), metadata, "insert_field").requireArguments(),
+ themeResId = theme.styleResId,
+ ).use { scenario -> scenario.onFragment(block) }
+ } finally {
+ Themes.currentTheme = previousTheme
+ }
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/InsertFieldDialogScreenshotTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/InsertFieldDialogScreenshotTest.kt
new file mode 100644
index 000000000000..2dec48df1f02
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/InsertFieldDialogScreenshotTest.kt
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.dialogs
+
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.viewpager2.widget.ViewPager2
+import com.ichi2.anki.R
+import com.ichi2.anki.ScreenshotTest
+import com.ichi2.anki.dialogs.InsertFieldDialog.SelectSpecialFieldFragment
+import com.ichi2.anki.dialogs.InsertFieldDialogViewModel.Tab
+import com.ichi2.anki.withCardTemplateEditor
+import com.ichi2.testutils.scrollToEnd
+import com.ichi2.testutils.simulateSystemBars
+import com.ichi2.testutils.windowInsetsOf
+import com.ichi2.utils.dp
+import org.junit.Test
+import org.robolectric.RuntimeEnvironment
+
+class InsertFieldDialogScreenshotTest : ScreenshotTest() {
+ @Test
+ fun portrait() =
+ captureInsertField(
+ "portrait",
+ with(targetContext) { windowInsetsOf(navBarBottom = 48.dp) },
+ )
+
+ @Test
+ fun gestureNavigation() =
+ captureInsertField(
+ "gesture_navigation",
+ with(targetContext) { windowInsetsOf(navBarBottom = 24.dp) },
+ )
+
+ @Test
+ fun portraitCutout() =
+ captureInsertField(
+ "portrait_cutout",
+ with(targetContext) { windowInsetsOf(navBarBottom = 48.dp, cutoutTop = 40.dp) },
+ )
+
+ @Test
+ fun landscape() {
+ RuntimeEnvironment.setQualifiers("+land")
+ captureInsertField(
+ "landscape",
+ with(targetContext) { windowInsetsOf(navBarRight = 48.dp, cutoutLeft = 32.dp) },
+ )
+ }
+
+ private fun captureInsertField(
+ name: String,
+ insets: WindowInsetsCompat,
+ ) = withCardTemplateEditor {
+ currentFragment!!.showInsertFieldDialog()
+ advanceRobolectricLooper()
+ val dialog = supportFragmentManager.fragments.filterIsInstance().single()
+ try {
+ val window = dialog.requireDialog().window!!
+ window.simulateSystemBars(insets)
+ captureScreen(name)
+
+ dialog.requireView().findViewById(R.id.view_pager).setCurrentItem(Tab.SPECIAL.position, false)
+ advanceRobolectricLooper()
+ ViewCompat.dispatchApplyWindowInsets(window.decorView, insets)
+ dialog.childFragmentManager.fragments
+ .filterIsInstance()
+ .single()
+ .binding.root
+ .scrollToEnd()
+ captureScreen("${name}_special_scrolled_to_bottom")
+ } finally {
+ dialog.dismissNow()
+ }
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/help/HelpDialogTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/help/HelpDialogTest.kt
index 49803edcd9b4..f2e9c7d75ce5 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/help/HelpDialogTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/help/HelpDialogTest.kt
@@ -15,6 +15,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.AnkiDroidApp
import com.ichi2.anki.BuildConfig
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.dialogs.help.HelpItem.Action.Rate
import io.mockk.mockk
@@ -56,12 +57,12 @@ class HelpDialogTest {
// checking the support menu
val expectedSupportItems =
listOfNotNull(
- R.string.help_item_support_opencollective_donate.takeIf { BuildConfig.SHOW_DONATE_LINKS },
- R.string.multimedia_editor_trans_translate,
- R.string.help_item_support_develop_ankidroid,
- R.string.help_item_support_rate_ankidroid,
- R.string.help_item_support_other_ankidroid,
- R.string.send_feedback,
+ CommonString.help_item_support_opencollective_donate.takeIf { BuildConfig.SHOW_DONATE_LINKS },
+ CommonString.multimedia_editor_trans_translate,
+ CommonString.help_item_support_develop_ankidroid,
+ CommonString.help_item_support_rate_ankidroid,
+ CommonString.help_item_support_other_ankidroid,
+ CommonString.send_feedback,
)
val actualSupportItems =
HelpDialog.newSupportInstance(true).requireArgsHelpEntries().map { it.titleResId }
@@ -73,10 +74,10 @@ class HelpDialogTest {
// checking the help menu
val expectedHelpItems =
listOf(
- R.string.help_title_using_ankidroid,
- R.string.help_title_get_help,
- R.string.help_title_community,
- R.string.help_title_privacy,
+ CommonString.help_title_using_ankidroid,
+ CommonString.help_title_get_help,
+ CommonString.help_title_community,
+ CommonString.help_title_privacy,
)
val actualHelpItems =
HelpDialog.newHelpInstance().requireArgsHelpEntries().map { it.titleResId }
@@ -113,39 +114,39 @@ class HelpDialogTest {
@Test
fun `Help menu handles submenus correctly`() {
withHelpDialog {
- onView(withText(R.string.help_title_community)).inRoot(isDialog()).perform(click())
+ onView(withText(CommonString.help_title_community)).inRoot(isDialog()).perform(click())
// check that the expected six children are shown
- onView(withText(R.string.help_item_discord))
+ onView(withText(CommonString.help_item_discord))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_item_reddit))
+ onView(withText(CommonString.help_item_reddit))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_item_facebook))
+ onView(withText(CommonString.help_item_facebook))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_item_mailing_list))
+ onView(withText(CommonString.help_item_mailing_list))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_item_twitter))
+ onView(withText(CommonString.help_item_twitter))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_item_anki_forums))
+ onView(withText(CommonString.help_item_anki_forums))
.inRoot(isDialog())
.check(matches(isDisplayed()))
// press back
pressBackUnconditionally()
// check that the expected initial four menu items are shown
- onView(withText(R.string.help_title_community))
+ onView(withText(CommonString.help_title_community))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_title_get_help))
+ onView(withText(CommonString.help_title_get_help))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_title_privacy))
+ onView(withText(CommonString.help_title_privacy))
.inRoot(isDialog())
.check(matches(isDisplayed()))
- onView(withText(R.string.help_title_using_ankidroid))
+ onView(withText(CommonString.help_title_using_ankidroid))
.inRoot(isDialog())
.check(matches(isDisplayed()))
}
@@ -154,27 +155,27 @@ class HelpDialogTest {
@Test
fun `Help menu survives recreating its view`() {
withHelpDialog { scenario ->
- onView(withText(R.string.help_title_community)).inRoot(isDialog()).check(matches(isDisplayed()))
+ onView(withText(CommonString.help_title_community)).inRoot(isDialog()).check(matches(isDisplayed()))
scenario.moveToState(Lifecycle.State.CREATED)
scenario.moveToState(Lifecycle.State.RESUMED)
- onView(withText(R.string.help_title_community)).inRoot(isDialog()).check(matches(isDisplayed()))
+ onView(withText(CommonString.help_title_community)).inRoot(isDialog()).check(matches(isDisplayed()))
}
}
@Test
fun `Help submenu and back navigation survive recreating the view`() {
withHelpDialog { scenario ->
- onView(withText(R.string.help_title_community)).inRoot(isDialog()).perform(click())
- onView(withText(R.string.help_item_discord)).inRoot(isDialog()).check(matches(isDisplayed()))
+ onView(withText(CommonString.help_title_community)).inRoot(isDialog()).perform(click())
+ onView(withText(CommonString.help_item_discord)).inRoot(isDialog()).check(matches(isDisplayed()))
scenario.moveToState(Lifecycle.State.CREATED)
scenario.moveToState(Lifecycle.State.RESUMED)
- onView(withText(R.string.help_item_discord)).inRoot(isDialog()).check(matches(isDisplayed()))
+ onView(withText(CommonString.help_item_discord)).inRoot(isDialog()).check(matches(isDisplayed()))
pressBackUnconditionally()
- onView(withText(R.string.help_title_community)).inRoot(isDialog()).check(matches(isDisplayed()))
+ onView(withText(CommonString.help_title_community)).inRoot(isDialog()).check(matches(isDisplayed()))
}
}
@@ -183,25 +184,25 @@ class HelpDialogTest {
withHelpDialog { scenario ->
scenario.onFragment { it.actionsDispatcher = mockActionDispatcher }
// start the first submenu
- onView(withText(R.string.help_title_using_ankidroid))
+ onView(withText(CommonString.help_title_using_ankidroid))
.inRoot(isDialog())
.perform(click())
// the manual url is being shown
- onView(withText(R.string.help_item_ankidroid_manual))
+ onView(withText(CommonString.help_item_ankidroid_manual))
.inRoot(isDialog())
.perform(click())
verify(exactly = 1) { mockActionDispatcher.onOpenUrl(AnkiDroidApp.manualUrl) }
// an url resource is being shown
- onView(withText(R.string.help_item_anki_manual)).inRoot(isDialog()).perform(click())
+ onView(withText(CommonString.help_item_anki_manual)).inRoot(isDialog()).perform(click())
verify(exactly = 1) { mockActionDispatcher.onOpenUrlResource(R.string.link_anki_manual) }
pressBackUnconditionally()
// start the second submenu
- onView(withText(R.string.help_title_get_help)).inRoot(isDialog()).perform(click())
+ onView(withText(CommonString.help_title_get_help)).inRoot(isDialog()).perform(click())
// the feedback url is being shown
- onView(withText(R.string.help_item_report_bug)).inRoot(isDialog()).perform(click())
+ onView(withText(CommonString.help_item_report_bug)).inRoot(isDialog()).perform(click())
verify(exactly = 1) { mockActionDispatcher.onOpenUrl(AnkiDroidApp.feedbackUrl) }
// a report is sent
- onView(withText(R.string.help_title_send_exception))
+ onView(withText(CommonString.help_title_send_exception))
.inRoot(isDialog())
.perform(click())
verify(exactly = 1) { mockActionDispatcher.onSendReport() }
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/logging/ActivityLaunchLoggerTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/logging/ActivityLaunchLoggerTest.kt
index 33604e9aceb9..ac17eaf01254 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/logging/ActivityLaunchLoggerTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/logging/ActivityLaunchLoggerTest.kt
@@ -10,20 +10,28 @@ import android.os.Bundle
import androidx.core.net.toUri
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
+import com.ichi2.anki.EmptyApplicationCategory
import com.ichi2.anki.NavigationDrawerActivity.Companion.EXTRA_STARTED_WITH_SHORTCUT
import com.ichi2.anki.SingleFragmentActivity.Companion.EXTRA_FRAGMENT_ARGS
import com.ichi2.anki.SingleFragmentActivity.Companion.EXTRA_FRAGMENT_NAME
+import com.ichi2.testutils.EmptyApplication
import io.mockk.every
import io.mockk.mockk
import org.junit.After
import org.junit.Before
import org.junit.Test
+import org.junit.experimental.categories.Category
import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
import timber.log.Timber
import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
+// Application startup logs can race with the launch diagnostics captured by these tests.
+@Config(application = EmptyApplication::class)
+@Category(EmptyApplicationCategory::class)
class ActivityLaunchLoggerTest {
private val messages = mutableListOf()
private val tree =
@@ -69,7 +77,7 @@ class ActivityLaunchLoggerTest {
activity.logActivityCreation(Bundle().apply { putString(privateText, privateText) })
- val message = messages.single()
+ val message = assertNotNull(messages.singleOrNull(), "Expected one log message; captured: $messages")
for (privateValue in listOf(privateText, "private-referrer", "private.caller.package", "1234567")) {
assertFalse(message.contains(privateValue), "Launch diagnostics leaked $privateValue")
}
@@ -89,7 +97,7 @@ class ActivityLaunchLoggerTest {
mockActivity(intent).logActivityCreation(null)
- val message = messages.single()
+ val message = assertNotNull(messages.singleOrNull(), "Expected one log message; captured: $messages")
assertTrue(message.contains(Intent.ACTION_VIEW))
assertTrue(message.contains(Intent.CATEGORY_LAUNCHER))
assertTrue(message.contains(EXTRA_FRAGMENT_NAME))
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/multiprofile/ProfileManagerTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/multiprofile/ProfileManagerTest.kt
index 60e6e9bb55de..000364a20030 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/multiprofile/ProfileManagerTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/multiprofile/ProfileManagerTest.kt
@@ -35,6 +35,7 @@ import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import java.io.File
+import kotlin.test.assertNotNull
import kotlin.test.assertNull
@RunWith(AndroidJUnit4::class)
@@ -54,6 +55,7 @@ class ProfileManagerTest {
@After
fun tearDown() {
unmockkAll()
+ ProfileManager.resetForTesting()
}
@Test
@@ -63,6 +65,40 @@ class ProfileManagerTest {
assertEquals("default", prefs.getString(KEY_LAST_ACTIVE_PROFILE_ID, null))
}
+ @Test
+ fun `createOrNull returns a manager on the active profile context`() {
+ val manager = assertNotNull(ProfileManager.createOrNull(context))
+
+ assertTrue("Expected a profile context", manager.activeProfileContext is ProfileContextWrapper)
+ assertNull(ProfileManager.attachError)
+ assertEquals(context.filesDir.absolutePath, manager.activeProfileContext.filesDir.absolutePath)
+ }
+
+ @Test
+ fun `createOrNull initializes the Default profile on first run`() {
+ ProfileManager.createOrNull(context)
+
+ assertEquals("default", prefs.getString(KEY_LAST_ACTIVE_PROFILE_ID, null))
+ }
+
+ @Test
+ fun `createOrNull returns null when the profile environment cannot be loaded`() {
+ val unusableStorage =
+ object : ContextWrapper(context) {
+ override fun getApplicationContext(): Context? = null
+
+ override fun getSharedPreferences(
+ name: String,
+ mode: Int,
+ ): SharedPreferences = throw IllegalStateException("storage unavailable")
+ }
+
+ val manager = ProfileManager.createOrNull(unusableStorage)
+
+ assertNull(manager, "must not brick startup")
+ assertNotNull(ProfileManager.attachError, "Failure must be recorded for later logging")
+ }
+
@Test
fun `create works before the application context is available`() {
val duringAttachBaseContext =
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/pages/CsvImporterTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/pages/CsvImporterTest.kt
new file mode 100644
index 000000000000..02019d11e7dc
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/pages/CsvImporterTest.kt
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.pages
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.RobolectricTest
+import com.ichi2.anki.SingleFragmentActivity
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+@RunWith(AndroidJUnit4::class)
+class CsvImporterTest : RobolectricTest() {
+ @Test
+ fun `acknowledging an import error closes the importer`() =
+ runTest {
+ val activity =
+ startActivityNormallyOpenCollectionWithIntent(
+ SingleFragmentActivity::class.java,
+ CsvImporter.getIntent(targetContext, "/missing.csv"),
+ )
+ val response = activity.handleUiPostRequest("importDialogRequireClose", byteArrayOf())
+
+ assertEquals(UiPostRequestResponse.Handled(byteArrayOf()), response)
+ assertTrue(activity.isFinishing)
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/pages/PostRequestHandlerTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/pages/PostRequestHandlerTest.kt
index 524865bf08fd..04daa5b2530a 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/pages/PostRequestHandlerTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/pages/PostRequestHandlerTest.kt
@@ -5,7 +5,6 @@ package com.ichi2.anki.pages
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.RobolectricTest
-import com.ichi2.testutils.HamcrestUtils.containsInAnyOrder
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.empty
import org.hamcrest.Matchers.not
@@ -18,11 +17,11 @@ import kotlin.test.assertNotNull
class PostRequestHandlerTest : RobolectricTest() {
@Test
fun `All backend typescript functions should be handled`() {
+ // TODO: Reset this after Anki 26.09 is merged
assertThat(
"Mapping exists for every TS backend function call",
- typescriptFunctionsUsedByBackend,
- // this matcher asserts equality in everything but order, no extras, nothing missing
- containsInAnyOrder((collectionMethods + uiMethods).keys),
+ typescriptFunctionsUsedByBackend - (collectionMethods + uiMethods).keys,
+ empty(),
)
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModelTest.kt
index 26dbda87dc47..c4a7960974a3 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModelTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModelTest.kt
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.assertNull
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import kotlin.properties.Delegates.notNull
+import kotlin.test.assertEquals
import kotlin.test.assertNotNull
/**
@@ -40,6 +41,13 @@ import kotlin.test.assertNotNull
class ImageOcclusionViewModelTest : JvmTest() {
private var deckIdToSwitchTo by notNull()
+ @Test
+ fun `IDs beyond JavaScript number precision are serialized as decimal strings`() {
+ val id = 9007199254740993L
+ assertEquals(id.toString(), ImageOcclusionArgs.Edit(id).toImageOcclusionMode().get("noteId"))
+ assertEquals(id.toString(), ImageOcclusionArgs.Add("/image.png", id, 1).toImageOcclusionMode().get("notetypeId"))
+ }
+
@Before
override fun setUp() {
super.setUp()
@@ -140,7 +148,7 @@ class ImageOcclusionViewModelTest : JvmTest() {
{
"kind": "add",
"imagePath": "/",
- "notetypeId": 12
+ "notetypeId": "12"
}
"""
@@ -154,7 +162,7 @@ class ImageOcclusionViewModelTest : JvmTest() {
val expected = """
{
"kind": "edit",
- "noteId": 12
+ "noteId": "12"
}
"""
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/preferences/ControlsSettingsFragmentTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/preferences/ControlsSettingsFragmentTest.kt
index a31e56670403..a04d84c92bfa 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/preferences/ControlsSettingsFragmentTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/preferences/ControlsSettingsFragmentTest.kt
@@ -2,12 +2,26 @@
package com.ichi2.anki.preferences
+import android.content.DialogInterface
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.R
import com.ichi2.anki.RobolectricTest
+import com.ichi2.anki.cardviewer.Gesture
+import com.ichi2.anki.common.destinations.DeferredNavigation
+import com.ichi2.anki.reviewer.Binding
+import com.ichi2.anki.reviewer.CardSide
+import com.ichi2.preferences.ReviewerControlPreference
import com.ichi2.testutils.HamcrestUtils
+import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.hasItem
+import org.hamcrest.Matchers.not
import org.junit.Test
import org.junit.runner.RunWith
+import org.robolectric.shadows.ShadowDialog
+import kotlin.test.assertNull
@RunWith(AndroidJUnit4::class)
class ControlsSettingsFragmentTest : RobolectricTest() {
@@ -28,4 +42,79 @@ class ControlsSettingsFragmentTest : RobolectricTest() {
assertThat(xmlKeys, HamcrestUtils.containsInAnyOrder(enumKeys))
}
}
+
+ /**
+ * Tests for alert dialog behavior in [ControlsSettingsFragment] using 'swipe up' gesture as the input binding.
+ */
+ @Test
+ fun `skip button binding only to answer`() {
+ withControlsSettings {
+ val binding = Binding.GestureInput(Gesture.SWIPE_UP)
+ answerPref.setBinding(binding)
+ clickAlertDialogButton(DialogInterface.BUTTON_NEGATIVE, checkDismissed = true)
+
+ val showAnswerBindings = showAnswerPref.getMappableBindings().map { Pair(it.binding, it.side) }
+ val answerBindings = answerPref.getMappableBindings().map { Pair(it.binding, it.side) }
+ assertThat("binding is assigned to Show answer", showAnswerBindings, not(hasItem(equalTo(Pair(binding, CardSide.QUESTION)))))
+ assertThat("binding not assigned to answer", answerBindings, hasItem(equalTo(Pair(binding, CardSide.ANSWER))))
+ }
+ }
+
+ @Test
+ fun `assign buttons binding to both answer and show answer`() {
+ withControlsSettings {
+ val binding = Binding.GestureInput(Gesture.SWIPE_UP)
+ answerPref.setBinding(binding)
+ clickAlertDialogButton(DialogInterface.BUTTON_POSITIVE, checkDismissed = true)
+
+ val showAnswerBindings = showAnswerPref.getMappableBindings().map { Pair(it.binding, it.side) }
+ val answerBindings = answerPref.getMappableBindings().map { Pair(it.binding, it.side) }
+ assertThat("binding not assigned to Show answer", showAnswerBindings, hasItem(equalTo(Pair(binding, CardSide.QUESTION))))
+ assertThat("binding not assigned to answer", answerBindings, hasItem(equalTo(Pair(binding, CardSide.ANSWER))))
+ }
+ }
+
+ @Test
+ fun `no dialog if binding is already assigned to show answer`() {
+ withControlsSettings {
+ val binding = Binding.GestureInput(Gesture.SWIPE_UP)
+ showAnswerPref.addBinding(binding, CardSide.QUESTION)
+
+ answerPref.setBinding(binding)
+
+ assertNull(ShadowDialog.getLatestDialog(), "no dialog should be shown")
+ val showAnswerBindings = showAnswerPref.getMappableBindings().map { Pair(it.binding, it.side) }
+ val answerBindings = answerPref.getMappableBindings().map { Pair(it.binding, it.side) }
+ assertThat("binding not kept on Show answer", showAnswerBindings, hasItem(Pair(binding, CardSide.QUESTION)))
+ assertThat("binding not assigned to answer", answerBindings, hasItem(Pair(binding, CardSide.ANSWER)))
+ }
+ }
+}
+
+private val ControlsSettingsFragment.showAnswerPref
+ get() = requirePreference(getString(R.string.show_answer_command_key))
+
+private val ControlsSettingsFragment.answerPref
+ get() = requirePreference(getString(R.string.answer_good_command_key))
+
+/**
+ * launches the [ControlsSettingsFragment] and runs [block] on it.
+ */
+context(_: DeferredNavigation)
+private fun withControlsSettings(block: ControlsSettingsFragment.() -> Unit) {
+ val intent =
+ PreferencesActivity.getIntent(
+ ApplicationProvider.getApplicationContext(),
+ ControlsSettingsFragment::class,
+ )
+ ActivityScenario.launch(intent).use { scenario ->
+ scenario.onActivity { activity ->
+ val preferencesFragment = activity.fragment as PreferencesFragment
+ val controlsSettingsFragment =
+ preferencesFragment.childFragmentManager.findFragmentByTag(
+ ControlsSettingsFragment::class.java.name,
+ ) as ControlsSettingsFragment
+ controlsSettingsFragment.block()
+ }
+ }
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/progress/ProgressObserverTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/progress/ProgressObserverTest.kt
index ef33cb102d41..86ac6b24bef1 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/progress/ProgressObserverTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/progress/ProgressObserverTest.kt
@@ -6,6 +6,7 @@ import android.os.Looper
import android.widget.TextView
import androidx.fragment.app.DialogFragment
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.dialogs.LoadingDialogFragment
@@ -99,7 +100,7 @@ class ProgressObserverTest : RobolectricTest() {
val controller = startActivity()
val activity = controller.get()
activity.observeProgress(viewModel, delayMillis = SHOW_DELAY) { message ->
- getString(R.string.progress_amount_bytes, message, "2 MB")
+ getString(CommonString.progress_amount_bytes, message, "2 MB")
}
val gate = CompletableDeferred()
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingResolveActionTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingResolveActionTest.kt
index aebd4eb4f114..c78c580b9016 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingResolveActionTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/reviewreminders/ReminderTroubleshootingResolveActionTest.kt
@@ -11,7 +11,7 @@ import androidx.core.net.toUri
import androidx.core.view.children
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.databinding.ItemTroubleshootingCheckBinding
import com.ichi2.testutils.withRequestIgnoreBatteryOptimizationsInManifest
@@ -90,7 +90,7 @@ class ReminderTroubleshootingResolveActionTest : RobolectricTest() {
/** The displayed 'Battery optimization' check */
private val ReminderTroubleshootingFragment.batteryOptimizationCheck: ItemTroubleshootingCheckBinding
get() {
- val title = requireContext().getString(R.string.reminder_troubleshooting_check_battery_optimization)
+ val title = requireContext().getString(CommonString.reminder_troubleshooting_check_battery_optimization)
return binding.checksList.children
.map { ItemTroubleshootingCheckBinding.bind(it) }
.single { it.title.text.toString() == title }
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivityTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivityTest.kt
index 1a46a39fb65f..5d352c5cf556 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivityTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/permissions/PermissionsActivityTest.kt
@@ -10,6 +10,7 @@ import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ActivityScenario.ActivityAction
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.CommonString
import com.ichi2.anki.OptionalPermissionSet
import com.ichi2.anki.PermissionSet
import com.ichi2.anki.R
@@ -48,7 +49,7 @@ class PermissionsActivityTest : RobolectricTest() {
testInvalidActivityFinishes()
assertThat(
ShadowToast.getTextOfLatestToast(),
- equalTo(getResourceString(R.string.something_wrong)),
+ equalTo(getResourceString(CommonString.something_wrong)),
)
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragmentTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragmentTest.kt
index 77061480769d..425cc76235ad 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragmentTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragmentTest.kt
@@ -2,9 +2,12 @@
package com.ichi2.anki.ui.windows.reviewer
+import android.view.View
+import android.webkit.WebView
import androidx.core.content.edit
import androidx.fragment.app.DialogFragment
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.R
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.cardviewer.Gesture
import com.ichi2.anki.common.preferences.sharedPrefs
@@ -15,9 +18,11 @@ import com.ichi2.anki.reviewer.MappableBinding.Companion.toPreferenceString
import com.ichi2.anki.reviewer.ReviewerBinding
import com.ichi2.anki.scheduling.SetDueDateDialog
import com.ichi2.anki.scheduling.singleDayText
+import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.utils.ext.DIALOG_FRAGMENT_TAG
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
+import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
@@ -35,6 +40,64 @@ import kotlin.test.assertTrue
class ReviewerFragmentTest : RobolectricTest() {
override fun getCollectionStorageMode() = CollectionStorageMode.IN_MEMORY_WITH_MEDIA
+ @After
+ fun resetSoftwareRendering() {
+ Prefs.sharedPrefs.edit {
+ remove(Prefs.key(R.string.disable_hardware_render_key))
+ }
+ }
+
+ @Test
+ fun `software rendering is applied to the study screen when enabled`() =
+ runTest {
+ addBasicNote()
+ Prefs.useSoftwareRendering = true
+
+ withReviewer {
+ assertRenderingLayerType(View.LAYER_TYPE_SOFTWARE)
+ }
+ }
+
+ @Test
+ fun `software rendering is reapplied after the WebView is recreated`() =
+ runTest {
+ addBasicNote()
+ Prefs.useSoftwareRendering = true
+
+ withReviewer {
+ val webViewLayout = binding.webViewLayout
+ val originalWebView = assertIs(webViewLayout.getChildAt(0))
+ webViewLayout.onRenderProcessGone(originalWebView)
+
+ assertNotSame(originalWebView, webViewLayout.getChildAt(0))
+ assertRenderingLayerType(View.LAYER_TYPE_SOFTWARE)
+ }
+ }
+
+ @Test
+ fun `default rendering is preserved when software rendering is disabled`() =
+ runTest {
+ addBasicNote()
+ Prefs.useSoftwareRendering = false
+
+ withReviewer {
+ assertRenderingLayerType(View.LAYER_TYPE_NONE)
+ }
+ }
+
+ @Test
+ fun `default rendering is preserved when the rendering preference is unset`() =
+ runTest {
+ addBasicNote()
+ Prefs.sharedPrefs.edit {
+ remove(Prefs.key(R.string.disable_hardware_render_key))
+ }
+
+ withReviewer {
+ assertRenderingLayerType(View.LAYER_TYPE_NONE)
+ }
+ }
+
@Test
fun `shaking does not stack set due date dialogs`() = assertShakeDoesNotStackDialogs(ViewerAction.RESCHEDULE_NOTE)
@@ -113,6 +176,13 @@ class ReviewerFragmentTest : RobolectricTest() {
}
}
+ private fun ReviewerFragment.assertRenderingLayerType(expected: Int) {
+ assertEquals(expected, requireView().layerType, "study screen root")
+ assertEquals(expected, binding.backButton.layerType, "toolbar button")
+ assertEquals(expected, binding.answerArea.layerType, "answer buttons")
+ assertEquals(expected, assertIs(binding.webViewLayout.getChildAt(0)).layerType, "card WebView")
+ }
+
private suspend fun TestScope.openEditTags(reviewer: ReviewerFragment): TagsDialog {
reviewer.viewModel.executeAction(ViewerAction.TAG)
advanceUntilIdle()
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerShakeTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerShakeTest.kt
index 3ea33518cfc2..73c588e455ab 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerShakeTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/ReviewerShakeTest.kt
@@ -66,9 +66,13 @@ class ReviewerShakeTest : RobolectricTest() {
}
scenario.recreate()
- advanceRobolectricLooper()
+ scenario.withSetDueDate {
+ assertEquals(listOf(cardId), cardIds)
+ // The next shake must open a new dialog, not find the restored one.
+ dismissNow()
+ }
+ assertEquals(1, sensors.listeners.size)
}
- assertEquals(1, sensors.listeners.size)
}
assertEquals(0, sensors.listeners.size)
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayViewTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayViewTest.kt
index 6a171ccd9a2a..2ac86feb47b1 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayViewTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioPlayViewTest.kt
@@ -7,6 +7,7 @@ import android.content.Context
import android.os.Looper
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.databinding.ViewAudioPlayBinding
import com.ichi2.themes.Themes
@@ -32,12 +33,12 @@ class AudioPlayViewTest {
@Test
fun `play button exposes a label to TalkBack`() {
- assertEquals(context.getString(R.string.play_recording), binding.playButton.contentDescription)
+ assertEquals(context.getString(CommonString.play_recording), binding.playButton.contentDescription)
}
@Test
fun `cancel button exposes a label to TalkBack`() {
- assertEquals(context.getString(R.string.dialog_cancel), binding.cancelButton.contentDescription)
+ assertEquals(context.getString(CommonString.dialog_cancel), binding.cancelButton.contentDescription)
}
@Test
@@ -45,6 +46,6 @@ class AudioPlayViewTest {
audioPlayView.changePlayIcon(R.drawable.ic_replay)
shadowOf(Looper.getMainLooper()).idle()
- assertEquals(context.getString(R.string.replay_voice), binding.playButton.contentDescription)
+ assertEquals(context.getString(CommonString.replay_voice), binding.playButton.contentDescription)
}
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioRecordViewTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioRecordViewTest.kt
index 363287a5795f..da60feb7620d 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioRecordViewTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/ui/windows/reviewer/audiorecord/AudioRecordViewTest.kt
@@ -6,7 +6,7 @@ package com.ichi2.anki.ui.windows.reviewer.audiorecord
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.databinding.ViewAudioRecordBinding
import com.ichi2.themes.Themes
import org.junit.Before
@@ -30,6 +30,6 @@ class AudioRecordViewTest {
@Test
fun `record button exposes a label to TalkBack`() {
- assertEquals(context.getString(R.string.record_voice), binding.recordButton.contentDescription)
+ assertEquals(context.getString(CommonString.record_voice), binding.recordButton.contentDescription)
}
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/utils/ext/ContainsFileTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/utils/ext/ContainsFileTest.kt
new file mode 100644
index 000000000000..bfafd00ce183
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/utils/ext/ContainsFileTest.kt
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: 2026 Ashish Yadav
+// SPDX-License-Identifier: GPL-3.0-or-later
+package com.ichi2.anki.utils.ext
+
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import java.io.File
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+/** Tests for [File.containsFile] */
+class ContainsFileTest {
+ @get:Rule
+ val temp = TemporaryFolder()
+
+ @Test
+ fun `finds an existing file`() {
+ temp.newFile("collection.anki2")
+ assertTrue(temp.root.containsFile("collection.anki2"))
+ }
+
+ @Test
+ fun `false when the file does not exist`() {
+ assertFalse(temp.root.containsFile("collection.anki2"))
+ }
+
+ @Test
+ fun `false when the name points to a directory`() {
+ temp.newFolder("collection.anki2")
+ assertFalse(temp.root.containsFile("collection.anki2"))
+ }
+
+ @Test
+ fun `finds a file in a subdirectory`() {
+ temp.newFolder("sub")
+ temp.newFile("sub/collection.anki2")
+ assertTrue(temp.root.containsFile("sub/collection.anki2"))
+ }
+
+ @Test
+ fun `false for a path escaping the directory`() {
+ temp.newFile("secret.txt")
+ val sub = temp.newFolder("sub")
+ assertFalse(sub.containsFile("../secret.txt"))
+ }
+
+ @Test
+ fun `false for an empty name`() {
+ assertFalse(temp.root.containsFile(""))
+ }
+
+ @Test
+ fun `false when the receiver is not a directory`() {
+ val file = temp.newFile("plain.txt")
+ assertFalse(file.containsFile("anything"))
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/widget/DeckAdapterTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/widget/DeckAdapterTest.kt
index 2dec0d50e1c4..72029240db1f 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/widget/DeckAdapterTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/widget/DeckAdapterTest.kt
@@ -23,6 +23,7 @@ import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.awaitDeckHolder
@@ -186,7 +187,7 @@ class DeckAdapterTest : RobolectricTest() {
assertTrue(changePayloads.all { it != null }, "A full row update interrupts the arrow ripple")
assertSame(parent, deckPicker.awaitDeckHolder(parentDeck))
assertEquals(
- deckPicker.getString(if (wasCollapsed) R.string.collapse else R.string.expand),
+ deckPicker.getString(if (wasCollapsed) CommonString.collapse else CommonString.expand),
parent.binding.deckExpander.contentDescription,
)
changePayloads.clear()
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt
index 6787514f821d..cb379bc0603c 100644
--- a/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/anki/widget/cardanalysis/CardAnalysisWidgetConfigTest.kt
@@ -28,6 +28,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.CommonString
import com.ichi2.anki.R
import com.ichi2.anki.RobolectricTest
import com.ichi2.widget.AppWidgetId
@@ -90,7 +91,7 @@ class CardAnalysisWidgetConfigTest : RobolectricTest() {
// first setup of the widget, no deck selected so deck name view should be empty
onView(withId(R.id.deck_name)).check(matches(withText("")))
// select dialog already open, select deck 1
- onView(withText(R.string.select_deck_title))
+ onView(withText(CommonString.select_deck_title))
.inRoot(isDialog())
.check(matches(isDisplayed()))
onView(withText(TEST_DECK_NAME1)).inRoot(isDialog()).perform(click())
@@ -129,7 +130,7 @@ class CardAnalysisWidgetConfigTest : RobolectricTest() {
// no selection yet
onView(withId(R.id.deck_name)).check(matches(withText("")))
// cancel deck selection
- onView(withText(R.string.dialog_cancel)).inRoot(isDialog()).perform(click())
+ onView(withText(CommonString.dialog_cancel)).inRoot(isDialog()).perform(click())
// still no deck selected, so deck name should still be empty
onView(withId(R.id.deck_name)).check(matches(withText("")))
// set configuration as done
diff --git a/AnkiDroid/src/test/java/com/ichi2/preferences/ExternalDirectorySelectionPreferenceTest.kt b/AnkiDroid/src/test/java/com/ichi2/preferences/ExternalDirectorySelectionPreferenceTest.kt
new file mode 100644
index 000000000000..efecc5bfbdcb
--- /dev/null
+++ b/AnkiDroid/src/test/java/com/ichi2/preferences/ExternalDirectorySelectionPreferenceTest.kt
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-FileCopyrightText: 2026 Shaan Narendran
+
+package com.ichi2.preferences
+
+import androidx.core.content.edit
+import androidx.preference.PreferenceManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.ichi2.anki.RobolectricTest
+import com.ichi2.anki.common.preferences.sharedPrefs
+import com.ichi2.anki.common.storage.CollectionHelper
+import com.ichi2.anki.startup.getDefaultAnkiDroidDirectory
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.equalTo
+import org.hamcrest.Matchers.hasItem
+import org.hamcrest.Matchers.instanceOf
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+/**
+ * Tests that [ExternalDirectorySelectionPreference] offers the directories a user
+ * would expect to pick from.
+ */
+@RunWith(AndroidJUnit4::class)
+class ExternalDirectorySelectionPreferenceTest : RobolectricTest() {
+ @Test
+ fun `the default directory is offered`() {
+ val pref = buildPreference()
+
+ assertThat(pref.entryPaths(), hasItem(getDefaultAnkiDroidDirectory(targetContext).absolutePath))
+ }
+
+ @Test
+ fun `the current directory is offered`() {
+ val current = File(targetContext.filesDir, "AnkiDroidTest").absolutePath
+ targetContext.sharedPrefs().edit(commit = true) {
+ putString(CollectionHelper.PREF_COLLECTION_PATH, current)
+ }
+
+ val pref = buildPreference()
+
+ assertThat(pref.entryPaths(), hasItem(current))
+ }
+
+ @Test
+ fun `each entry has a label`() {
+ val pref = buildPreference()
+ pref.makeDialogFragment()
+
+ assertThat(pref.entries.size, equalTo(pref.entryValues.size))
+ }
+
+ @Test
+ fun `the dialog is the full width list`() {
+ val pref = buildPreference()
+
+ assertThat(pref.makeDialogFragment(), instanceOf(FullWidthListPreferenceDialogFragment::class.java))
+ }
+
+ /** Builds the preference attached to a screen, as the settings screen does */
+ private fun buildPreference(): ExternalDirectorySelectionPreference {
+ val pref = ExternalDirectorySelectionPreference(targetContext, null)
+ pref.key = CollectionHelper.PREF_COLLECTION_PATH
+ val preferenceManager = PreferenceManager(targetContext)
+ val screen = preferenceManager.createPreferenceScreen(targetContext)
+ preferenceManager.setPreferences(screen)
+ screen.addPreference(pref)
+ return pref
+ }
+
+ /** The paths offered by the dialog */
+ private fun ExternalDirectorySelectionPreference.entryPaths(): List {
+ makeDialogFragment()
+ return entryValues.map { it.toString() }
+ }
+}
diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.kt b/AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.kt
index 54c8da48e48c..8d66ac3af50d 100644
--- a/AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.kt
@@ -10,7 +10,7 @@ import android.content.ContextWrapper
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.common.utils.ext.requireSystemService
import com.ichi2.utils.ClipboardUtil.AUDIO_MIME_TYPES
import com.ichi2.utils.ClipboardUtil.IMAGE_MIME_TYPES
@@ -146,7 +146,7 @@ class ClipboardUtilTest {
assertTrue(context.copyToClipboard(TruncatedString.from("hello")))
assertThat(
ShadowToast.getTextOfLatestToast(),
- equalTo(context.getString(R.string.about_ankidroid_successfully_copied_debug_info)),
+ equalTo(context.getString(CommonString.about_ankidroid_successfully_copied_debug_info)),
)
}
@@ -163,7 +163,7 @@ class ClipboardUtilTest {
assertFalse(contextWithClipboard(null).copyToClipboard(TruncatedString.from("hello")))
assertThat(
ShadowToast.getTextOfLatestToast(),
- equalTo(context.getString(R.string.failed_to_copy)),
+ equalTo(context.getString(CommonString.failed_to_copy)),
)
}
@@ -177,7 +177,7 @@ class ClipboardUtilTest {
assertFalse(contextWithClipboard(throwingClipboard).copyToClipboard(TruncatedString.from("hello")))
assertThat(
ShadowToast.getTextOfLatestToast(),
- equalTo(context.getString(R.string.failed_to_copy)),
+ equalTo(context.getString(CommonString.failed_to_copy)),
)
}
diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/ImportUtilsTest.kt b/AnkiDroid/src/test/java/com/ichi2/utils/ImportUtilsTest.kt
index 9ebccb5f75bb..f17f298d4ac8 100644
--- a/AnkiDroid/src/test/java/com/ichi2/utils/ImportUtilsTest.kt
+++ b/AnkiDroid/src/test/java/com/ichi2/utils/ImportUtilsTest.kt
@@ -11,7 +11,7 @@ import android.net.Uri
import androidx.annotation.CheckResult
import androidx.core.net.toUri
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.ichi2.anki.R
+import com.ichi2.anki.CommonString
import com.ichi2.anki.RobolectricTest
import com.ichi2.anki.common.crashreporting.CrashReportService
import com.ichi2.anki.common.crashreporting.CrashReporter
@@ -67,7 +67,7 @@ class ImportUtilsTest : RobolectricTest() {
val failure = assertIs(result)
assertIs(failure.exception)
- assertEquals(targetContext.getString(R.string.import_error_copy_to_cache_title), failure.title)
+ assertEquals(targetContext.getString(CommonString.import_error_copy_to_cache_title), failure.title)
verifyNoInteractions(crashReporter)
}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index c5b20ad42642..6d1e48f82be4 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -19,6 +19,12 @@ dependencies {
implementation("com.android.tools.build:gradle:${libs.versions.androidGradlePlugin.get()}")
// Force the catalog version of KGP, otherwise it's overridden by AGP.
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin.get()}")
+ testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform.launcher)
+}
+
+tasks.test {
+ useJUnitPlatform()
}
configure {
diff --git a/buildSrc/src/main/kotlin/com/ichi2/anki/gradle/VersionCode.kt b/buildSrc/src/main/kotlin/com/ichi2/anki/gradle/VersionCode.kt
new file mode 100644
index 000000000000..a3e238bb1ff7
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/ichi2/anki/gradle/VersionCode.kt
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.gradle
+
+object VersionCode {
+ private const val MAX_BUMP = 100_000
+
+ // Consume whole comments atomically so dots inside them cannot look like chained calls.
+ private const val WHITESPACE_OR_COMMENTS = """(?>\s|//[^\r\n]*|/\*[\s\S]*?\*/)*"""
+
+ // Include chained calls across whitespace/comments so a numeric prefix cannot pass as a literal.
+ private val assignment = Regex("""^\h*versionCode\b\h*=?\s*([^\r\n]+(?:\R$WHITESPACE_OR_COMMENTS\.[^\r\n]*)*)""", RegexOption.MULTILINE)
+ private val literal = Regex("""([1-9][0-9]*)\h*;?\h*(?://.*)?""")
+
+ /**
+ * Accepted formats, using digits only with no leading zeros:
+ * - Groovy: `versionCode 22500202`
+ * - Kotlin: `versionCode = 22500202`
+ * - Whitespace: ` versionCode=22500202`
+ * - Comment: `versionCode = 22500202 // beta`
+ */
+ fun read(buildScript: String): Int {
+ val declaration = assignment.findAll(buildScript).singleOrNull()
+ val number = declaration?.let { literal.matchEntire(it.groupValues[1].trim()) }
+ return requireNotNull(number?.groupValues?.get(1)?.toIntOrNull()) {
+ "Expected exactly one versionCode declaration with a positive integer literal (digits only, no leading zeros), e.g. versionCode = 22500202."
+ }
+ }
+
+ /** Checks the increase in the base code, before the ABI prefix is added. */
+ fun validate(
+ previous: Int,
+ code: Int?,
+ ) {
+ require(previous > 0 && code != null && code > 0) {
+ "Previous and current versionCode must be positive: $previous -> $code"
+ }
+ val increase = code.toLong() - previous.toLong()
+ require(increase in 0L..MAX_BUMP.toLong()) {
+ "versionCode increase must be between 0 and $MAX_BUMP: " +
+ "$previous -> $code (increase $increase)."
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/com/ichi2/anki/gradle/VersionCodeTasks.kt b/buildSrc/src/main/kotlin/com/ichi2/anki/gradle/VersionCodeTasks.kt
new file mode 100644
index 000000000000..9c01cebdc8c6
--- /dev/null
+++ b/buildSrc/src/main/kotlin/com/ichi2/anki/gradle/VersionCodeTasks.kt
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.provider.Provider
+import org.gradle.api.provider.ProviderFactory
+import org.gradle.api.tasks.TaskProvider
+import java.io.File
+
+object VersionCodeTasks {
+ @JvmStatic
+ fun registerValidationTask(
+ project: Project,
+ versionCode: Int?,
+ ): TaskProvider =
+ with(project) {
+ val explicitBase = providers.gradleProperty("versionCodeBase")
+ val baseCode = atRevision(providers, rootDir, explicitBase.getOrElse("HEAD^"))
+ val headCode = atRevision(providers, rootDir, "HEAD")
+ val currentScript = providers.fileContents(layout.projectDirectory.file(buildFile.name)).asText
+ tasks.register("validateVersionCode") {
+ group = "verification"
+ description = "Validates the versionCode bump."
+ doLast {
+ // Require a literal in the current script as well as the comparison revision.
+ VersionCode.read(currentScript.get())
+ // Use CI's base, or compare local changes to HEAD (HEAD^ once committed).
+ val previousCode = if (explicitBase.isPresent || headCode.get() == versionCode) baseCode.get() else headCode.get()
+ VersionCode.validate(previousCode, versionCode)
+ }
+ }
+ }
+
+ /** Read whichever script exists at the comparison revision, including across a Kotlin DSL migration. */
+ private fun atRevision(
+ providers: ProviderFactory,
+ rootDir: File,
+ revision: String,
+ ): Provider {
+ val scripts =
+ listOf("AnkiDroid/build.gradle", "AnkiDroid/build.gradle.kts").map { path ->
+ providers.exec {
+ workingDir(rootDir)
+ commandLine("git", "show", "$revision:$path")
+ isIgnoreExitValue = true
+ }
+ }
+ return providers.provider {
+ val script =
+ requireNotNull(scripts.singleOrNull { it.result.get().exitValue == 0 }) {
+ "Expected exactly one AnkiDroid build.gradle or build.gradle.kts at '$revision'."
+ }
+ VersionCode.read(script.standardOutput.asText.get())
+ }
+ }
+}
diff --git a/buildSrc/src/test/kotlin/com/ichi2/anki/gradle/VersionCodeTest.kt b/buildSrc/src/test/kotlin/com/ichi2/anki/gradle/VersionCodeTest.kt
new file mode 100644
index 000000000000..920fe07c81d8
--- /dev/null
+++ b/buildSrc/src/test/kotlin/com/ichi2/anki/gradle/VersionCodeTest.kt
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package com.ichi2.anki.gradle
+
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+
+class VersionCodeTest {
+ @Test
+ fun `accepts Groovy Kotlin whitespace and comment formats`() {
+ for (script in listOf(
+ "versionCode 22500202", // Groovy
+ "versionCode = 22500202", // Kotlin
+ " versionCode=\n 22500202;\n", // Whitespace
+ "versionCode = 22500202 // beta", // Comment
+ )) {
+ assertEquals(22500202, VersionCode.read(script))
+ }
+ }
+
+ @Test
+ fun `rejects missing ambiguous and unsupported versionCode declarations`() {
+ for (script in listOf(
+ "",
+ "versionCode = appVersionCode",
+ "versionCode = 22_500_202",
+ "versionCode = 22500202 + 1",
+ "versionCode = 22500202.toInt()",
+ "versionCode = 22500202\n .toInt()",
+ "versionCode=22500202\nversionCode=22500203",
+ "versionCode=22500202\nversionCode=appVersionCode",
+ "versionCode=2147483648",
+ )) {
+ val error = assertThrows(IllegalArgumentException::class.java) { VersionCode.read(script) }
+ assertTrue(error.message!!.contains("literal"))
+ }
+ }
+
+ @Test
+ fun `rejects zero and leading-zero literals`() {
+ // can be parsed as octal
+ for (literal in listOf("0100000", "022500202", "001", "0")) {
+ assertThrows(IllegalArgumentException::class.java) { VersionCode.read("versionCode = $literal") }
+ }
+ }
+
+ @Test
+ fun `rejects chained calls after whitespace or comments`() {
+ for (gap in listOf("\n\n", "\n// adjustment\n", "\n/* adjustment\n * continues here */\n")) {
+ assertThrows(IllegalArgumentException::class.java) { VersionCode.read("versionCode = 22500202$gap .plus(1)") }
+ }
+ }
+
+ @Test
+ fun `ignores dots in comments following a literal`() {
+ for (comment in listOf("// See example.com", "/* See example.com */")) {
+ assertEquals(22500202, VersionCode.read("versionCode = 22500202\n$comment\nversionName = \"2.25.0beta2\""))
+ }
+ }
+
+ @Test
+ fun `accepts unchanged codes one increment and the maximum bump`() {
+ VersionCode.validate(previous = 22500202, code = 22500202)
+ VersionCode.validate(previous = 22500202, code = 22500203)
+ VersionCode.validate(previous = 22500300, code = 22600300)
+ }
+
+ @Test
+ fun `rejects oversized and decreasing codes`() {
+ for (code in listOf(22600203, 22500201, Int.MAX_VALUE)) {
+ assertThrows(IllegalArgumentException::class.java) { VersionCode.validate(22500202, code) }
+ }
+ }
+
+ @Test
+ fun `rejects a missing code`() {
+ assertThrows(IllegalArgumentException::class.java) { VersionCode.validate(22500202, null) }
+ }
+}
diff --git a/common/android/src/main/java/com/ichi2/anki/common/crashreporting/CrashReportService.kt b/common/android/src/main/java/com/ichi2/anki/common/crashreporting/CrashReportService.kt
index 23c1167b6ada..e2fd68739177 100644
--- a/common/android/src/main/java/com/ichi2/anki/common/crashreporting/CrashReportService.kt
+++ b/common/android/src/main/java/com/ichi2/anki/common/crashreporting/CrashReportService.kt
@@ -262,3 +262,29 @@ fun runCatchingWithReport(
if (e is Error) throw e
Result.failure(e)
}
+
+/**
+ * Runs the provided block, catching [Exception] and logging it.
+ *
+ * Unlike [runCatchingWithReport], this does **not** send a crash report.
+ *
+ * **Note**: This differs from [runCatching] - `Error` is thrown
+ *
+ * @param origin Data logged to Timber
+ * @param block Code to execute
+ *
+ * @throws Error If raised, this will be rethrown
+ *
+ * @return A Result containing either the successful result of [block] or the [Exception] thrown
+ */
+fun runCatchingWithLog(
+ origin: String? = null,
+ block: () -> T,
+): Result =
+ try {
+ Result.success(block())
+ } catch (e: Throwable) {
+ Timber.w(e, origin)
+ if (e is Error) throw e
+ Result.failure(e)
+ }
diff --git a/tools/localization/yarn.lock b/tools/localization/yarn.lock
index c11c51c83c2e..93527ff95bdf 100644
--- a/tools/localization/yarn.lock
+++ b/tools/localization/yarn.lock
@@ -5993,9 +5993,9 @@ __metadata:
linkType: hard
"undici@npm:^6.25.0":
- version: 6.28.0
- resolution: "undici@npm:6.28.0"
- checksum: 10c0/3029a70df06b38b5b2f30732932a1e92544c753cd82c8abdf0d35afad48e0ba91612e79fe3a442dbbb9434d6a9eba2b714d5ea28984c903dda2b5d5444f38354
+ version: 6.29.0
+ resolution: "undici@npm:6.29.0"
+ checksum: 10c0/3fb2117985be977ba8b6bd6a242fec915e1b1d0f9681f5adb3f508fb1717352452d65bcfd3bed09e7c776cfda403a4e8edb24a318492f8b2d793439af2b8699b
languageName: node
linkType: hard
diff --git a/tools/release.sh b/tools/release.sh
index 74761c80d304..a3954f66dbb1 100755
--- a/tools/release.sh
+++ b/tools/release.sh
@@ -96,6 +96,12 @@ if [ "$PUBLIC" != "public" ]; then
sed -i -e s/versionCode="$PREVIOUS_CODE"/versionCode="$GUESSED_CODE"/g $GRADLEFILE
fi
+# Validate the resulting versionCode before committing or tagging the release.
+if ! ./gradlew :AnkiDroid:validateVersionCode --no-configuration-cache
+then
+ exit 1
+fi
+
# If any changes go in during the release process, pushing fails, so push immediately.
# Worst case this burns a version number despite a failure later, and we have a version/tag
# that never launched. That's better than having to manually patch up build.gradle.kts and push a tag
diff --git a/tools/validate-version-code.sh b/tools/validate-version-code.sh
new file mode 100755
index 000000000000..6acfd877c218
--- /dev/null
+++ b/tools/validate-version-code.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+# Usage from the repository root: tools/validate-version-code.sh [base-commit]
+
+set -euo pipefail
+
+base=${1:-HEAD^}
+if [[ "$base" =~ ^0+$ ]]; then
+ base=HEAD^
+fi
+
+# Fetch the comparison commit if it is outside the shallow checkout.
+if ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
+ git fetch --no-tags --depth=1 origin "$base"
+fi
+
+./gradlew :AnkiDroid:validateVersionCode -PversionCodeBase="$base" --daemon
diff --git a/widgets/src/main/AndroidManifest.xml b/widgets/src/main/AndroidManifest.xml
index e10007615799..670d9116afe1 100644
--- a/widgets/src/main/AndroidManifest.xml
+++ b/widgets/src/main/AndroidManifest.xml
@@ -1,4 +1,7 @@
-
+
+
+
+