From eac04584ca51432a32231b997ed76fe23b439c2e Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Sun, 28 Mar 2021 19:45:08 +0100 Subject: [PATCH 001/171] refactor: rename AnkiDroidApp.isInitialized --- AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java | 2 +- AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java index 0f58cb7e606d..21e5dc1c70b7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java @@ -605,7 +605,7 @@ protected void enableToolbar(@Nullable View view) { } protected boolean showedActivityFailedScreen(Bundle savedInstanceState) { - if (!AnkiDroidApp.isInitialized()) { + if (!AnkiDroidApp.isUninitialized()) { return false; } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index a733b14db28d..219194b4465c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -200,7 +200,7 @@ public static InputStream getResourceAsStream(@NonNull String name) { } - public static boolean isInitialized() { + public static boolean isUninitialized() { return sInstance == null; } From 7727d4ea3a95d8ad6c14593a0b21a81c1244f42f Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Sun, 28 Mar 2021 19:46:22 +0100 Subject: [PATCH 002/171] document test: activityHandlesRestoreBackup Had a user ask about this, it's unintuitive, and comments weren't linked to the implementation This should reduce confusion in future --- .../com/ichi2/anki/ActivityStartupUnderBackupTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ActivityStartupUnderBackupTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/ActivityStartupUnderBackupTest.java index 1b99dabad29c..4191232c87a4 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/ActivityStartupUnderBackupTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/ActivityStartupUnderBackupTest.java @@ -17,6 +17,7 @@ package com.ichi2.anki; import android.app.Activity; +import android.os.Bundle; import com.ichi2.anki.multimediacard.activity.LoadPronounciationActivity; import com.ichi2.anki.multimediacard.activity.TranslationActivity; @@ -67,10 +68,12 @@ public void before() { } + /** + * Tests that each activity can handle {@link AnkiDroidApp#getInstance()} returning null + * This happens during a backup, for details, see {@link AnkiActivity#showedActivityFailedScreen(Bundle)} + */ @Test public void activityHandlesRestoreBackup() { - // See: showActivityFailedScreen - AnkiDroidApp.simulateRestoreFromBackup(); ActivityController controller = mLauncher.build(getTargetContext()).create(); From c37358632bbe3fd84ae30ff536cb361783c0e2e5 Mon Sep 17 00:00:00 2001 From: Excelsior Date: Tue, 30 Mar 2021 23:35:44 +0530 Subject: [PATCH 003/171] added @NonNull annotation (#8401) --- AnkiDroid/src/main/java/com/ichi2/utils/TextViewUtil.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/TextViewUtil.java b/AnkiDroid/src/main/java/com/ichi2/utils/TextViewUtil.java index 6bf1225efc51..11be45457e9c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/TextViewUtil.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/TextViewUtil.java @@ -18,8 +18,11 @@ import android.widget.TextView; +import androidx.annotation.NonNull; + + public class TextViewUtil { - public static float getTextSizeSp(TextView first) { + public static float getTextSizeSp(@NonNull TextView first) { return first.getTextSize() / first.getResources().getDisplayMetrics().scaledDensity; } } From 5283cfa25155b2bf73cb234b22bfd874002c71ee Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Tue, 30 Mar 2021 20:09:13 +0200 Subject: [PATCH 004/171] NF: addTasks -> addTask (#8392) Because it add a single task --- AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java | 3 +-- AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index c0031a9e8622..a763c7670500 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -98,7 +98,6 @@ import static com.ichi2.libanki.Collection.DismissType.RESCHEDULE_CARDS; import static com.ichi2.libanki.Collection.DismissType.RESET_CARDS; import static com.ichi2.libanki.Collection.DismissType.SUSPEND_NOTE; -import static com.ichi2.libanki.Consts.DECK_DYN; import static com.ichi2.libanki.Undoable.*; import static com.ichi2.utils.BooleanGetter.False; import static com.ichi2.utils.BooleanGetter.True; @@ -177,7 +176,7 @@ protected CollectionTask(Task task, TaskLi mTask = task; mListener = listener; mPreviousTask = previousTask; - TaskManager.addTasks(this); + TaskManager.addTask(this); } @Override diff --git a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java index db78515d4534..240a90fb3f76 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java @@ -20,7 +20,7 @@ public class TaskManager { * */ private static final List sTasks = Collections.synchronizedList(new LinkedList<>()); - protected static void addTasks(CollectionTask task) { + protected static void addTask(CollectionTask task) { sTasks.add(task); } From 892b5c0c640bdcabaa39641a819566825865b39e Mon Sep 17 00:00:00 2001 From: Mrudul Tora Date: Wed, 31 Mar 2021 03:06:52 +0530 Subject: [PATCH 005/171] Add Unit Test for ArrayUtil (#8372) * Unit Test for ArrayUtil * Removed run with AndroidJunit4 * Removed Before annotation from the test --- .../java/com/ichi2/utils/ArrayUtilTest.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/ArrayUtilTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/ArrayUtilTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/ArrayUtilTest.java new file mode 100644 index 000000000000..09f1bb977b64 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/ArrayUtilTest.java @@ -0,0 +1,38 @@ +/* + Copyright (c) 2021 Mrudul Tora + + 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.utils; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class ArrayUtilTest { + private final Integer[] sampleItems = new Integer[] {1, 2, 3, 4, 5, 6}; + + + @Test + public void arrayToArrayList() { + List list = new ArrayList<>(); + Collections.addAll(list, sampleItems); + assertThat(ArrayUtil.toArrayList(sampleItems), is(list)); + } +} \ No newline at end of file From 1890f95f0c2116c6da93e264486795ed41e0aceb Mon Sep 17 00:00:00 2001 From: Piyush Goel <46752548+Arnold2381@users.noreply.github.com> Date: Tue, 30 Mar 2021 01:46:32 +0530 Subject: [PATCH 006/171] Updated the build link from travis to actions page --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb1e52ac9d5a..7d8b958b300c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

release -build +build Open Collective backers and sponsors commit-activity forks From c19389d77cd231b58fc44022601906e269585d74 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Tue, 30 Mar 2021 21:31:04 +0100 Subject: [PATCH 007/171] refactor: test param name --- AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java b/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java index fbd77faa2a17..941ebd949533 100644 --- a/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java @@ -46,8 +46,8 @@ public static T assertThrows(Runnable r, Class clazz) { throw new IllegalStateException("unreachable"); } - public static void assertEqualsArrayList(T[] ar, List l) { - assertEquals(Arrays.asList(ar), l); + public static void assertEqualsArrayList(T[] expected, List actual) { + assertEquals(Arrays.asList(expected), actual); } From 2b158d99cd7066792dd76155a5b05fb0cf4118cc Mon Sep 17 00:00:00 2001 From: tapish2000 <44476667+tapish2000@users.noreply.github.com> Date: Tue, 30 Mar 2021 11:42:52 +0530 Subject: [PATCH 008/171] Improve CardBrowser Accessibility - Added Spacing to Column Spinners --- AnkiDroid/src/main/res/layout/card_browser.xml | 12 ++++++------ AnkiDroid/src/main/res/values/styles.xml | 5 +++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/AnkiDroid/src/main/res/layout/card_browser.xml b/AnkiDroid/src/main/res/layout/card_browser.xml index 04d92a888e86..111cb3153b08 100644 --- a/AnkiDroid/src/main/res/layout/card_browser.xml +++ b/AnkiDroid/src/main/res/layout/card_browser.xml @@ -22,23 +22,23 @@ + android:orientation="horizontal" + style="@style/card_browser_spinner_layout"> + app:popupTheme="@style/ActionBar.Popup" /> + app:popupTheme="@style/ActionBar.Popup" /> ?android:attr/selectableItemBackground @color/black + + + From 4830272eddd47d6f66cb96c7dca85cafbf0d3807 Mon Sep 17 00:00:00 2001 From: Akshay Vilas Jadhav <52353967+Akshay0701@users.noreply.github.com> Date: Thu, 1 Apr 2021 01:20:00 +0530 Subject: [PATCH 009/171] Change FloatingActionMenu to Material library APIs (#8290) * Implemented FloatingActionMenu Without External Dependency * Fabs with animation and without animation * Created FloatingActionMenu Class Co-authored-by: Mike Hardy --- .../main/java/com/ichi2/anki/DeckPicker.java | 70 ++----- .../anki/DeckPickerFloatingActionMenu.java | 191 ++++++++++++++++++ .../com/ichi2/anki/widgets/FabBehavior.java | 22 +- .../res/drawable-hdpi/ic_add_white_24dp.png | Bin 223 -> 0 bytes .../ic_file_download_white_24dp.png | Bin 163 -> 0 bytes .../drawable-hdpi/ic_folder_white_24dp.png | Bin 135 -> 0 bytes .../res/drawable-mdpi/ic_add_white_24dp.png | Bin 174 -> 0 bytes .../ic_file_download_white_24dp.png | Bin 116 -> 0 bytes .../drawable-mdpi/ic_folder_white_24dp.png | Bin 122 -> 0 bytes .../res/drawable-xhdpi/ic_add_white_24dp.png | Bin 198 -> 0 bytes .../ic_file_download_white_24dp.png | Bin 157 -> 0 bytes .../drawable-xhdpi/ic_folder_white_24dp.png | Bin 181 -> 0 bytes .../res/drawable-xxhdpi/ic_add_white_24dp.png | Bin 222 -> 0 bytes .../ic_file_download_white_24dp.png | Bin 197 -> 0 bytes .../drawable-xxhdpi/ic_folder_white_24dp.png | Bin 245 -> 0 bytes .../ic_file_download_white_24dp.png | Bin 233 -> 0 bytes .../drawable-xxxhdpi/ic_folder_white_24dp.png | Bin 325 -> 0 bytes .../src/main/res/drawable/ic_add_white.xml | 9 + .../res/drawable/ic_file_download_white.xml | 9 + .../src/main/res/drawable/ic_folder_white.xml | 9 + .../main/res/layout/floating_add_button.xml | 184 +++++++++++++---- .../src/main/res/menu-television/reviewer.xml | 2 +- AnkiDroid/src/main/res/menu/card_browser.xml | 2 +- AnkiDroid/src/main/res/menu/model_browser.xml | 2 +- AnkiDroid/src/main/res/menu/model_editor.xml | 2 +- AnkiDroid/src/main/res/menu/reviewer.xml | 2 +- .../src/main/res/menu/tags_dialog_menu.xml | 2 +- 27 files changed, 399 insertions(+), 107 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java delete mode 100644 AnkiDroid/src/main/res/drawable-hdpi/ic_add_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-hdpi/ic_folder_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-mdpi/ic_add_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-mdpi/ic_file_download_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-mdpi/ic_folder_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xhdpi/ic_add_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xxhdpi/ic_file_download_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xxhdpi/ic_folder_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xxxhdpi/ic_file_download_white_24dp.png delete mode 100644 AnkiDroid/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png create mode 100644 AnkiDroid/src/main/res/drawable/ic_add_white.xml create mode 100644 AnkiDroid/src/main/res/drawable/ic_file_download_white.xml create mode 100644 AnkiDroid/src/main/res/drawable/ic_folder_white.xml diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 4a7e5bccda0b..093928c1ea51 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -22,6 +22,7 @@ package com.ichi2.anki; import android.Manifest; +import android.animation.Animator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -42,6 +43,7 @@ import android.provider.Settings; import com.afollestad.materialdialogs.GravityEnum; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import androidx.annotation.NonNull; @@ -82,8 +84,6 @@ import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; -import com.getbase.floatingactionbutton.FloatingActionButton; -import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.ichi2.anki.CollectionHelper.CollectionIntegrityStorageCheck; import com.ichi2.anki.StudyOptionsFragment.StudyOptionsListener; import com.ichi2.anki.analytics.UsageAnalytics; @@ -201,7 +201,7 @@ public class DeckPicker extends NavigationDrawerActivity implements private RecyclerView mRecyclerView; private LinearLayoutManager mRecyclerViewLayoutManager; private DeckAdapter mDeckListAdapter; - private FloatingActionsMenu mActionsMenu; + private final Snackbar.Callback mSnackbarShowHideCallback = new Snackbar.Callback(); private LinearLayout mNoDecksPlaceholder; @@ -216,6 +216,8 @@ public class DeckPicker extends NavigationDrawerActivity implements private EditText mDialogEditText; + private DeckPickerFloatingActionMenu mFloatingActionMenu; + // flag asking user to do a full sync which is used in upgrade path private boolean mRecommendFullSync = false; @@ -269,8 +271,8 @@ public class DeckPicker extends NavigationDrawerActivity implements private void onDeckClick(View v, DeckSelectionType selectionType) { long deckId = (long) v.getTag(); Timber.i("DeckPicker:: Selected deck with id %d", deckId); - if (mActionsMenu != null && mActionsMenu.isExpanded()) { - mActionsMenu.collapse(); + if (mFloatingActionMenu.isFABOpen()) { + mFloatingActionMenu.closeFloatingActionMenu(); } boolean collectionIsOpen = false; @@ -517,9 +519,7 @@ protected void onCreate(Bundle savedInstanceState) throws SQLException { mPullToSyncWrapper.setEnabled(mRecyclerViewLayoutManager.findFirstCompletelyVisibleItemPosition() == 0)); // Setup the FloatingActionButtons, should work everywhere with min API >= 15 - mActionsMenu = findViewById(R.id.add_content_menu); - mActionsMenu.findViewById(R.id.fab_expand_menu_button).setContentDescription(getString(R.string.menu_add)); - configureFloatingActionsMenu(); + mFloatingActionMenu = new DeckPickerFloatingActionMenu(view, this); mReviewSummaryTextView = findViewById(R.id.today_stats_text_view); @@ -629,56 +629,12 @@ private boolean firstCollectionOpen() { } } - private void configureFloatingActionsMenu() { - final FloatingActionButton addDeckButton = findViewById(R.id.add_deck_action); - final FloatingActionButton addSharedButton = findViewById(R.id.add_shared_action); - final FloatingActionButton addNoteButton = findViewById(R.id.add_note_action); - addDeckButton.setOnClickListener(view -> { - if (mActionsMenu == null) { - return; - } - mActionsMenu.collapse(); - mDialogEditText = new FixedEditText(DeckPicker.this); - mDialogEditText.setSingleLine(true); - // mDialogEditText.setFilters(new InputFilter[] { mDeckNameFilter }); - new MaterialDialog.Builder(DeckPicker.this) - .title(R.string.new_deck) - .positiveText(R.string.dialog_ok) - .customView(mDialogEditText, true) - .onPositive((dialog, which) -> { - String deckName = mDialogEditText.getText().toString(); - if (Decks.isValidDeckName(deckName)) { - boolean creation_succeed = createNewDeck(deckName); - if (!creation_succeed) { - return; - } - } else { - Timber.i("configureFloatingActionsMenu::addDeckButton::onPositiveListener - Not creating invalid deck name '%s'", deckName); - UIUtils.showThemedToast(this, getString(R.string.invalid_deck_name), false); - } - }) - .negativeText(R.string.dialog_cancel) - .show(); - }); - addSharedButton.setOnClickListener(view -> { - Timber.i("Adding Shared Deck"); - mActionsMenu.collapse(); - addSharedDeck(); - }); - addNoteButton.setOnClickListener(view -> { - Timber.i("Adding Note"); - mActionsMenu.collapse(); - addNote(); - }); - } - - /** * It can fail if an ancestor is a filtered deck. * @param deckName Create a deck with this name. * @return Whether creation succeeded. */ - private boolean createNewDeck(String deckName) { + protected boolean createNewDeck(String deckName) { Timber.i("DeckPicker:: Creating new deck..."); try { getCol().getDecks().id(deckName); @@ -1010,6 +966,7 @@ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putLong("mContextMenuDid", mContextMenuDid); savedInstanceState.putBoolean("mClosedWelcomeMessage", mClosedWelcomeMessage); + savedInstanceState.putBoolean("mIsFABOpen", mFloatingActionMenu.isFABOpen()); } @@ -1017,6 +974,7 @@ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mContextMenuDid = savedInstanceState.getLong("mContextMenuDid"); + mFloatingActionMenu.setIsFABOpen(savedInstanceState.getBoolean("mIsFABOpen")); } @@ -1073,8 +1031,8 @@ public void onBackPressed() { super.onBackPressed(); } else { Timber.i("Back key pressed"); - if (mActionsMenu != null && mActionsMenu.isExpanded()) { - mActionsMenu.collapse(); + if (mFloatingActionMenu.isFABOpen()) { + mFloatingActionMenu.closeFloatingActionMenu(); } else { automaticSync(); finishWithAnimation(); @@ -2417,7 +2375,7 @@ public void actualOnPostExecute(@NonNull DeckPicker deckPicker, List dueTree) * * This method also triggers an update for the widget to reflect the newly calculated counts. */ - private void updateDeckList() { + protected void updateDeckList() { updateDeckList(false); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java new file mode 100644 index 000000000000..f5e434f0b1de --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java @@ -0,0 +1,191 @@ +/**************************************************************************************** + * Copyright (c) 2021 Akshay Jadhav * + * * + * 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; + +import android.animation.Animator; +import android.content.SharedPreferences; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.ichi2.libanki.Decks; +import com.ichi2.ui.FixedEditText; + +import timber.log.Timber; + +public class DeckPickerFloatingActionMenu { + + private final FloatingActionButton mAddDeckButton; + private final FloatingActionButton mAddNoteButton; + private final FloatingActionButton mAddSharedButton; + private final FloatingActionButton mFabMain; + private final LinearLayout mAddSharedLayout; + private final LinearLayout mAddDeckLayout; + private final LinearLayout mAddNoteLayout; + private final View mFabBGLayout; + private boolean mIsFABOpen = false; + + private final DeckPicker mDeckPicker; + + public DeckPickerFloatingActionMenu(View view, DeckPicker deckPicker) { + this.mDeckPicker = deckPicker; + mAddNoteLayout = (LinearLayout)view.findViewById(R.id.add_note_layout); + mAddSharedLayout = (LinearLayout)view.findViewById(R.id.add_shared_layout); + mAddDeckLayout = (LinearLayout)view.findViewById(R.id.add_deck_layout); + mFabMain = (FloatingActionButton)view.findViewById(R.id.fab_main); + mAddNoteButton = (FloatingActionButton)view.findViewById(R.id.add_note_action); + mAddSharedButton = (FloatingActionButton)view.findViewById(R.id.add_shared_action); + mAddDeckButton = (FloatingActionButton)view.findViewById(R.id.add_deck_action); + mFabBGLayout = view.findViewById(R.id.fabBGLayout); + + mFabMain.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!mIsFABOpen) { + showFloatingActionMenu(); + } else { + closeFloatingActionMenu(); + } + } + }); + + mFabBGLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + closeFloatingActionMenu(); + } + }); + + mAddDeckButton.setOnClickListener(addDeckButtonView -> { + if (mIsFABOpen) { + closeFloatingActionMenu(); + EditText mDialogEditText = new FixedEditText(mDeckPicker); + mDialogEditText.setSingleLine(true); + new MaterialDialog.Builder(mDeckPicker) + .title(R.string.new_deck) + .positiveText(R.string.dialog_ok) + .customView(mDialogEditText, true) + .onPositive((dialog, which) -> { + String deckName = mDialogEditText.getText().toString(); + if (Decks.isValidDeckName(deckName)) { + boolean creation_succeed = deckPicker.createNewDeck(deckName); + if (!creation_succeed) { + return; + } + } else { + Timber.d("configureFloatingActionsMenu::addDeckButton::onPositiveListener - Not creating invalid deck name '%s'", deckName); + UIUtils.showThemedToast(mDeckPicker, mDeckPicker.getString(R.string.invalid_deck_name), false); + } + }) + .negativeText(R.string.dialog_cancel) + .show(); + } + }); + + mAddSharedButton.setOnClickListener(addSharedButtonView -> { + Timber.d("configureFloatingActionsMenu::addSharedButton::onClickListener - Adding Shared Deck"); + closeFloatingActionMenu(); + deckPicker.addSharedDeck(); + }); + + mAddNoteButton.setOnClickListener(addNoteButtonView -> { + Timber.d("configureFloatingActionsMenu::addNoteButton::onClickListener - Adding Note"); + closeFloatingActionMenu(); + deckPicker.addNote(); + }); + } + + private boolean animationDisabled() { + SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(mDeckPicker); + return preferences.getBoolean("safeDisplay", false); + } + + + public boolean isFABOpen() { + return mIsFABOpen; + } + + + public void setIsFABOpen(boolean mIsFABOpen) { + this.mIsFABOpen = mIsFABOpen; + } + + + private void showFloatingActionMenu() { + mIsFABOpen = true; + if (!animationDisabled()) { + // Show with animation + mAddNoteLayout.setVisibility(View.VISIBLE); + mAddSharedLayout.setVisibility(View.VISIBLE); + mAddDeckLayout.setVisibility(View.VISIBLE); + mFabBGLayout.setVisibility(View.VISIBLE); + mFabMain.animate().rotationBy(140); + mAddNoteLayout.animate().translationY(0).setDuration(30); + mAddSharedLayout.animate().translationY(0).setDuration(50); + mAddDeckLayout.animate().translationY(0).setDuration(100); + mAddDeckLayout.animate().alpha(1f).setDuration(100); + mAddSharedLayout.animate().alpha(1f).setDuration(50); + mAddNoteLayout.animate().alpha(1f).setDuration(30); + } else { + // Show without animation + mAddNoteLayout.setVisibility(View.VISIBLE); + mAddSharedLayout.setVisibility(View.VISIBLE); + mAddDeckLayout.setVisibility(View.VISIBLE); + mFabBGLayout.setVisibility(View.VISIBLE); + } + } + + protected void closeFloatingActionMenu() { + mIsFABOpen = false; + mFabBGLayout.setVisibility(View.GONE); + if (!animationDisabled()) { + // Close with animation + mFabMain.animate().rotation(0); + mAddNoteLayout.animate().translationY(200f).setDuration(30); + mAddSharedLayout.animate().translationY(400f).setDuration(50); + mAddDeckLayout.animate().alpha(0f).setDuration(100); + mAddSharedLayout.animate().alpha(0f).setDuration(50); + mAddNoteLayout.animate().alpha(0f).setDuration(30); + mAddDeckLayout.animate().translationY(600f).setDuration(100).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { } + + @Override + public void onAnimationEnd(Animator animator) { + if (!mIsFABOpen) { + mAddNoteLayout.setVisibility(View.GONE); + mAddSharedLayout.setVisibility(View.GONE); + mAddDeckLayout.setVisibility(View.GONE); + } + } + + @Override + public void onAnimationCancel(Animator animator) { } + + @Override + public void onAnimationRepeat(Animator animator) { } + }); + } else { + // Close without animation + mAddNoteLayout.setVisibility(View.GONE); + mAddSharedLayout.setVisibility(View.GONE); + mAddDeckLayout.setVisibility(View.GONE); + } + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/widgets/FabBehavior.java b/AnkiDroid/src/main/java/com/ichi2/anki/widgets/FabBehavior.java index 64484f9dc79b..fc981c5b04db 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/widgets/FabBehavior.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/widgets/FabBehavior.java @@ -24,7 +24,6 @@ import android.util.AttributeSet; import android.view.View; -import com.getbase.floatingactionbutton.FloatingActionsMenu; import java.util.List; @@ -33,7 +32,7 @@ * Defines the behavior for the floating action button. If the dependency is a Snackbar, move the * fab up. */ -public class FabBehavior extends CoordinatorLayout.Behavior { +public class FabBehavior extends CoordinatorLayout.Behavior { private float mTranslationY; @@ -45,7 +44,7 @@ public FabBehavior(Context context, AttributeSet attrs) { super(context, attrs); } - private static float getFabTranslationYForSnackbar(CoordinatorLayout parent, FloatingActionsMenu fab) { + private static float getFabTranslationYForSnackbar(CoordinatorLayout parent, View fab) { float minOffset = 0.0F; List dependencies = parent.getDependencies(fab); int i = 0; @@ -61,12 +60,12 @@ private static float getFabTranslationYForSnackbar(CoordinatorLayout parent, Flo } @Override - public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull FloatingActionsMenu child, @NonNull View dependency) { + public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) { return dependency instanceof Snackbar.SnackbarLayout; } @Override - public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull FloatingActionsMenu fab, @NonNull View dependency) { + public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View fab, @NonNull View dependency) { if (dependency instanceof Snackbar.SnackbarLayout && fab.getVisibility() == View.VISIBLE) { float translationY = getFabTranslationYForSnackbar(parent, fab); if (translationY != this.mTranslationY) { @@ -77,4 +76,17 @@ public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNul } return false; } + + @Override + public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull View fab, @NonNull View dependency) { + super.onDependentViewRemoved(parent, fab, dependency); + if (dependency instanceof Snackbar.SnackbarLayout && fab.getVisibility() == View.VISIBLE) { + float translationY = getFabTranslationYForSnackbar(parent, fab); + if (translationY == this.mTranslationY) { + ViewCompat.animate(fab).cancel(); + fab.setTranslationY(0); + this.mTranslationY = 0; + } + } + } } \ No newline at end of file diff --git a/AnkiDroid/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/AnkiDroid/src/main/res/drawable-hdpi/ic_add_white_24dp.png deleted file mode 100644 index 481643ecd5e5c361bdf5440f80b6ead58c015a87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZK3>dAqwX{BQ3+vmeOgEbxddW?|m)`b<0N2FLh>sBaz zbNHm-*5u;7=mneO8+En?J13^G1)j}b{4(Q;!Yv7xt%vHjER#LP#>jBmH-z#3;kA!} PRxo(F`njxgN@xNAMifE{ diff --git a/AnkiDroid/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png b/AnkiDroid/src/main/res/drawable-hdpi/ic_file_download_white_24dp.png deleted file mode 100644 index c8a2039c583803eab552eb2ad15248ad4f306b14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8mZytjh{y4_Q*ZMgP~c#YJsK$U z)~(q1m-m)lF^^vy&zFBx;@>#sNTW$[{bH;=%sZG7QZ>uQBcw?@Ce|ClF7G156^ z#Rt_}Q9HRs{l8sYa>)1&$3+cMLFMTJXY2PZ*sH$V@BeR?lj;@>g(qYZ*L3k@0BvRP MboFyt=akR{0D4_J0ssI2 diff --git a/AnkiDroid/src/main/res/drawable-hdpi/ic_folder_white_24dp.png b/AnkiDroid/src/main/res/drawable-hdpi/ic_folder_white_24dp.png deleted file mode 100644 index 02ea533a8d662e512843bf218e372653e95d843b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K;pQnpsh{y4_S5I>?DDbdcG|@O=~G=WkLM z=IP=XVsSe8&v^%_1es$}4u^UcOKr$FAbzMPnm+mvS-{o;0&fnzA@EQ^BmzWWo}eRZ9~msAiWq$nrXiF)+AFy^vQr>(2u; OhQZU-&t;ucLK6V({vp)> diff --git a/AnkiDroid/src/main/res/drawable-mdpi/ic_folder_white_24dp.png b/AnkiDroid/src/main/res/drawable-mdpi/ic_folder_white_24dp.png deleted file mode 100644 index 831d723ba9659a44186c1fd08752902f59669429..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1M^6{W5R22v2@+}#>L2`{{(qt) zvm)<`Q=Hj9{dp?B-k)#6`~UxbmC(cg19_d}4r?#Evfm-&!|_RRhEx83m({x|$*?rj VKUyK^_-~+L44$rjF6*2Ung9auEG+;4 diff --git a/AnkiDroid/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/AnkiDroid/src/main/res/drawable-xhdpi/ic_add_white_24dp.png deleted file mode 100644 index 67042105d29bc5676206dbdb1cdc9359e58bd63d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=12eq z?L1u^Ln02py|$5)K|z4oVe|hBQ9Bz}EZMQ1`FhKTnUCMHGB9jmw3Y!X{lPFtFM;7Y kgDFtxY{Pe1h8HYi&yV;pzTM$H6)4T%>FVdQ&MBb@0Q4<48~^|S diff --git a/AnkiDroid/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png b/AnkiDroid/src/main/res/drawable-xhdpi/ic_file_download_white_24dp.png deleted file mode 100644 index f53cc0c62c228828d73c90f681eda0835179b0b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0D6i*k&kch)?FYe|&V8Fv1*n95m zR5e$DAHp{l&A!8XfIV8!#r>2YP$7e#!c^^(-Y0%@RoZ*qi`F<)`EKq{{v+@Hw5*sR zFA(}!c2Utj$B^^;IgbBnE;xI?rFh=CsojiM7+vgNGclA;TJ|n(&Igb&p00i_>zopr E0CTxJ1ONa4 diff --git a/AnkiDroid/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png b/AnkiDroid/src/main/res/drawable-xhdpi/ic_folder_white_24dp.png deleted file mode 100644 index 71a5a137c463dfd97dfad592c86b7eac773664c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0D8c!F;kch)?uX*!1I0(1~u3k7j z(@|)CtD?lCSzab=p|%h2vhJCDWr_9aDM8CMGG^>pz2)3<9-DOK6&2^5E;t7@iOv_Z za^ku1A-hLl9mAHDjh3bd#2aH97>pYj7@1i1Fnhmp-(Sr7N?SnU?z8GoH}30QFuL9E fEIT*+)p6OLF7v?Cmm9c&?qKkA^>bP0l+XkK)+RzI diff --git a/AnkiDroid/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/AnkiDroid/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png deleted file mode 100644 index 72cedcad4f1bcedad5e0b69728dd893c52edf322..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6b$opaSX}0_x9RGP6h=5W{1uHFWkCy^H1}O)P{mkk7qly8JQr!Ds4T702EwEU(nYs qt)QS_%>n}^QsrM(T z{K~l&Yj@SA9If=c^vXIaY>wlkMR7)vg;t&u=kNuYJa3$GaHnEwp`ORgIc%D)jV%O+J|}OmjAiz^vX--1bb;NmdKI;Vst0GcUV@&Et; diff --git a/AnkiDroid/src/main/res/drawable-xxxhdpi/ic_file_download_white_24dp.png b/AnkiDroid/src/main/res/drawable-xxxhdpi/ic_file_download_white_24dp.png deleted file mode 100644 index ded5652e406a8bb0788402b1ea2034968a241ead..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeG3?%1&o4*=J@dWsUxB}__Fko@OdIgZrQWE4B z%@VZ8#xa<@HhnOS1viNAnEC})a|0sJS72xJ-2)f?&QaR z?3aFfZT$|bN3Ya!ZWwgOIdJDMXyKo5=w(d|$6F^2ai?7ZNyM@uS}y1~X^1JRa37T8 i%D!pbm~%M(0r%^vOJ5j@by@-4#Ng@b=d#Wzp$Pz$!&MXj diff --git a/AnkiDroid/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png b/AnkiDroid/src/main/res/drawable-xxxhdpi/ic_folder_white_24dp.png deleted file mode 100644 index a1afbe9daf5a5e3945f33915f8817be26b539148..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z{un2;uunK>+P+xzK0zI93I~B z(C26sy3FL&q}$ba<$&VE(+|=Gi?KQ4VXb{{b%I@HcvNQkRwH8&_$~OogvaO4 zw%oRd*KGoKY|H)oF+L(JmZ@K7yH~M)rTJtXSd}W-{epFln3^$y#Qw2YO4`4lFD^P$~Ceo|M5hK8FSd#^MHkMkW@H6#Nv%Xop z`uOoFkL` + + diff --git a/AnkiDroid/src/main/res/drawable/ic_file_download_white.xml b/AnkiDroid/src/main/res/drawable/ic_file_download_white.xml new file mode 100644 index 000000000000..3e991ef3ade8 --- /dev/null +++ b/AnkiDroid/src/main/res/drawable/ic_file_download_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/AnkiDroid/src/main/res/drawable/ic_folder_white.xml b/AnkiDroid/src/main/res/drawable/ic_folder_white.xml new file mode 100644 index 000000000000..2d57ce7f266c --- /dev/null +++ b/AnkiDroid/src/main/res/drawable/ic_folder_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/AnkiDroid/src/main/res/layout/floating_add_button.xml b/AnkiDroid/src/main/res/layout/floating_add_button.xml index 2f53623e8ff3..dec109616731 100644 --- a/AnkiDroid/src/main/res/layout/floating_add_button.xml +++ b/AnkiDroid/src/main/res/layout/floating_add_button.xml @@ -1,49 +1,153 @@ - - - + + + + - + + - + + + + + - + + + + + - - \ No newline at end of file + android:padding="5dp" + android:clipToPadding="false" + android:gravity="center_vertical" + android:layout_gravity="bottom|end" + android:orientation="horizontal" + android:visibility="gone" + > + + + + + + + + + + + diff --git a/AnkiDroid/src/main/res/menu-television/reviewer.xml b/AnkiDroid/src/main/res/menu-television/reviewer.xml index c6a9cd297a23..104e2e7afce3 100644 --- a/AnkiDroid/src/main/res/menu-television/reviewer.xml +++ b/AnkiDroid/src/main/res/menu-television/reviewer.xml @@ -79,7 +79,7 @@ diff --git a/AnkiDroid/src/main/res/menu/model_browser.xml b/AnkiDroid/src/main/res/menu/model_browser.xml index 33139e5a2a0c..06b232e8abb8 100644 --- a/AnkiDroid/src/main/res/menu/model_browser.xml +++ b/AnkiDroid/src/main/res/menu/model_browser.xml @@ -4,6 +4,6 @@ \ No newline at end of file diff --git a/AnkiDroid/src/main/res/menu/model_editor.xml b/AnkiDroid/src/main/res/menu/model_editor.xml index 2015da16f3f4..5be3dd0b30d0 100644 --- a/AnkiDroid/src/main/res/menu/model_editor.xml +++ b/AnkiDroid/src/main/res/menu/model_editor.xml @@ -4,6 +4,6 @@ \ No newline at end of file diff --git a/AnkiDroid/src/main/res/menu/reviewer.xml b/AnkiDroid/src/main/res/menu/reviewer.xml index ef56c0dafdc4..8a40dc668a1e 100644 --- a/AnkiDroid/src/main/res/menu/reviewer.xml +++ b/AnkiDroid/src/main/res/menu/reviewer.xml @@ -72,7 +72,7 @@ From b563063ca8ff81591639a3d480d6713882e03199 Mon Sep 17 00:00:00 2001 From: Tushar Bhatt Date: Thu, 1 Apr 2021 01:22:40 +0530 Subject: [PATCH 010/171] NF: name change AnkiActivity.isUninitialized -> AnkiActivity.isInitialized (#8416) --- AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java | 2 +- AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java index 21e5dc1c70b7..0603e3f80924 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java @@ -605,7 +605,7 @@ protected void enableToolbar(@Nullable View view) { } protected boolean showedActivityFailedScreen(Bundle savedInstanceState) { - if (!AnkiDroidApp.isUninitialized()) { + if (AnkiDroidApp.isInitialized()) { return false; } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index 219194b4465c..8749860cc56b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -200,8 +200,8 @@ public static InputStream getResourceAsStream(@NonNull String name) { } - public static boolean isUninitialized() { - return sInstance == null; + public static boolean isInitialized() { + return sInstance != null; } From 585ba4e9b880727cec8f81d09f912ff84e36eebc Mon Sep 17 00:00:00 2001 From: Excelsior Date: Thu, 1 Apr 2021 01:24:22 +0530 Subject: [PATCH 011/171] Add unit test for ClipboardUtil (#8415) --- .../com/ichi2/utils/ClipboardUtilTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.java new file mode 100644 index 000000000000..eac407e061e8 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/ClipboardUtilTest.java @@ -0,0 +1,25 @@ +package com.ichi2.utils; + +import android.content.ClipDescription; +import android.content.ClipboardManager; + +import org.junit.Test; +import static com.ichi2.utils.ClipboardUtil.hasImage; +import static org.junit.Assert.*; + + +public class ClipboardUtilTest { + + @Test + public void hasImageClipboardmanagernulltest() { + ClipboardManager clipboardManager = null; + assertFalse(hasImage(clipboardManager)); + } + + @Test + public void hasImageDescriptionnulltest() { + ClipDescription clipDescription = null; + assertFalse(hasImage(clipDescription)); + } + +} From c1ef700082bc76ce8a34e7c5666869d80defde30 Mon Sep 17 00:00:00 2001 From: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com> Date: Thu, 1 Apr 2021 01:25:13 +0530 Subject: [PATCH 012/171] fix: ACRA Crash Dialog - Can't enter additional information (#8395) * fix: ACRA Crash Dialog - Can't enter additional information * add javadoc --- AnkiDroid/src/main/res/layout/feedback.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/res/layout/feedback.xml b/AnkiDroid/src/main/res/layout/feedback.xml index 93e5b70ab95c..e240433e0869 100644 --- a/AnkiDroid/src/main/res/layout/feedback.xml +++ b/AnkiDroid/src/main/res/layout/feedback.xml @@ -14,7 +14,8 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" > - + Date: Tue, 30 Mar 2021 03:21:44 +0200 Subject: [PATCH 013/171] NF: avoid duplicate work The field text can't change after exception, so pos and pos1 are the same variables --- AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java index 6ed953ad0b09..6ab3694337cf 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java @@ -368,10 +368,8 @@ private void repositionFieldDialog() { Runnable confirm = () -> { try { mCol.modSchemaNoCheck(); - String newPosition1 = mFieldNameInput.getText().toString(); - int pos1 = Integer.parseInt(newPosition1); TaskManager.launchCollectionTask(new CollectionTask.RepositionField(mMod, - mNoteFields.getJSONObject(mCurrentPos), pos1 - 1), + mNoteFields.getJSONObject(mCurrentPos), pos - 1), listener); dismissContextMenu(); } catch (JSONException e1) { From c1697ad37158b47d4a44c200e7428704af61792b Mon Sep 17 00:00:00 2001 From: Prateek Singh <76490368+prateek-singh-3212@users.noreply.github.com> Date: Thu, 1 Apr 2021 01:35:24 +0530 Subject: [PATCH 014/171] NoteEditor Toolbar nightmode support (#8399) * Added toolbar night mode support Co-authored-by: Kartikey Saran <55613721+kartikeysaran@users.noreply.github.com> --- .../src/main/java/com/ichi2/anki/NoteEditor.java | 12 ++++++++++-- .../main/java/com/ichi2/anki/noteeditor/Toolbar.java | 9 +++++++++ AnkiDroid/src/main/res/layout/note_editor.xml | 1 - AnkiDroid/src/main/res/values/attrs.xml | 2 ++ AnkiDroid/src/main/res/values/theme_black.xml | 2 ++ AnkiDroid/src/main/res/values/theme_dark.xml | 2 ++ AnkiDroid/src/main/res/values/theme_light.xml | 2 ++ AnkiDroid/src/main/res/values/theme_plain.xml | 2 ++ 8 files changed, 29 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java index d59fdaca9d34..d2e5774c151c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java @@ -131,6 +131,7 @@ import java.util.Set; import androidx.core.content.ContextCompat; +import androidx.core.content.res.ResourcesCompat; import androidx.core.text.HtmlCompat; import androidx.fragment.app.DialogFragment; import timber.log.Timber; @@ -474,6 +475,10 @@ protected void onCollectionLoaded(Collection col) { modifyCurrentSelection(formatter, (FieldEditText) currentFocus); }); + // Sets the background and icon color of toolbar respectively. + mToolbar.setBackgroundColor(Themes.getColorFromAttr(NoteEditor.this, R.attr.toolbarBackgroundColor)); + mToolbar.setIconColor(Themes.getColorFromAttr(NoteEditor.this, R.attr.toolbarIconColor)); + enableToolbar(mainView); mFieldsLayoutContainer = findViewById(R.id.CardEditorEditFieldsLayout); @@ -1790,7 +1795,7 @@ private void initFieldEditText(FieldEditText editText, final int index, boolean // Sets the background color of disabled EditText. if (!enabled) { - editText.setBackgroundColor(Themes.getColorFromAttr(NoteEditor.this,R.attr.editTextBackgroundColor)); + editText.setBackgroundColor(Themes.getColorFromAttr(NoteEditor.this, R.attr.editTextBackgroundColor)); } editText.setEnabled(enabled); } @@ -2004,7 +2009,10 @@ private void updateToolbar() { } // Let the user add more buttons (always at the end). - mToolbar.insertItem(0, R.drawable.ic_add_toolbar_icon, this::displayAddToolbarDialog); + // Sets the add custom tag icon color. + final Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_add_toolbar_icon, null); + drawable.setTint(Themes.getColorFromAttr(NoteEditor.this, R.attr.toolbarIconColor)); + mToolbar.insertItem(0, drawable, this::displayAddToolbarDialog); } @NonNull diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.java b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.java index cde1650a660d..c785e4d8fd65 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/Toolbar.java @@ -45,6 +45,7 @@ import java.util.ArrayList; import java.util.List; +import androidx.annotation.ColorInt; import androidx.annotation.DrawableRes; import androidx.annotation.IdRes; import androidx.annotation.NonNull; @@ -307,6 +308,14 @@ public void onFormat(TextFormatter formatter) { mFormatCallback.performFormat(formatter); } + public void setIconColor(@ColorInt int color) { + for (int i = 0; i < this.mToolbar.getChildCount(); i++) { + AppCompatImageButton button = (AppCompatImageButton) this.mToolbar.getChildAt(i); + button.setColorFilter(color); + } + mStringPaint.setColor(color); + } + public interface TextFormatListener { void performFormat(TextFormatter formatter); diff --git a/AnkiDroid/src/main/res/layout/note_editor.xml b/AnkiDroid/src/main/res/layout/note_editor.xml index 726bdcb81ce7..62fa1484973c 100644 --- a/AnkiDroid/src/main/res/layout/note_editor.xml +++ b/AnkiDroid/src/main/res/layout/note_editor.xml @@ -140,7 +140,6 @@ + + diff --git a/AnkiDroid/src/main/res/values/theme_black.xml b/AnkiDroid/src/main/res/values/theme_black.xml index c7e527bb5f0e..7a17cac8362e 100644 --- a/AnkiDroid/src/main/res/values/theme_black.xml +++ b/AnkiDroid/src/main/res/values/theme_black.xml @@ -90,6 +90,8 @@ #855 #EE5C5C5C + #616161 + @color/white #ff303030 #c8303030 diff --git a/AnkiDroid/src/main/res/values/theme_dark.xml b/AnkiDroid/src/main/res/values/theme_dark.xml index 3b8f47697bed..3fe284a5a570 100644 --- a/AnkiDroid/src/main/res/values/theme_dark.xml +++ b/AnkiDroid/src/main/res/values/theme_dark.xml @@ -93,6 +93,8 @@ #855 #EE5C5C5C + #616161 + @color/white @color/material_light_blue_700 @color/material_light_blue_900 diff --git a/AnkiDroid/src/main/res/values/theme_light.xml b/AnkiDroid/src/main/res/values/theme_light.xml index 55029c010a7a..617d5f75a49b 100644 --- a/AnkiDroid/src/main/res/values/theme_light.xml +++ b/AnkiDroid/src/main/res/values/theme_light.xml @@ -107,6 +107,8 @@ APIs. It's visible when there aren't enough decks to fill the screen. #fcc #EBCCCCCC + #D6D7D7 + @color/black @color/material_light_blue_700 @color/material_light_blue_900 diff --git a/AnkiDroid/src/main/res/values/theme_plain.xml b/AnkiDroid/src/main/res/values/theme_plain.xml index f5ff4028facb..5db0d5ac04d8 100644 --- a/AnkiDroid/src/main/res/values/theme_plain.xml +++ b/AnkiDroid/src/main/res/values/theme_plain.xml @@ -37,6 +37,8 @@ ?attr/dividerHorizontal #EBCCCCCC + #D6D7D7 + @color/black @color/theme_plain_accent #c8607d8b From 07f3628342409d1c741a614a2762603bb366d2ab Mon Sep 17 00:00:00 2001 From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com> Date: Thu, 1 Apr 2021 01:39:55 +0530 Subject: [PATCH 015/171] Add unit test for CollectionUtils (#8402) * CollectionUtils test added * List matcher * Added IndexOutOfBoundsException and ArrayListCheck --- .../com/ichi2/utils/CollectionUtilsTest.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/CollectionUtilsTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/CollectionUtilsTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/CollectionUtilsTest.java new file mode 100644 index 000000000000..d71fe9c313ac --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/CollectionUtilsTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Aditya Srivastav + * + * 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.utils; + +import org.hamcrest.Matchers; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static com.ichi2.testutils.AnkiAssert.assertEqualsArrayList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class CollectionUtilsTest { + + List testList = new ArrayList() {{ + add(1); + add(2); + add(3); + }}; + + @Test + public void testGetLastListElement() { + assertThat(CollectionUtils.getLastListElement(testList), is(3)); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void testGetLastOnEmptyList() { + List emptyList = new ArrayList<>(); + CollectionUtils.getLastListElement(emptyList); + } + + @Test + public void testAddAll() { + List toTest = new ArrayList<>(); + CollectionUtils.addAll(toTest, testList); + assertEqualsArrayList(new Integer [] {1, 2, 3}, toTest); + } +} From 831c2565c2d02b56a540e850e4b2e3823946a100 Mon Sep 17 00:00:00 2001 From: Vaibhavi Lokegaonkar <53178543+Vaibhavi1707@users.noreply.github.com> Date: Thu, 1 Apr 2021 01:51:09 +0530 Subject: [PATCH 016/171] Enable java 8 library desugaring (#8361) * Enabled desugaring Co-authored-by: Mike Hardy --- AnkiDroid/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index b2839cab490e..329c773f0bf5 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -154,6 +154,7 @@ android { preDexLibraries = preDexEnabled && !ciBuild } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -216,6 +217,7 @@ dependencies { } } + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' compileOnly 'org.jetbrains:annotations:20.1.0' compileOnly "com.google.auto.service:auto-service-annotations:1.0-rc7" annotationProcessor "com.google.auto.service:auto-service:1.0-rc7" From 565655eb506d7631dca5e7fc4dc997cbe0078689 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Wed, 31 Mar 2021 23:21:59 +0200 Subject: [PATCH 017/171] NF: Add UniqueArrayList data structure (#8333) * NF: Added UniqueArrayList data structure. UniqueArrayList combines a List and a Set to be able to: - maintain order - fast random access - fast lookup of items - sortable - mainlining unique values - optionally have a comparator to define the uniqueness of an item, for example `String.CASE_INSENSITIVE_ORDER`. This data structure can be used for maintaining a list of tags. * Update AnkiDroid/build.gradle --- AnkiDroid/build.gradle | 1 + .../java/com/ichi2/utils/UniqueArrayList.java | 187 ++++++ .../com/ichi2/utils/UniqueArrayListTest.java | 570 ++++++++++++++++++ 3 files changed, 758 insertions(+) create mode 100644 AnkiDroid/src/main/java/com/ichi2/utils/UniqueArrayList.java create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 329c773f0bf5..c90b8aa15b4a 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -266,6 +266,7 @@ dependencies { // io.github.java-diff-utils:java-diff-utils is the natural successor here, but requires API24, #7091 implementation 'org.bitbucket.cowwoc:diff-match-patch:1.2' implementation 'org.apache.commons:commons-compress:1.12' // #6419 - handle >2GB apkg files + implementation 'org.apache.commons:commons-collections4:4.4' // SetUniqueList implementation 'net.mikehardy:google-analytics-java7:2.0.13' implementation 'com.squareup.okhttp3:okhttp:4.9.1' implementation 'com.arcao:slf4j-timber:3.1' diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/UniqueArrayList.java b/AnkiDroid/src/main/java/com/ichi2/utils/UniqueArrayList.java new file mode 100644 index 000000000000..d23c01866ff6 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/utils/UniqueArrayList.java @@ -0,0 +1,187 @@ +/* + Copyright (c) 2021 Tarek Mohamed Abdalla + + 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.utils; + + +import org.apache.commons.collections4.list.SetUniqueList; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; +import java.util.Spliterator; +import java.util.TreeSet; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +/** + * A collection of items that doesn't allow duplicate items, and allows fast random access, lookup, maintaining order, and sorting. + *

+ * The {@link List} interface makes certain assumptions/requirements. This + * implementation breaks these in certain ways, but this is merely the result of + * rejecting duplicates. Each violation is explained in the method, but it + * should not affect you. + * + * This class does also implement the {@link Set} interface, and as a result + * you should bear in mind that Sets require immutable objects to function correctly. + * + * @implNote The implementation of this class extends {@link SetUniqueList} and adds the ability to define a comparator + * to be used to judge uniqueness of elements, and allows sorting. + *

+ * Data structures used internally: + * - {@link ArrayList} to enable fast random access, sorting, and maintaining order of items during iteration + * - {@link TreeSet} if a comparator is given or a {@link HashSet} otherwise. + */ +public class UniqueArrayList extends SetUniqueList implements List, Set { + + /** + * Internal list used in {@link SetUniqueList} implementation. + *

+ * This is the same list as the one used internally in {@link SetUniqueList}. We keep a reference to it here in + * order to be able to sort it. {@link SetUniqueList} implementation needs to make sure the internal {@link Set} + * and {@link List} don't get out of sync, and {@link SetUniqueList} cannot be sorted via {@link Collections#sort(List)} + * or {@link SetUniqueList#sort(Comparator)} both will throw an exception, due to a limitation on this class {@link java.util.ListIterator} + * + * Sorting can be only done via {@link UniqueArrayList#sort()} or {@link UniqueArrayList#sort(Comparator)}. + * + * Modification to this list reference should be done with cautious to avoid having the internal {@link Set} out of sync + */ + private final List mList; + + + /** + * Constructor that wraps (not copies) the List and specifies the set to use. + *

+ * The set and list must both be correctly initialised to the same elements. + * + * @param set the set to decorate, must not be null + * @param list the list to decorate, must not be null + * @throws NullPointerException if set or list is null + */ + protected UniqueArrayList(final List list, final Set set) { + super(list, set); + mList = list; + } + + + /** + * Constructs a new empty {@link UniqueArrayList} + */ + public UniqueArrayList() { + this(new ArrayList<>(), new HashSet<>()); + } + + + /** + * Constructs a new {@link UniqueArrayList} containing the elements of the specified collection + * + * @param source the source collection that will be used to construct UniqueArrayList + */ + public static UniqueArrayList from(final List source) { + return UniqueArrayList.from(source, null); + } + + /** + * Constructs a new {@link UniqueArrayList} containing the elements of the specified collection, with an optional + * comparator to be used to judge uniqueness. + * + * @implNote Modified implantation of {@link SetUniqueList#setUniqueList(List)} to : + * - support using comparators to check for uniqueness. + * - make a copy of the list passed. + * + * @param source the source collection that will be used to construct UniqueArrayList + * @param comparator used to judge uniqueness + */ + public static UniqueArrayList from(@NonNull List source, @Nullable Comparator comparator) { + if (source == null) { + throw new NullPointerException("List must not be null"); + } + + Set set; + if (comparator == null) { + set = new HashSet<>(); + } else { + set = new TreeSet<>(comparator); + } + + final UniqueArrayList sl = new UniqueArrayList<>(new ArrayList<>(), set); + + sl.addAll(source); + + return sl; + } + + + /** + * Sorts the list into ascending order, according to the + * {@linkplain Comparable natural ordering} of its elements. + * All elements in the list must implement the {@link Comparable} + * interface. Furthermore, all elements in the list must be + * mutually comparable (that is, {@code e1.compareTo(e2)} + * must not throw a {@code ClassCastException} for any elements + * {@code e1} and {@code e2} in the list). + * + * @see #sort(Comparator) + */ + public void sort() { + sort(null); + } + + /** + * Sorts the list according to the order induced by the specified comparator. + * All elements in the list must be mutually comparable using the + * specified comparator (that is, {@code c.compare(e1, e2)} must not throw + * a {@code ClassCastException} for any elements {@code e1} and {@code e2} + * in the list). + * + *

This sort is guaranteed to be stable: equal elements will + * not be reordered as a result of the sort. + * + *

The specified list must be modifiable, but need not be resizable. + * + * @implNote + * This implementation defers to the {@link Collections#sort(List, Comparator)} + * method using this list and the given comparator. + * + * DO NOT call {@link Collections#sort(List, Comparator)} using this list directly + * this will throw due to a limitation on setting items on the {@link ListIterator} + * returned by {@link #listIterator()} + * + * @param c the comparator to determine the order of the list. A + * {@code null} value indicates that the elements' natural + * ordering should be used. + * + * @see Collections#sort(List, Comparator) + */ + @Override + public void sort(@Nullable Comparator c) { + Collections.sort(mList, c); + } + + + @NonNull + @Override + @RequiresApi(24) + public Spliterator spliterator() { + return super.spliterator(); + } +} diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java new file mode 100644 index 000000000000..7df32441f759 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java @@ -0,0 +1,570 @@ +/* + Copyright (c) 2021 Tarek Mohamed Abdalla + + 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.utils; + +import org.hamcrest.collection.IsIterableContainingInOrder; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class UniqueArrayListTest { + + + private List dupData = Arrays.asList( + "55", + "TEst", + "TEst", + "12", + "TEst", + "dsf23A", + "dsf23A", + "dsf23A", + "dsf23A", + "23", + "12", + "sd", + "TEst", + "55" + ); + + private List noDupData = Arrays.asList( + "55", + "TEst", + "12", + "dsf23A", + "23", + "sd" + ); + + private void assertSameLists(List a, List b){ + assertThat(b, IsIterableContainingInOrder.contains(a.toArray())); + } + + private void assertNotSameLists(List a, List b){ + assertThat(b, not(IsIterableContainingInOrder.contains(a.toArray()))); + } + + @Test + public void testOrderIsMaintained() { + List longs = Arrays.asList(1, 1, 2, 3, 4, 1, 5, 1, 6, 7, 8, 9, 10, 11, 1, 12, 13); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertTrue(uniqueList.indexOf(5) > uniqueList.indexOf(1)); + } + + + @Test + public void test_Sorting() { + List longs = Arrays.asList(10, 9, 7, 3, 2, -1, 5, 1, 65, -656); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + Collections.sort(longs); + uniqueList.sort(); + assertSameLists(longs, uniqueList); + } + + + @Test + public void test_uniqueness_after_sorting() { + List longs = Arrays.asList(10, 9, 7, 3, 2, -1, 5, 1, 65, -656); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + Collections.sort(longs); + uniqueList.sort(); + assertSameLists(longs, uniqueList); + + uniqueList.addAll(longs); + uniqueList.add(10); + uniqueList.add(5, 65); + + assertSameLists(longs, uniqueList); + + uniqueList.add(575757); + assertNotSameLists(longs, uniqueList); + } + + + @Test + public void test_comparator() { + List list = Arrays.asList("TarekkMA", "TarekkMA", "TarekkmA", "tarekkma"); + UniqueArrayList uniqueList = UniqueArrayList.from(list, String.CASE_INSENSITIVE_ORDER); + + assertEquals(1, uniqueList.size()); + assertEquals("TarekkMA", uniqueList.get(0)); + + uniqueList.clear(); + Collections.reverse(list); + uniqueList.addAll(list); + + assertEquals(1, uniqueList.size()); + assertEquals("tarekkma", uniqueList.get(0)); + } + + + @Test + public void test_add_unique_after_sorting() { + List longs = Arrays.asList(10, 9, 7, 3, 2, -1, 5, 1, 65, -656); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + Collections.sort(longs); + uniqueList.sort(); + assertEquals(longs, uniqueList); + uniqueList.addAll(longs); + assertEquals(longs, uniqueList); + } + + + @Test + public void testFromCollection() { + UniqueArrayList uniqueArrayList = UniqueArrayList.from(dupData); + assertEquals(noDupData, uniqueArrayList); + + uniqueArrayList = new UniqueArrayList<>(); + assertTrue(uniqueArrayList.isEmpty()); + } + + + @Test + public void testFromEmptyCollection() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + assertTrue(uniqueArrayList.isEmpty()); + } + + + @Test + public void test_add_not_existing() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + } + + + @Test + public void test_add_existing() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + } + + + @Test + public void test_set_not_existing() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + + String res = uniqueArrayList.set(1, "m"); + + assertEquals(Arrays.asList("a", "m", "f"), uniqueArrayList); + assertEquals("Z", res); + } + + + @Test + public void test_set_existing() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + + String res = uniqueArrayList.set(1, "a"); + + assertEquals(Arrays.asList("a", "f"), uniqueArrayList); + assertEquals("Z", res); + } + + @Test + public void test_set_will_remove_replaced_item() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.set(0, "b"); + uniqueArrayList.add("a"); + + assertSameLists(Arrays.asList("b", "a"), uniqueArrayList); + } + + + @Test + public void test_addAll_no_change() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + boolean res = uniqueArrayList.addAll(Arrays.asList("a", "Z", "f")); + + assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + assertFalse(res); + } + + + @Test + public void test_addAll_full_change() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + boolean res = uniqueArrayList.addAll(Arrays.asList("w", "x", "y")); + + assertEquals(Arrays.asList("a", "Z", "f", "w", "x", "y"), uniqueArrayList); + assertTrue(res); + } + + + @Test + public void test_addAll_partial_change() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + boolean res = uniqueArrayList.addAll(Arrays.asList("f", "Y", "Z")); + + assertEquals(Arrays.asList("a", "Z", "f", "Y"), uniqueArrayList); + assertTrue(res); + } + + + @Test + public void test_addAll_withIndex_no_change() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + boolean res = uniqueArrayList.addAll(1, Arrays.asList("a", "Z", "f")); + + assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + assertFalse(res); + } + + + @Test + public void test_addAll_withIndex_full_change() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + boolean res = uniqueArrayList.addAll(1, Arrays.asList("w", "x", "y")); + + assertSameLists(Arrays.asList("a", "w", "x", "y", "Z", "f"), uniqueArrayList); + assertTrue(res); + } + + + @Test + public void test_addAll_withIndex_partial_change() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + boolean res = uniqueArrayList.addAll(1, Arrays.asList("f", "Y", "Z")); + + assertSameLists(Arrays.asList("a", "Y", "Z", "f"), uniqueArrayList); + assertTrue(res); + } + + + @Test + public void test_addAll_withIndex_last_position() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + uniqueArrayList.add("a"); + uniqueArrayList.add("Z"); + uniqueArrayList.add("f"); + + boolean res = uniqueArrayList.addAll(uniqueArrayList.size(), Arrays.asList("w", "x", "y")); + + assertSameLists(Arrays.asList("a", "Z", "f", "w", "x", "y"), uniqueArrayList); + assertTrue(res); + } + + + @Test + public void test_addAll_order_1() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + final List l1 = Arrays.asList(1, 2, 3, 4, 5); + assertTrue(uniqueArrayList.addAll(l1)); + assertSameLists(l1, uniqueArrayList); + + final List l2 = Arrays.asList(5, 4, 3, 2, 1, 0); + assertTrue(uniqueArrayList.addAll(0,l2)); + + final List l3 = Arrays.asList(0, 1, 2, 3, 4, 5); + assertSameLists(l3, uniqueArrayList); + } + + + @Test + public void test_addAll_order_2() { + UniqueArrayList uniqueArrayList = new UniqueArrayList<>(); + + final List l1 = Arrays.asList(1, 2, 3, 4, 5); + assertTrue(uniqueArrayList.addAll(l1)); + assertSameLists(l1, uniqueArrayList); + + final List l2 = Arrays.asList(0, 1, 2, 3, 4, 5); + assertTrue(uniqueArrayList.addAll(0,l2)); + + assertSameLists(l2, uniqueArrayList); + } + + + @Test + public void test_clear() { + List longs = Arrays.asList(1, 1, 2, 3, 4, 1, 5, 1, 6, 7, 8, 9, 10, 11, 1, 12, 13); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertFalse(uniqueList.isEmpty()); + uniqueList.clear(); + assertTrue(uniqueList.isEmpty()); + uniqueList.addAll(longs); + assertFalse(uniqueList.isEmpty()); + } + + + @Test + public void test_remove_object() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertNotEquals(-1, uniqueList.indexOf(1L)); + uniqueList.remove(1L); + assertEquals(-1, uniqueList.indexOf(1L)); + uniqueList.set(10, 1L); + assertEquals(10, uniqueList.indexOf(1L)); + } + + + @Test + public void test_remove_index() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertNotEquals(-1, uniqueList.indexOf(1L)); + uniqueList.remove(0/*index of 1L*/); + assertEquals(-1, uniqueList.indexOf(1L)); + uniqueList.set(10, 1L); + assertEquals(10, uniqueList.indexOf(1L)); + } + + + @Test + public void test_contain() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertTrue(uniqueList.contains(1L)); + assertFalse(uniqueList.contains(1502L)); + } + + + @Test + public void test_get_by_index() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertEquals((Long) 1L, uniqueList.get(0)); + assertNotEquals((Long) 1L, uniqueList.get(1)); + + + assertEquals((Long) 13L, uniqueList.get(12)); + assertEquals((Long) 12L, uniqueList.get(11)); + } + + + @Test + public void test_indexOf_and_lastIndexOf() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertEquals(0, uniqueList.indexOf(1L)); + assertEquals(5, uniqueList.indexOf(6L)); + assertEquals(12, uniqueList.indexOf(13L)); + assertEquals(9, uniqueList.indexOf(10L)); + assertEquals(2, uniqueList.indexOf(3L)); + + assertEquals(0, uniqueList.lastIndexOf(1L)); + assertEquals(5, uniqueList.lastIndexOf(6L)); + assertEquals(12, uniqueList.lastIndexOf(13L)); + assertEquals(9, uniqueList.lastIndexOf(10L)); + assertEquals(2, uniqueList.lastIndexOf(3L)); + } + + + @Test + public void test_iterator() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + List list = new ArrayList<>(); + uniqueList.iterator().forEachRemaining(list::add); + + assertEquals(Arrays.asList( + 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L + ), list); + } + + + @Test + public void test_listIterator() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + List list = new ArrayList<>(); + uniqueList.listIterator().forEachRemaining(list::add); + + assertEquals(Arrays.asList( + 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L + ), list); + } + + + @Test + public void test_listIterator_index() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + List list = new ArrayList<>(); + uniqueList.listIterator(5).forEachRemaining(list::add); + + assertEquals(Arrays.asList( + 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L + ), list); + } + + + @Test + public void test_subList() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertEquals(Arrays.asList( + 6L, 7L, 8L, 9L, 10L + ), uniqueList.subList(5, 10)); + } + + + @Test + public void test_containsAll() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertTrue(uniqueList.containsAll(Arrays.asList(1L, 10L, 13L))); + assertFalse(uniqueList.containsAll(Arrays.asList(1L, 130L, 13L))); + assertFalse(uniqueList.containsAll(Arrays.asList(-1L, 130L, 1003L))); + } + + + @Test + public void test_retainAll() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertNotEquals(2, uniqueList.size()); + uniqueList.retainAll(Arrays.asList(1L, 3L)); + assertEquals(2, uniqueList.size()); + assertEquals(Arrays.asList(1L, 3L), uniqueList); + } + + + @Test + public void test_isEmpty() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + assertFalse(uniqueList.isEmpty()); + uniqueList.removeAll(Arrays.asList( + 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L + )); + assertTrue(uniqueList.isEmpty()); + } + + + @Test + public void test_toArray() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + final Object[] arr = Arrays.asList( + 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L + ).toArray(); + + assertArrayEquals(arr, uniqueList.toArray()); + } + + + @Test + public void test_toArrayType() { + List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); + UniqueArrayList uniqueList = UniqueArrayList.from(longs); + + final Long[] arr = Arrays.asList( + 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L + ).toArray(new Long[] {}); + + final Long[] arr_res = uniqueList.toArray(new Long[] {}); + assertArrayEquals(arr, arr_res); + + assertThat(arr_res[0], instanceOf(Long.class)); + } +} \ No newline at end of file From dca44e9ed5dfd1abe28ceb01ce2c467869d6e6dc Mon Sep 17 00:00:00 2001 From: AnkiDroid Translations Date: Wed, 31 Mar 2021 20:24:30 +0000 Subject: [PATCH 018/171] Updated strings from Crowdin --- .../src/main/res/values-ar/10-preferences.xml | 2 +- .../src/main/res/values-de/02-strings.xml | 6 +- .../src/main/res/values-de/03-dialogs.xml | 4 +- .../src/main/res/values-de/10-preferences.xml | 4 +- .../src/main/res/values-eo/02-strings.xml | 4 +- .../src/main/res/values-eo/03-dialogs.xml | 4 +- .../src/main/res/values-eo/10-preferences.xml | 4 +- .../src/main/res/values-es-rAR/01-core.xml | 2 +- .../src/main/res/values-es-rAR/02-strings.xml | 4 +- .../src/main/res/values-es-rES/01-core.xml | 2 +- .../src/main/res/values-es-rES/02-strings.xml | 4 +- AnkiDroid/src/main/res/values-fa/01-core.xml | 16 +++--- .../src/main/res/values-fa/02-strings.xml | 44 +++++++-------- .../src/main/res/values-fa/03-dialogs.xml | 56 +++++++++---------- .../src/main/res/values-fa/04-network.xml | 2 +- .../res/values-fa/16-multimedia-editor.xml | 2 +- AnkiDroid/src/main/res/values-fr/01-core.xml | 2 +- .../src/main/res/values-fr/02-strings.xml | 4 +- .../src/main/res/values-fr/10-preferences.xml | 4 +- .../src/main/res/values-hi/03-dialogs.xml | 2 +- .../src/main/res/values-hi/07-cardbrowser.xml | 12 ++-- .../src/main/res/values-hi/10-preferences.xml | 2 +- AnkiDroid/src/main/res/values-it/01-core.xml | 4 +- .../src/main/res/values-it/02-strings.xml | 6 +- .../src/main/res/values-it/03-dialogs.xml | 6 +- .../src/main/res/values-it/04-network.xml | 2 +- .../src/main/res/values-it/10-preferences.xml | 4 +- .../main/res/values-it/18-standard-models.xml | 4 +- .../src/main/res/values-or/02-strings.xml | 2 +- .../src/main/res/values-or/03-dialogs.xml | 9 ++- .../src/main/res/values-or/04-network.xml | 4 +- .../src/main/res/values-or/09-backup.xml | 2 +- .../src/main/res/values-or/10-preferences.xml | 4 +- .../main/res/values-or/17-model-manager.xml | 2 +- .../src/main/res/values-pl/02-strings.xml | 4 +- .../src/main/res/values-pl/03-dialogs.xml | 2 +- .../src/main/res/values-pl/10-preferences.xml | 2 +- AnkiDroid/src/main/res/values-sv/01-core.xml | 50 ++++++++--------- .../src/main/res/values-tt/02-strings.xml | 2 +- .../src/main/res/values-tt/03-dialogs.xml | 4 +- .../src/main/res/values-tt/04-network.xml | 2 +- .../src/main/res/values-uk/02-strings.xml | 4 +- .../src/main/res/values-uk/03-dialogs.xml | 4 +- .../src/main/res/values-uk/10-preferences.xml | 2 +- .../main/res/values-zh-rCN/10-preferences.xml | 2 +- .../marketdescription-ar.txt | 2 +- .../marketdescription-fr.txt | 4 +- .../marketdescription-it.txt | 2 +- 48 files changed, 160 insertions(+), 161 deletions(-) diff --git a/AnkiDroid/src/main/res/values-ar/10-preferences.xml b/AnkiDroid/src/main/res/values-ar/10-preferences.xml index d145b60a3061..372ab5a89f97 100644 --- a/AnkiDroid/src/main/res/values-ar/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ar/10-preferences.xml @@ -173,7 +173,7 @@ مهلة مربع الوقت XXX دقيقة طريقة المعالجة الجديدة للمناطق الزمنية - Start of next day (0 is midnight, 23 is 11PM) + بداية اليوم التالي (0 يعني منتصف الليل، 23 يعني الساعة 11 مساءً) XXX ساعة بعد منتصف الليل مجدول V2 التجريبي تفعيل المجدول التجريبي. يفرض التغييرات في جانب واحد عند المزامنة التالية diff --git a/AnkiDroid/src/main/res/values-de/02-strings.xml b/AnkiDroid/src/main/res/values-de/02-strings.xml index b86ae7db97b9..23ccf9fcbaf0 100644 --- a/AnkiDroid/src/main/res/values-de/02-strings.xml +++ b/AnkiDroid/src/main/res/values-de/02-strings.xml @@ -313,7 +313,7 @@ Fehler im Karteninhalt: Laden von »%s« fehlgeschlagen Fehler beim Karteninhalt: Medienaktualisierung erforderlich - Loading http resources is no longer supported + Das Laden von http-Ressourcen wird nicht mehr unterstützt Fehler beim Laden von Stapel »%s« Stapel durchsuchen @@ -345,6 +345,6 @@ + - - Email address is required - Password is required + Die E-Mail-Adresse ist erforderlich + Das Passwort ist erforderlich diff --git a/AnkiDroid/src/main/res/values-de/03-dialogs.xml b/AnkiDroid/src/main/res/values-de/03-dialogs.xml index cd649a875b4c..ad00444da41f 100644 --- a/AnkiDroid/src/main/res/values-de/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-de/03-dialogs.xml @@ -228,8 +228,8 @@ Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Fehlerbehebungsbericht senden + Bericht bereits eingereicht Ein automatische Synchronisierung kann in %d Sekunden ausgelöst werden diff --git a/AnkiDroid/src/main/res/values-de/10-preferences.xml b/AnkiDroid/src/main/res/values-de/10-preferences.xml index cc8e3052ba2b..acf0d1a22faf 100644 --- a/AnkiDroid/src/main/res/values-de/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-de/10-preferences.xml @@ -173,7 +173,7 @@ Zeitlimitwert XXX min Umgang mit neuer Zeitzone - Start of next day (0 is midnight, 23 is 11PM) + Start des nächsten Tages (0 ist Mitternacht, 23 ist 23 Uhr) XXX Stunden nach Mitternacht Experimenteller Zeitplaner V2 Experimentellen Zeitplaner aktivieren. Erzwingt bei der nächsten Synchronisation Änderungen in einer Richtung @@ -290,5 +290,5 @@ Einfache Formatierung von Antworten Behebt das Erscheinen von »􏿾« in eingetippten Antworten - Open Changelog + Änderungsprotokoll öffnen diff --git a/AnkiDroid/src/main/res/values-eo/02-strings.xml b/AnkiDroid/src/main/res/values-eo/02-strings.xml index 00b51ce7d6f7..92047385eff4 100644 --- a/AnkiDroid/src/main/res/values-eo/02-strings.xml +++ b/AnkiDroid/src/main/res/values-eo/02-strings.xml @@ -345,6 +345,6 @@ + - - Email address is required - Password is required + Retpoŝta adreso estas postulata + Pasvorto estas postulata diff --git a/AnkiDroid/src/main/res/values-eo/03-dialogs.xml b/AnkiDroid/src/main/res/values-eo/03-dialogs.xml index e0fdb9056367..828cb82a9deb 100644 --- a/AnkiDroid/src/main/res/values-eo/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-eo/03-dialogs.xml @@ -228,8 +228,8 @@ Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Sendi raporton pri problemo + Raporto sendita Aŭtomata samtempigo povas startigi post %d sekundo diff --git a/AnkiDroid/src/main/res/values-eo/10-preferences.xml b/AnkiDroid/src/main/res/values-eo/10-preferences.xml index 2527abaf092b..41220b832a35 100644 --- a/AnkiDroid/src/main/res/values-eo/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-eo/10-preferences.xml @@ -173,7 +173,7 @@ Limigo de lernada tempero (timebox) XXX min Pritraktado de nova horzono - Start of next day (0 is midnight, 23 is 11PM) + Komenco de tago (0 estas la horo 00:00. 23 estas la horo 23:00) XXX horo(j) post noktmezo Eksperimenta V2 planilo Aktivigi testan planilon V2. Devigas ŝanĝojn en unu direkto je la sekva samtempigo @@ -290,5 +290,5 @@ Simpla aranĝo de enigita respondo Ripari “􏿾” aperantan en entajpita respondo - Open Changelog + Montri ŝanĝoprotokolon diff --git a/AnkiDroid/src/main/res/values-es-rAR/01-core.xml b/AnkiDroid/src/main/res/values-es-rAR/01-core.xml index 8def9e7958c4..b6659d3e6071 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/01-core.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/01-core.xml @@ -71,7 +71,7 @@ %d día %d restante %d días %d restantes - Esta colección está vacía + La colección está vacía Empieza añadiendo tarjetas usando el ícono + 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 c116360a9d0c..377d578ca01a 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml @@ -345,6 +345,6 @@ + - - Email address is required - Password is required + Se requiere dirección de correo electrónico + La contraseña es requerida diff --git a/AnkiDroid/src/main/res/values-es-rES/01-core.xml b/AnkiDroid/src/main/res/values-es-rES/01-core.xml index 4a50fb183cc8..323c1c8785e5 100644 --- a/AnkiDroid/src/main/res/values-es-rES/01-core.xml +++ b/AnkiDroid/src/main/res/values-es-rES/01-core.xml @@ -71,7 +71,7 @@ %d día %d restante %d días %d restantes - Esta colección está vacía + La colección está vacía Empieza añadiendo tarjetas usando el ícono + 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 855f589bd159..3a39e0344bc9 100644 --- a/AnkiDroid/src/main/res/values-es-rES/02-strings.xml +++ b/AnkiDroid/src/main/res/values-es-rES/02-strings.xml @@ -345,6 +345,6 @@ + - - Email address is required - Password is required + Se requiere dirección de correo electrónico + La contraseña es requerida diff --git a/AnkiDroid/src/main/res/values-fa/01-core.xml b/AnkiDroid/src/main/res/values-fa/01-core.xml index b5e40044f04b..d8f88670bb68 100644 --- a/AnkiDroid/src/main/res/values-fa/01-core.xml +++ b/AnkiDroid/src/main/res/values-fa/01-core.xml @@ -202,12 +202,12 @@ مرورگر کارت کارت آنکی - %1$s has a problem:
%2$s
- More information - Found \'{{%1$s}}\', but there is no field called \'%1$s\'. - Found \'{{/%1$s}}\', but missing \'{{#%1$s}}\' or \'{{^%1$s}}\'. - Missing \'}}\' in \'%s\'. - Missing \'{{%s}}\'. - Found \'{{/%1$s}}\', but expected \'{{/%2$s}}\' - Error in filter %s + %1$s دارای مشکل
%2$s است
+ اطلاعات بیشتر + \'{{%1$s}}\' پیدا شد، اما فیلدی به نام %1$s وجود ندارد + {{/%1$s}}\' پیدا شد، اما \'{{#%1$s}}\' یا \'{{^%1$s}}\' وجود ندارد. + \'}}\' در \'%s\' وجود ندارد. + \'{{%s}}\' وجود ندارد. + به جای \'{{/%2$s}}\'، \'{{/%1$s}}\' به کار رفته + خطا در فیلتر %s diff --git a/AnkiDroid/src/main/res/values-fa/02-strings.xml b/AnkiDroid/src/main/res/values-fa/02-strings.xml index 3473adc24c61..0035c7364d1b 100644 --- a/AnkiDroid/src/main/res/values-fa/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fa/02-strings.xml @@ -97,8 +97,8 @@ زمان ویرایش یادداشت - Modify today’s new card limit - Modify today’s review card limit + تعیین محدودیت برای کارت جدید امروز + تعیین محدودیت برای مرور کارت امروز مرور کارت های فراموش شده مرور آنهایي که در شرف مطالعه اند مطالعه کارت ها به صورت تصادفی @@ -110,7 +110,7 @@ کارتی ساخته نشد. لطفاً فیلدهای بیشتری را پر کنید فیلد نخست خالی است این نوع یادداشت باید حاوی کلوز باشد - Set field language + تنظیم زبان زمینه نوع یادداشت فعلی هیچ کارتی تولید نکرد.\nلطفاٌ نوع یادداشت دیگری را انتخاب کنید، یا روی \'کارت‌ها\' کلیک کرده یک فیلد جایگزین را وارد کنید Cloze deletions will only work on a Cloze note type ذخیره یادداشت @@ -198,7 +198,7 @@ فایل %1$s قابل وارد کردن یافت نشد گزینه ها Deck options - Study options + تنظیمات مطالعه تنظیم زبان تبدیل متن به صدا TTS مطالعه بر اساس تنظیمات دلخواه بیشتر @@ -239,7 +239,7 @@ - %s seconds + %s ثانیه @@ -261,7 +261,7 @@ دستگاه شما نرم افزار قادر به انجام این عمل را ندارد. مرورگر یافت نشد، لطفاً صفحۀ مربوطه را در سیستم دیگری باز کنید: - Error loading page: %s + خطای بارگذاری صفحه: %s لینک صفحۀ خارجی درون کارت پشتیبانی نمی‌شود. کارت‌های بازیابی شده @@ -279,34 +279,34 @@ تصویر تخته سفید در %s ذخیره شد رنگ ماژیک تخته سفید - Empty Cards.]]> - Empty first field: %s - First field matched: %s + کارت های خالی را اجرا کنید.]]> + فیلد اول خالی: %s + فیلد اول مطابق با: %s Added duplicate with first field: %s - Appeared twice in file: %s + دوبار در فایل %s وجود دارد One or more notes were not imported, because they didn\'t generate any cards. This can happen when you have empty fields or when you have not mapped the content in the text file to the correct fields ‘%1$s’ had %2$d fields, expected %3$d Aborted: %s Detected automated test. If you are a human, contact AnkiDroid support - %d note added - %d notes added + %d یادداشت اضافه شد + %d یادداشت اضافه شد - %d note updated - %d notes updated + %d یادداشت به روزرسانی شد + %d یادداشت به روزرسانی شد - %d note unchanged - %d notes unchanged + %d یادداشت بدون تغییر + %d یادداشت بدون تغییر This card uses unsupported AnkiDroid features. Contact developer %1$s, or view the wiki. %2$s Card provided invalid data. %s Invalid AnkiDroid JS API version. Contact developer %s, or view wiki AnkiDroid JS API update available. Contact developer %s, or view wiki - View - (Error Code: %d) + نما + (شماره خطا: %d) Copied debug information to clipboard Error copying debug information to clipboard @@ -316,12 +316,12 @@ Loading http resources is no longer supported Failed to load deck ‘%s’ - Search decks - Fatal Error + جستجو در تمام دسته‌ها + خطای مخرب AnkiDroid relies on the System WebView which is unavailable. This can happen if the system is installing updates. Please try again in a few minutes.\n\n%s - Font Size - Show Toolbar + اندازه فونت + نمایش نوار ابزار Format as Bold Format as Italic Format as Underline diff --git a/AnkiDroid/src/main/res/values-fa/03-dialogs.xml b/AnkiDroid/src/main/res/values-fa/03-dialogs.xml index d87b2c5dbcd7..dea38462749c 100644 --- a/AnkiDroid/src/main/res/values-fa/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-fa/03-dialogs.xml @@ -146,11 +146,11 @@ هیچ فایل مفقود شده یا بلا استفاده یافت نشد پایگاه داده رسانه بازسازی شد حذف استفاده نشده ها - Deleting media… - Deletion result + حذف رسانه… + نتیجه حذف - %d file deleted - %d files deleted + %d فایل حذف شد + %d فایل حذف شد همه کارتها @@ -158,7 +158,7 @@ موعد در حال پیدا کردن کارت های خالی… - Do you want to cancel? + کنسل میکنید؟ کارت خالی یافت نشد کارتها برای اینکه حذف شوند: %d کارتهای حذف شده: %d @@ -189,7 +189,7 @@ دیتابیس قفل است نرم‌افزاری دیگر در حال استفاده از دیتابیس آنکی‌دروید است. لطفاً نرم افزار دیگر را ببندید سپس آنکی‌دروید را باز کنید. - Incompatible Database Version + نسخه پایگاه داده ناسازگار است The database is a more advanced version than this version of AnkiDroid can work with. Upgrade AnkiDroid or downgrade the database to open it\n\nSupported version: %1$d\nDatabase version: %2$d\n\nThe following restore options will overwrite your current collection, possibly with a compatible database version: تصویر پس‌زمینه اعمال شد @@ -209,32 +209,32 @@ تنظیم یادآور انجام نشد یادآورهای زیادی تنظیم شده‌اند. بعضی از آنها نمایش داده نخواهند شد - Keyboard Language Selection + انتخاب زبان کیبرد 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. - Using AnkiDroid - AnkiDroid Manual - Anki Manual + استفاده از AnkiDroid + دفترچه راهنما AnkiDroid + دفتر راهنما Anki AnkiDroid FAQ - Get Help - Mailing List - Reddit - Report a Bug - Support AnkiDroid - Donate - Develop - Rate - Other - Community - Anki Forums - Discord - Facebook - Twitter - Send troubleshooting report - Report already submitted + کمک گرفتن + لیست ایمیل‌ها + ردیت + گزارش مشکل + درباره AnkiDroid + کمک مالی + توسعه + امتیازدهی + دیگر + انجمن + فوروم های Anki + دیسکورد + فیسبوک + توییتر + ارسال گزارش عیب یابی + گزارش قبلا ثبت شده است - An automatic sync may be triggered in %d second - An automatic sync may be triggered in %d seconds + همگام سازی خودکار %d ثانیه دیگر آغاز میشود + همگام سازی خودکار %d ثانیه دیگر آغاز میشود diff --git a/AnkiDroid/src/main/res/values-fa/04-network.xml b/AnkiDroid/src/main/res/values-fa/04-network.xml index 32110d6e320f..00d3c53bd99b 100644 --- a/AnkiDroid/src/main/res/values-fa/04-network.xml +++ b/AnkiDroid/src/main/res/values-fa/04-network.xml @@ -128,5 +128,5 @@ همسان کردن (کامل) همسان کردن (ورود به حساب) - Could not connect to AnkiWeb. Is your internet working?\n\n%s + عدم اتصال به AnkiWeb، آیا اتصال به اینترنت برقرار است؟ \n\n%s diff --git a/AnkiDroid/src/main/res/values-fa/16-multimedia-editor.xml b/AnkiDroid/src/main/res/values-fa/16-multimedia-editor.xml index ba4f03190b65..fcf64c71ac41 100644 --- a/AnkiDroid/src/main/res/values-fa/16-multimedia-editor.xml +++ b/AnkiDroid/src/main/res/values-fa/16-multimedia-editor.xml @@ -39,7 +39,7 @@ ضبط صدا ویرایش‌گر پیشرفته انصراف از پاک کردن - Clear field + پاک کردن فیلد قدرت گرفته از glosbe.com diff --git a/AnkiDroid/src/main/res/values-fr/01-core.xml b/AnkiDroid/src/main/res/values-fr/01-core.xml index d3f04464cb3b..c92416f5f6f7 100644 --- a/AnkiDroid/src/main/res/values-fr/01-core.xml +++ b/AnkiDroid/src/main/res/values-fr/01-core.xml @@ -184,7 +184,7 @@ Forcer le paquet Il faut au moins un type de carte. - Supprimer le type de carte “%2$s” et son %1$d carte ? + Supprimer le type de carte « %2$s » et son %1$d carte ? Supprimer le type de carte “%2$s” et ses %1$d cartes ? Une carte n\'a pas pu être générée diff --git a/AnkiDroid/src/main/res/values-fr/02-strings.xml b/AnkiDroid/src/main/res/values-fr/02-strings.xml index 0f4a953727bc..9a16cbe10e8c 100644 --- a/AnkiDroid/src/main/res/values-fr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fr/02-strings.xml @@ -187,7 +187,7 @@ Fichier Anki sauvegardé avec succès Échec de la sauvegarde du fichier Anki Le fichier « %s » a été exporté. Voulez-vous le partager à l\'aide d\'une autre app ? - AnkiDroid a exporté %s flashcards + AnkiDroid a exporté %s cartes mémoire
Ceci est un paquet Anki envoyé depuis AnkiDroid. @@ -345,6 +345,6 @@ + - - L\'adresse e-mail est requise + L\'adresse courriel est requise Mot de passe requis diff --git a/AnkiDroid/src/main/res/values-fr/10-preferences.xml b/AnkiDroid/src/main/res/values-fr/10-preferences.xml index fe4f534595c9..8277b76efec8 100644 --- a/AnkiDroid/src/main/res/values-fr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fr/10-preferences.xml @@ -173,7 +173,7 @@ Limite de temps XXX min Nouvelle gestion du fuseau horaire - Start of next day (0 is midnight, 23 is 11PM) + Début du jour suivant (0 est minuit, 23 est 23 h) XXX heures après minuit Planificateur V2 expérimental Activer le planificateur expérimental. Force les changements dans un sens lors de la prochaine synchronisation @@ -290,5 +290,5 @@ Formatage simple des réponses saisies Fixe « <unk> » dans les résultats des types de réponses - Open Changelog + Ouvrir le journal des modifications diff --git a/AnkiDroid/src/main/res/values-hi/03-dialogs.xml b/AnkiDroid/src/main/res/values-hi/03-dialogs.xml index 4a461f72ad71..649bd53b9e25 100644 --- a/AnkiDroid/src/main/res/values-hi/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-hi/03-dialogs.xml @@ -219,7 +219,7 @@ Reddit Report a Bug AnkiDroid का समर्थन करें - Donate + दान करें Develop Rate अन्य diff --git a/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml index 4c56d617b3c7..87b5883a666e 100644 --- a/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml @@ -87,15 +87,15 @@ (अध्ययन) No cards found in deck ‘%s’ Search all decks - Unknown + अज्ञात - %d card deleted - %d cards deleted + %d कार्ड हटाया गया + %d कार्ड हटाए गए - Added - First Review - Latest Review + जोड़ा गया + पहली समीक्षा + नवीनतम समीक्षा Interval आसानी Reviews diff --git a/AnkiDroid/src/main/res/values-hi/10-preferences.xml b/AnkiDroid/src/main/res/values-hi/10-preferences.xml index 20dc83bd2de7..93387e9bbce8 100644 --- a/AnkiDroid/src/main/res/values-hi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hi/10-preferences.xml @@ -173,7 +173,7 @@ Timebox समय सीमा XXX min New timezone handling - Start of next day (0 is midnight, 23 is 11PM) + अगले दिन की शुरुआत (0 आधी रात है, 23 11PM है) XXX घंटे पिछले आधी रात प्रायोगिक V2 अनुसूचक Enable the experimental scheduler. Forces changes in one direction on next sync diff --git a/AnkiDroid/src/main/res/values-it/01-core.xml b/AnkiDroid/src/main/res/values-it/01-core.xml index ca124b4f23eb..d0d9eb246d63 100644 --- a/AnkiDroid/src/main/res/values-it/01-core.xml +++ b/AnkiDroid/src/main/res/values-it/01-core.xml @@ -184,8 +184,8 @@ Sovrascrittura Mazzo È necessario almeno un tipo di carta. - Eliminare il tipo di carta \'%2$s\', e la sua carta %1$d? - Eliminare il tipo di carta \'%2$s\', e le sue carte %1$d? + Eliminare il tipo di carta «%2$s», e la sua carta %1$d? + Eliminare il tipo di carta «%2$s», e le sue carte %1$d? Una carta non può essere generata La rimozione di questo tipo di carta causerebbe l\'eliminazione di una o più note. Si prega di creare e salvare prima un nuovo tipo di carta. diff --git a/AnkiDroid/src/main/res/values-it/02-strings.xml b/AnkiDroid/src/main/res/values-it/02-strings.xml index 408e2cc4c3cb..af544e900c12 100644 --- a/AnkiDroid/src/main/res/values-it/02-strings.xml +++ b/AnkiDroid/src/main/res/values-it/02-strings.xml @@ -313,7 +313,7 @@ Errore nel contenuto della carta: impossibile caricare «%s» Errore del Contenuto della Carta: Richiesto aggiornamento multimediale - Loading http resources is no longer supported + Il caricamento delle risorse http non è più supportato Impossibile caricare il mazzo «%s» Cerca mazzi @@ -345,6 +345,6 @@ + - - Email address is required - Password is required + L\'indirizzo di posta elettronica è obbligatorio. + La password è obbligatoria diff --git a/AnkiDroid/src/main/res/values-it/03-dialogs.xml b/AnkiDroid/src/main/res/values-it/03-dialogs.xml index 78932e1cd271..7ad0d1797d8d 100644 --- a/AnkiDroid/src/main/res/values-it/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-it/03-dialogs.xml @@ -215,7 +215,7 @@ Manuale di Anki FAQ di AnkiDroid Chiedi Aiuto - Mailing List + Lista di diffusione Reddit Segnala un Bug Supporta AnkiDroid @@ -228,8 +228,8 @@ Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Invia un rapporto sulla risoluzione dei problemi + Rapporto già inviato Una sincronizzazione automatica potrebbe essere attivata in %d secondo diff --git a/AnkiDroid/src/main/res/values-it/04-network.xml b/AnkiDroid/src/main/res/values-it/04-network.xml index 5a0b5c4e7ce0..c66919fa4030 100644 --- a/AnkiDroid/src/main/res/values-it/04-network.xml +++ b/AnkiDroid/src/main/res/values-it/04-network.xml @@ -58,7 +58,7 @@ Connesso come Esci Accesso in corso, attendi... - Indirizzo email o password errati + Indirizzo di posta elettronica o password errati Reimposta la password Preferenze diff --git a/AnkiDroid/src/main/res/values-it/10-preferences.xml b/AnkiDroid/src/main/res/values-it/10-preferences.xml index f8aca02b43d3..7bb07cc39aab 100644 --- a/AnkiDroid/src/main/res/values-it/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-it/10-preferences.xml @@ -173,7 +173,7 @@ Limite di tempo per sessione XXX min Nuova gestione fuso orario - Start of next day (0 is midnight, 23 is 11PM) + Inizio del giorno successivo (0 è mezzanotte, 23 è 23 ore) XXX ore dopo mezzanotte Pianificatore sperimentale V2 Abilita la pianificazione sperimentale. Forza le modifiche in una direzione alla prossima sincronizzazione @@ -290,5 +290,5 @@ Formattazione semplice della risposta Correzioni ‘􏿾’ che appaiono nei risultati delle risposte digitate - Open Changelog + Apri il registro dei cambiamenti diff --git a/AnkiDroid/src/main/res/values-it/18-standard-models.xml b/AnkiDroid/src/main/res/values-it/18-standard-models.xml index cfba3d499168..b05f2907e2fc 100644 --- a/AnkiDroid/src/main/res/values-it/18-standard-models.xml +++ b/AnkiDroid/src/main/res/values-it/18-standard-models.xml @@ -20,8 +20,8 @@ - Anteriore - Indietro + Fronte + Retro Testo Extra Aggiungi inverso diff --git a/AnkiDroid/src/main/res/values-or/02-strings.xml b/AnkiDroid/src/main/res/values-or/02-strings.xml index ccf90972ce50..d2ccf93bef98 100644 --- a/AnkiDroid/src/main/res/values-or/02-strings.xml +++ b/AnkiDroid/src/main/res/values-or/02-strings.xml @@ -145,7 +145,7 @@ %dଟି କାର୍ଡ ଯୋଗ କରାଯାଇଛି ଡାଟାବେସ୍ ଯାଞ୍ଚ - ମିଡ଼ିଆ ଯାଞ୍ଚ କରନ୍ତୁ + ମିଡ଼ିଆ ଯାଞ୍ଚ ଖାଲି କାର୍ଡଗୁଡ଼ିକ ଡାଟାବେସ୍ ଯାଞ୍ଚ କରାଯାଉଛି... ଏହି ପତ୍ରଟି ଖାଲି ଅଛି। ତାସଖଣ୍ଡ ଚିଠାରୁ ମେନୁରେ \"ଖାଲି କାର୍ଡଗୁଡ଼ିକ\" ବିକଳ୍ପ ବାଛନ୍ତୁ। diff --git a/AnkiDroid/src/main/res/values-or/03-dialogs.xml b/AnkiDroid/src/main/res/values-or/03-dialogs.xml index afd5b43de116..ed1e0ba87e53 100644 --- a/AnkiDroid/src/main/res/values-or/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-or/03-dialogs.xml @@ -54,8 +54,7 @@ TTS ଆରମ୍ଭ ହେଉଛି... କଥା କୁହ ନାହିଁ - %1$s:
ଆଣ୍ଡ୍ରଏଡ୍ ପାଇଁ ଫ୍ଲାସକାର୍ଡAnkiDroid ହେଉଛି ଏକ ଫ୍ଲାସକାର୍ଡ ଆପ୍ଲିକେସନ୍ ଯାହା ଡେସ୍କଟପ୍ ସଫ୍ଟୱେର୍ -Anki ସହିତ ସମ୍ପୂର୍ଣ୍ଣ ସୁସଙ୍ଗତ। ଆପଣ ଉଭୟ Ankidroid କିମ୍ବା Anki ଡେସ୍କଟପରେ ସାମଗ୍ରୀ ସୃଷ୍ଟି, ଡାଉନଲୋଡ୍ ଏବଂ ଶିଖିପାରିବେ, ଏବଂ ସେମାନଙ୍କୁ AnkiWeb ସହିତ ସହଜରେ ସିଙ୍କ୍ରୋନାଇଜ୍ କରିପାରିବେ।]]>
+ %1$s:
ଆଣ୍ଡ୍ରଏଡ୍ ପାଇଁ ଫ୍ଲାସକାର୍ଡAnkiDroid ହେଉଛି ଏକ ଫ୍ଲାସକାର୍ଡ ଆପ୍ଲିକେସନ୍ ଯାହା ଡେସ୍କଟପ୍ ସଫ୍ଟୱେର୍ Anki ସହିତ ସମ୍ପୂର୍ଣ୍ଣ ସୁସଙ୍ଗତ। ଆପଣ ଉଭୟ Ankidroid କିମ୍ବା Anki ଡେସ୍କଟପରେ ସାମଗ୍ରୀ ସୃଷ୍ଟି, ଡାଉନଲୋଡ୍ ଏବଂ ଶିଖିପାରିବେ, ଏବଂ ସେମାନଙ୍କୁ AnkiWeb ସହିତ ସହଜରେ ସିଙ୍କ୍ରୋନାଇଜ୍ କରିପାରିବେ।]]>
issue trackerକୁ ତ୍ରୁଟି ରିପୋର୍ଟ କରନ୍ତୁ; ନୂତନ ବୈଶିଷ୍ଟ୍ୟ ଚିନ୍ତାଧାରା ମଧ୍ୟ ବହୁତ ସ୍ୱାଗତଯୋଗ୍ୟ। Wikiରେ ଉତ୍ତର ଖୋଜ କିମ୍ବା Forumରେ ଏକ ଆଲୋଚନା ଆରମ୍ଭ କର।]]> ଖୋଲା-ଉତ୍ସ ସଫ୍ଟୱେର, ତେଣୁ ସମସ୍ତଙ୍କୁ ଯୋଗଦାନ କରିବାକୁ ଉତ୍ସାହିତ କରାଯାଏ :-)]]> ଅନୁବାଦ କରିବା, Wiki ଅପଡେଟ୍ କରିବା, screenshot ପ୍ରଦାନ କରିବା, କିମ୍ବା Ankidroid ବିଷୟରେ blog କରିବାରେ ସାହାଯ୍ୟ କରିପାରିବେ।]]> @@ -64,10 +63,10 @@ ପୂର୍ବାବଲୋକନ ନୂତନ କାର୍ଡଗୁଡ଼ିକ ପୁନଃସ୍ଥାନିତ କରନ୍ତୁ ଆରମ୍ଭ ସ୍ଥିତି: - କେବଳ ନୂତନ କାର୍ଡ ପୁନଃଅବସ୍ଥିତ କରାଯାଇପାରିବ + କେବଳ ନୂତନ କାର୍ଡ ବିଳମ୍ବ କରାଯାଇପାରିବ %d କାର୍ଡ ପୁନଃଅବସ୍ଥିତ - %dଟି କାର୍ଡ ପୁନଃଅବସ୍ଥିତ + %dଟି କାର୍ଡ ବିଳମ୍ବ Reset card progress ଏହି କାର୍ଡକୁ ନୂତନ କାର୍ଡ ଧାଡି ଶେଷରେ ରଖାଯିବ @@ -89,7 +88,7 @@ %d କାର୍ଡ reschedule ହୋଇଛି %dଟି କାର୍ଡ reschedule ହୋଇଛି
- ଏକ ନୂତନ ପତ୍ର ପୁନଃନିର୍ମାଣ କରାଯାଉଛି | ପୁନଃନିର୍ଦ୍ଧାରଣ ନୂତନ କାର୍ଡଗୁଡ଼ିକୁ ସମୀକ୍ଷା ପତ୍ରରେ ପରିଣତ କରେ | ନୂତନ ଧାଡିରେ ଏକ ନୂତନ ପତ୍ରର ସ୍ଥିତି ପରିବର୍ତ୍ତନ କରିବାକୁ ‘ଅବସ୍ଥାନ ପତ୍ର’ ବିକଳ୍ପ ବ୍ୟବହାର କରନ୍ତୁ + ଏକ ନୂତନ ପତ୍ର ପୁନଃନିର୍ମାଣ କରାଯାଉଛି। ପୁନଃନିର୍ଦ୍ଧାରଣ ନୂତନ କାର୍ଡଗୁଡ଼ିକୁ ସମୀକ୍ଷା ପତ୍ରରେ ପରିଣତ କରେ। ନୂତନ ଧାଡିରେ ଏକ ନୂତନ ପତ୍ରର ସ୍ଥିତି ପରିବର୍ତ୍ତନ କରିବାକୁ ‘ପତ୍ର ବିଳମ୍ବ’ ବିକଳ୍ପ ବ୍ୟବହାର କରନ୍ତୁ ଭବିଷ୍ୟତରେ ନିର୍ଦ୍ଧାରକ କିପରି ପତ୍ରଙ୍କୁ ବ୍ୟବହାର କରେ ତାହା ମଧ୍ୟ ପୁନଃସେଟ୍ କଲେ ପୁନଃସେଟ୍ ହେଇଯାଏ (ସହଜତା %d%% ରେ ସେଟ୍ ହେବ) ଆପଣଙ୍କର ଫାଇଲ୍ ମ୍ୟାନେଜର୍ ରେ ତାସଖଣ୍ଡ ଚୟନ କରନ୍ତୁ କିମ୍ବା ଏହାକୁ ପରିଦର୍ଶନ କରନ୍ତୁ: ଏହାକୁ ଆମଦାନୀ କରିବାକୁ ଡାଉନଲୋଡ୍ ପୃଷ୍ଠା ଡାଟାବେସ୍ ତ୍ରୁଟି diff --git a/AnkiDroid/src/main/res/values-or/04-network.xml b/AnkiDroid/src/main/res/values-or/04-network.xml index cb9fc0a78df9..b2d5a615a7e6 100644 --- a/AnkiDroid/src/main/res/values-or/04-network.xml +++ b/AnkiDroid/src/main/res/values-or/04-network.xml @@ -79,8 +79,8 @@ , ସମୟ କ୍ଷେତ୍ର ସମ୍ଭବତ ଭୁଲ୍ ଅଟେ , ତାରିଖ ସମ୍ଭବତ ଭୁଲ ଅଟେ ସିଙ୍କ୍ ରେ ଦ୍ୱନ୍ଦ୍ୱ - କୌଣସି ପରିବର୍ତ୍ତନ ମିଳିଲା ନାହିଁ | - ସଂଗ୍ରହ ସମକାଳୀକ ହୋଇଛି | + କୌଣସି ପରିବର୍ତ୍ତନ ମିଳିଲା ନାହିଁ। + ସଂଗ୍ରହ ସମକାଳୀକ ହୋଇଛି। ସିଙ୍କ୍ ତ୍ରୁଟି, ପ୍ରକାର: %1$s, ବାର୍ତ୍ତା: %2$s ଆପଣଙ୍କର ଡାଟାବେସ୍ ଭ୍ରଷ୍ଟ ଅଟେ। ସିଙ୍କ୍ କରିବାକୁ ପୁନର୍ବାର ଚେଷ୍ଟା କରିବା ପୂର୍ବରୁ ଦୟାକରି ଏହାକୁ ଠିକ୍ କରନ୍ତୁ।\n\nଆପଣଙ୍କର ଡାଟାବେସ୍ ମରାମତି ବିଷୟରେ ସୂଚନା ପାଇଁ %s ଦେଖନ୍ତୁ। ସର୍ଭର ବ୍ୟସ୍ତ | ପରେ ପୁନର୍ବାର ଚେଷ୍ଟା କରନ୍ତୁ | diff --git a/AnkiDroid/src/main/res/values-or/09-backup.xml b/AnkiDroid/src/main/res/values-or/09-backup.xml index d0ee3f8a0e36..059305ed6cbc 100644 --- a/AnkiDroid/src/main/res/values-or/09-backup.xml +++ b/AnkiDroid/src/main/res/values-or/09-backup.xml @@ -40,7 +40,7 @@ ବ୍ୟାକଅପ୍ ସଞ୍ଚୟ ହୋଇନାହିଁ। SD କାର୍ଡରେ ପର୍ଯ୍ୟାପ୍ତ ସ୍ଥାନ ବାକି ନାହିଁ। ବ୍ୟାକଅପ୍ ଗଭୀରତାକୁ କମ୍ କରନ୍ତୁ କିମ୍ବା ଅନ୍ୟ କିଛି ଫାଇଲ୍ ଅପସାରଣ କରନ୍ତୁ। ଆପଣଙ୍କ SD କାର୍ଡରେ %d MB ରୁ କମ୍ ସ୍ଥାନ ବାକି ଅଛି।\nଡାଟା ନଷ୍ଟକୁ ଏଡାଇବା ପାଇଁ କିଛି ସ୍ଥାନ ଖାଲି କରନ୍ତୁ। - ବ୍ୟାକଅପ୍ ରୁ ପୁନରୁଦ୍ଧାର କରନ୍ତୁ + ବ୍ୟାକଅପ୍ ରୁ ପୁନରୁଦ୍ଧାର ନୂତନ ସଂଗ୍ରହ ସଂଗ୍ରହ ଅପସାରଣ କରନ୍ତୁ ଏବଂ ନୂତନ ନିର୍ମାଣ କରନ୍ତୁ ସଂଗ୍ରହ ବିଲୋପ କରନ୍ତୁ ଏବଂ ଏକ ନୂତନ ସୃଷ୍ଟି କରନ୍ତୁ? ଏହା ଆପଣଙ୍କର ସମସ୍ତ ଶିକ୍ଷଣ ପ୍ରଗତିକୁ ଛାଡ଼ି ଦେବ ଏବଂ ସମସ୍ତ ପତ୍ର ବିଲୋପ କରିବ | diff --git a/AnkiDroid/src/main/res/values-or/10-preferences.xml b/AnkiDroid/src/main/res/values-or/10-preferences.xml index 4ca1859f4db9..d424fa55291a 100644 --- a/AnkiDroid/src/main/res/values-or/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-or/10-preferences.xml @@ -139,7 +139,7 @@ ଭାଷା ସିଷ୍ଟମ୍ ଭାଷା ବିଜ୍ଞପ୍ତିଗୁଡିକ - କେତେବେଳେ ସୂଚିତ କର + କେତେବେଳେ ସୂଚିତ କରିବି କଦାପି ସୂଚିତ କରନାହିଁ ବିଚାରାଧୀନ ବାର୍ତ୍ତା ଉପଲବ୍ଧ %d ରୁ ଅଧିକ ପତ୍ର ବାକି ଅଛି @@ -173,7 +173,7 @@ ଟାଇମ୍ ବକ୍ସ ସମୟ ସୀମା XXX ମିନିଟ୍ ନୂତନ ସମୟ କ୍ଷେତ୍ର ପରିଚାଳନା - Start of next day (0 is midnight, 23 is 11PM) + ପରବର୍ତ୍ତୀ ଦିନର ଆରମ୍ଭ (0 ଅର୍ଥ ମଧ୍ୟରାତ୍ରି, 23 ହେଉଛି 11 PM) XXX ଘଣ୍ଟା ମଧ୍ୟରାତ୍ରି ପରୀକ୍ଷାମୂଳକ V2 ନିର୍ଧାରକ | ପରୀକ୍ଷାମୂଳକ କାର୍ଯ୍ୟସୂଚୀ ସକ୍ଷମ କରନ୍ତୁ | ପରବର୍ତ୍ତୀ ସିଙ୍କରେ ଗୋଟିଏ ଦିଗରେ ପରିବର୍ତ୍ତନ କରିବାକୁ ବାଧ୍ୟ କରେ diff --git a/AnkiDroid/src/main/res/values-or/17-model-manager.xml b/AnkiDroid/src/main/res/values-or/17-model-manager.xml index 630437921083..14e7cbc17025 100644 --- a/AnkiDroid/src/main/res/values-or/17-model-manager.xml +++ b/AnkiDroid/src/main/res/values-or/17-model-manager.xml @@ -20,7 +20,7 @@ - ନୋଟ୍ ପ୍ରକାରଗୁଡ଼ିକ ପରିଚାଳନା କରନ୍ତୁ + ନୋଟ୍ ପ୍ରକାରଗୁଡ଼ିକ ପରିଚାଳନା କ୍ଷେତ୍ରଗୁଡିକ ପରିଚାଳନା କର ଆପଣ ଆପଣଙ୍କର ଶେଷ ନୋଟ୍ ପ୍ରକାର ବିଲୋପ କରିପାରିବେ ନାହିଁ diff --git a/AnkiDroid/src/main/res/values-pl/02-strings.xml b/AnkiDroid/src/main/res/values-pl/02-strings.xml index d7e1d7055646..d4f09db74be2 100644 --- a/AnkiDroid/src/main/res/values-pl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pl/02-strings.xml @@ -371,6 +371,6 @@ + - - Email address is required - Password is required + Adres e-mail jest wymagany + Hasło jest wymagane diff --git a/AnkiDroid/src/main/res/values-pl/03-dialogs.xml b/AnkiDroid/src/main/res/values-pl/03-dialogs.xml index 952e93230a85..c9214875680d 100644 --- a/AnkiDroid/src/main/res/values-pl/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-pl/03-dialogs.xml @@ -243,7 +243,7 @@ Facebook Twitter Send troubleshooting report - Report already submitted + Raport został już wysłany Automatyczna synchronizacja może zostać uruchomiona za %d sekundę diff --git a/AnkiDroid/src/main/res/values-pl/10-preferences.xml b/AnkiDroid/src/main/res/values-pl/10-preferences.xml index ac9c555b90c0..eb33b353e1f6 100644 --- a/AnkiDroid/src/main/res/values-pl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pl/10-preferences.xml @@ -294,5 +294,5 @@ Proste formatowanie wpisywanej odpowiedzi Naprawia ‘􏿾’ pojawiające się we wpisywanych odpowiedziach - Open Changelog + Otwórz listę zmian w programie diff --git a/AnkiDroid/src/main/res/values-sv/01-core.xml b/AnkiDroid/src/main/res/values-sv/01-core.xml index 551bcd725b18..8507e6a90828 100644 --- a/AnkiDroid/src/main/res/values-sv/01-core.xml +++ b/AnkiDroid/src/main/res/values-sv/01-core.xml @@ -38,7 +38,7 @@ ~ this program. If not, see . --> - yes + ja Kortlekar Kortbläddrare @@ -64,15 +64,15 @@ %d minuter kvar
- %d hour %d left - %d hours %d left + %d timme %d kvar + %d timmar %d kvar - %d day %d left - %d days %d left + %d dag %d kvar + %d dagar %d kvar - Collection is empty - Start adding cards\nusing the + icon. + Samlingen är tom + Börja lägga till kort\nmed + knappen %1$d kort studerade i %2$s idag @@ -81,7 +81,7 @@ Skriv svar Visa svar - Hide answer + Följ svar Igen Svår Bra @@ -94,7 +94,7 @@ Flytta alla till kortlek Sluta dölja Byt namn på lek - Create shortcut + Skapa genväg Lägg till Lägg till not Synkronisera konto @@ -106,20 +106,20 @@ Dölj Lås Ta bort not - Flag - Flag card - No flag - Red flag - Orange flag - Green flag - Blue flag - Edit tags + Flagga + Flagga kort + Ingen flagga + Röd flagga + Orange flagga + Grön flagga + Blå flagga + Redigera taggar Vill du verkligen ta bort den här noten och alla dess kort?\n%s Markera text Sökning i %1$s Markera not Avmarkera not - Check pronunciation + Kontrollera uttal Skapa kortlek Skapa Skapa filtrerad kortlek @@ -129,7 +129,7 @@ The user is taken to the "App Info" screen, and needs to click "Permissions" and grant AnkiDroid the \'Storage\' permission. This needs to be a fairly generic message as implementations of the permissions/app info screen will differ between devices. --> - Please grant AnkiDroid the ‘Storage’ permission to continue + Vänligen ge AnkiDroid behörigheten \'Lagring\' för att fortsätta - Rebuilding filtered deck… + Bygger om filtrerad kortlek... Bygg om Töm - Create subdeck - Emptying filtered deck… + Skapa underkortlek + Tömmer filtrerad kortlek... Anpassad studiesession Var god döp om den existerande anpassade studiekortleken först. Den här leken är tom - Add card - Invalid deck name + Lägg till kort + Ogiltigt kortleksnamn Den här leken är tom. Använd +-knappen för att lägga till nytt innehåll. Den dagliga studiegränsen har nåtts Inga kort finns schemalagda @@ -180,7 +180,7 @@ Kortmall för framsida Kortmall för baksida Formgivning - Card Browser Appearance + Visning i kortbläddrare Deck Override Minst en korttyp krävs. diff --git a/AnkiDroid/src/main/res/values-tt/02-strings.xml b/AnkiDroid/src/main/res/values-tt/02-strings.xml index d5452df33dfe..4b86f7083edd 100644 --- a/AnkiDroid/src/main/res/values-tt/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tt/02-strings.xml @@ -216,7 +216,7 @@ %s minutes - %s hours + %s сәгатъ %s days diff --git a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml index fd377f0c1728..be30fc66d92f 100644 --- a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml @@ -110,9 +110,9 @@ SD card almost full Restore backup? Your collection will be replaced with an older copy. Any unsaved progress will be lost. - Cancel + Баш тарту OK - No + Юк Continue Create Delete diff --git a/AnkiDroid/src/main/res/values-tt/04-network.xml b/AnkiDroid/src/main/res/values-tt/04-network.xml index d854e7ae54ae..cbc738948f8a 100644 --- a/AnkiDroid/src/main/res/values-tt/04-network.xml +++ b/AnkiDroid/src/main/res/values-tt/04-network.xml @@ -42,7 +42,7 @@ Сез челтәрдә түгел Try Anyway Sync error - Error + Хата Retry Get shared decks A network error has occurred diff --git a/AnkiDroid/src/main/res/values-uk/02-strings.xml b/AnkiDroid/src/main/res/values-uk/02-strings.xml index e333c567e95f..a847d178169a 100644 --- a/AnkiDroid/src/main/res/values-uk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-uk/02-strings.xml @@ -371,6 +371,6 @@ + - - Email address is required - Password is required + Необхідно вказати адресу електронної пошти + Поле «Пароль» обов\'язкове до заповнення diff --git a/AnkiDroid/src/main/res/values-uk/03-dialogs.xml b/AnkiDroid/src/main/res/values-uk/03-dialogs.xml index 4ceb72ef69dd..270d6572d7c5 100644 --- a/AnkiDroid/src/main/res/values-uk/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-uk/03-dialogs.xml @@ -242,8 +242,8 @@ Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Надіслати звіт про несправності + Звіт вже надіслано Автоматична синхронізація може запуститися через %d секунду. diff --git a/AnkiDroid/src/main/res/values-uk/10-preferences.xml b/AnkiDroid/src/main/res/values-uk/10-preferences.xml index 1b4611bc2a7f..4c14006a15d9 100644 --- a/AnkiDroid/src/main/res/values-uk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-uk/10-preferences.xml @@ -174,7 +174,7 @@ Період лічильника переглядів XXX хв. Обробка нового часового поясу - Start of next day (0 is midnight, 23 is 11PM) + Початок наступного дня (0 – опівніч, 23 – 23:00) XXX год. Експериментальний планувальник V2 Увімкнути експериментальний планувальник. Примушує зміни в одному напрямку при наступній синхронізації 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 f85f37504052..cd3a7dfeb7f3 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml @@ -173,7 +173,7 @@ 时间盒时间限制 XXX 分钟 新时区处理 - Start of next day (0 is midnight, 23 is 11PM) + 次日开始时间 (0为午夜,23为 11PM) 午夜过后的XXX小时 实验性 V2 调度器 启用实验性调度器。在下次同步时强制单向变更 diff --git a/docs/marketing/localized_description/marketdescription-ar.txt b/docs/marketing/localized_description/marketdescription-ar.txt index 4002f5118e57..fe7ccccbc01f 100644 --- a/docs/marketing/localized_description/marketdescription-ar.txt +++ b/docs/marketing/localized_description/marketdescription-ar.txt @@ -12,7 +12,7 @@ • دعم محتويات البطاقات التعليمية: نصوص، صور، أصوات، LaTeX • التكرار المتباعد (خوارزمية supermemo 2) • دعم ميزة تحويل النص إلى كلام -• أكثر من 6000 رزمة جاهزة +• أكثر من 6000 شَدّة أوراق جاهزة • عنصر واجهة تقدم التعلم • إحصائيات مفصلة • المزامنة مع أنكي ويب diff --git a/docs/marketing/localized_description/marketdescription-fr.txt b/docs/marketing/localized_description/marketdescription-fr.txt index 3ab0e4f7aadb..ed3ab95bf449 100644 --- a/docs/marketing/localized_description/marketdescription-fr.txt +++ b/docs/marketing/localized_description/marketdescription-fr.txt @@ -16,9 +16,9 @@ Ajoutez des cartes par le biais de l’application Anki pour ordinateur ou direc • widget progression • statistiques détaillées • synchronisation avec AnkiWeb -• open source +• code source ouvert -★ Fonctionnalités supplémentaires: +★ Fonctionnalités supplémentaires : • écrire les réponses (en option) • tableau blanc • éditeur de cartes diff --git a/docs/marketing/localized_description/marketdescription-it.txt b/docs/marketing/localized_description/marketdescription-it.txt index 9ab301e30276..2670ae854953 100644 --- a/docs/marketing/localized_description/marketdescription-it.txt +++ b/docs/marketing/localized_description/marketdescription-it.txt @@ -16,7 +16,7 @@ Aggiungi materiale con l'applicazione per desktop Anki o direttamente con AnkiDr • widget con progresso • statistiche dettagliate • sincronizzazione con AnkiWeb -• open source +• codice sorgente aperto ★ Caratteristiche aggiuntive: • risposte scritte (opzionale) From 3cca3b1f6e4c8edf05386a4652fb5e34566ddf26 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Thu, 1 Apr 2021 01:01:19 +0000 Subject: [PATCH 019/171] Bumped version to 2.15alpha37 @branch-specific --- AnkiDroid/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index c90b8aa15b4a..42b9bf6079b8 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -50,8 +50,8 @@ 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=21500136 - versionName="2.15alpha36" + versionCode=21500137 + versionName="2.15alpha37" minSdkVersion 21 //noinspection OldTargetApi - also performed in api/build.fradle targetSdkVersion 29 // change .travis.yml platform download at same time From b722525f6322befbf28c08de46d0ca4beb16bf06 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 31 Mar 2021 01:21:32 +0200 Subject: [PATCH 020/171] NF: indicate that mUndoNameId is a string ressource --- AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java index b8251910fcde..1fbcef3c9669 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java @@ -80,6 +80,7 @@ import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.sqlite.db.SupportSQLiteDatabase; import androidx.sqlite.db.SupportSQLiteStatement; @@ -172,6 +173,7 @@ public enum DismissType { RESCHEDULE_CARDS(R.string.card_editor_reschedule_card), RESET_CARDS(R.string.card_editor_reset_card); + @StringRes private final int mUndoNameId; DismissType(int undoNameId) { From 6cc4d6e2896c311a5c7ec35b5f33231e3717835c Mon Sep 17 00:00:00 2001 From: Akshay Vilas Jadhav <52353967+Akshay0701@users.noreply.github.com> Date: Fri, 2 Apr 2021 03:19:40 +0530 Subject: [PATCH 021/171] Add deck search in NoteEditor (#8381) * search deck in NoteEditor * extracted "Deck Search" to resources * removed onNeutral --- .../com/ichi2/anki/CardTemplateEditor.java | 2 +- .../main/java/com/ichi2/anki/NoteEditor.java | 43 +++++++++++++------ .../anki/dialogs/DeckSelectionDialog.java | 23 ++++++---- AnkiDroid/src/main/res/values/01-core.xml | 1 + 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java index 3815c0612204..11384211a857 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java @@ -559,7 +559,7 @@ private void displayDeckOverrideDialog(Collection col, TemporaryModel tempModel) FunctionalInterfaces.Filter nonDynamic = (d) -> !Decks.isDynamic(d); List decks = SelectableDeck.fromCollection(col, nonDynamic); String title = getString(R.string.card_template_editor_deck_override); - DeckSelectionDialog dialog = DeckSelectionDialog.newInstance(title, explanation, decks); + DeckSelectionDialog dialog = DeckSelectionDialog.newInstance(title, explanation, true, decks); AnkiActivity.showDialogFragment(activity, dialog); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java index d2e5774c151c..5b012ebcb4f2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java @@ -51,6 +51,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; @@ -66,6 +67,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anki.dialogs.ConfirmationDialog; +import com.ichi2.anki.dialogs.DeckSelectionDialog; import com.ichi2.anki.dialogs.DiscardChangesDialog; import com.ichi2.anki.dialogs.IntegerDialog; import com.ichi2.anki.dialogs.LocaleSelectionDialog; @@ -92,6 +94,7 @@ import com.ichi2.compat.CompatHelper; import com.ichi2.libanki.Card; import com.ichi2.libanki.Collection; +import com.ichi2.libanki.Decks; import com.ichi2.libanki.Models; import com.ichi2.libanki.Model; import com.ichi2.libanki.Note; @@ -105,6 +108,7 @@ import com.ichi2.utils.ContentResolverUtil; import com.ichi2.utils.DeckComparator; import com.ichi2.utils.FileUtil; +import com.ichi2.utils.FunctionalInterfaces; import com.ichi2.utils.FunctionalInterfaces.Consumer; import com.ichi2.utils.KeyUtils; import com.ichi2.utils.MapUtil; @@ -149,6 +153,7 @@ * @see the Anki Desktop manual */ public class NoteEditor extends AnkiActivity implements + DeckSelectionDialog.DeckSelectionListener, TagsDialog.TagsDialogListener { // DA 2020-04-13 - Refactoring Plans once tested: // * There is a difference in functionality depending on whether we are editing @@ -253,6 +258,22 @@ private SaveNoteHandler saveNoteHandler() { return new SaveNoteHandler(this); } + + @Override + public void onDeckSelected(@Nullable DeckSelectionDialog.SelectableDeck deck) { + if (deck != null) { + mCurrentDid = deck.getDeckId(); + mNoteDeckSpinner.setSelection(mAllDeckIds.indexOf(deck.getDeckId()), false); + } + } + + private void displayDeckOverrideDialog(Collection col) { + FunctionalInterfaces.Filter nonDynamic = (d) -> !Decks.isDynamic(d); + List decks = DeckSelectionDialog.SelectableDeck.fromCollection(col, nonDynamic); + DeckSelectionDialog dialog = DeckSelectionDialog.newInstance(getString(R.string.search_deck), null, false, decks); + AnkiActivity.showDialogFragment(NoteEditor.this, dialog); + } + private enum AddClozeType { SAME_NUMBER, INCREMENT_NUMBER @@ -623,19 +644,15 @@ public View getDropDownView(int position, View convertView, ViewGroup parent) { }; mNoteDeckSpinner.setAdapter(noteDeckAdapter); noteDeckAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mNoteDeckSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { + mNoteDeckSpinner.setOnTouchListener(new View.OnTouchListener() { - @Override - public void onItemSelected(AdapterView parent, View view, int pos, long id) { - // Timber.i("NoteEditor:: onItemSelected() fired on mNoteDeckSpinner with pos = %d", pos); - mCurrentDid = mAllDeckIds.get(pos); - } - - @Override - public void onNothingSelected(AdapterView parent) { - // Do Nothing - } - }); + public boolean onTouch(View view, MotionEvent motionEvent) { + if (motionEvent.getAction() == MotionEvent.ACTION_UP) { + displayDeckOverrideDialog(getCol()); + } + return true; + } + }); mCurrentDid = intent.getLongExtra(EXTRA_DID, mCurrentDid); String mGetTextFromSearchView = intent.getStringExtra(EXTRA_TEXT_FROM_SEARCH_VIEW); @@ -767,7 +784,7 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { case KeyEvent.KEYCODE_D: //null check in case Spinner is moved into options menu in the future if (event.isCtrlPressed() && (mNoteDeckSpinner != null)) { - mNoteDeckSpinner.performClick(); + displayDeckOverrideDialog(getCol()); } break; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java index 63a9f4506bee..78d02058eb84 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java @@ -59,11 +59,12 @@ public class DeckSelectionDialog extends AnalyticsDialogFragment { * A dialog which handles selecting a deck */ @NonNull - public static DeckSelectionDialog newInstance(@NonNull String title, @NonNull String summaryMessage, @NonNull List decks) { + public static DeckSelectionDialog newInstance(@NonNull String title, @Nullable String summaryMessage, @NonNull boolean keepRestoreDefaultButton, @NonNull List decks) { DeckSelectionDialog f = new DeckSelectionDialog(); Bundle args = new Bundle(); args.putString("summaryMessage", summaryMessage); args.putString("title", title); + args.putBoolean("keepRestoreDefaultButton", keepRestoreDefaultButton); args.putParcelableArrayList("deckNames", new ArrayList<>(decks)); f.setArguments(args); return f; @@ -88,7 +89,12 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { Bundle arguments = requireArguments(); - summary.setText(getSummaryMessage(arguments)); + if (getSummaryMessage(arguments) == null) { + summary.setVisibility(View.GONE); + } else { + summary.setVisibility(View.VISIBLE); + summary.setText(getSummaryMessage(arguments)); + } RecyclerView recyclerView = dialogView.findViewById(R.id.deck_picker_dialog_list); recyclerView.requestFocus(); @@ -108,19 +114,20 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { MaterialDialog.Builder builder = new MaterialDialog.Builder(requireActivity()) .neutralText(R.string.dialog_cancel) - .negativeText(R.string.restore_default) - .customView(dialogView, false) - .onNegative((dialog, which) -> onDeckSelected(null)) - .onNeutral((dialog, which) -> { }); + .customView(dialogView, false); + + if (arguments.getBoolean("keepRestoreDefaultButton")) { + builder = builder.negativeText(R.string.restore_default).onNegative((dialog, which) -> onDeckSelected(null)); + } mDialog = builder.build(); return mDialog; } - @NonNull + @Nullable private String getSummaryMessage(Bundle arguments) { - return Objects.requireNonNull(arguments.getString("summaryMessage")); + return arguments.getString("summaryMessage"); } diff --git a/AnkiDroid/src/main/res/values/01-core.xml b/AnkiDroid/src/main/res/values/01-core.xml index 60a1c92b3dd5..b6e8b54c210c 100644 --- a/AnkiDroid/src/main/res/values/01-core.xml +++ b/AnkiDroid/src/main/res/values/01-core.xml @@ -138,6 +138,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. From 0376a0a39f063b329407dec1135b8522be4f9b3f Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 1 Apr 2021 06:52:24 +0100 Subject: [PATCH 022/171] Make launcher icon red in debug mode I was struggling to tell the icons apart in my quick launch as the app name was truncated --- AnkiDroid/build.gradle | 5 +++++ .../src/main/res/drawable-v24/ic_launcher_foreground.xml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 42b9bf6079b8..4c74aab63904 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -84,6 +84,9 @@ android { testCoverageEnabled true } + // make the icon red if in debug mode + resValue 'color', 'anki_foreground_icon_color_0', "#FFFF0000" + resValue 'color', 'anki_foreground_icon_color_1', "#FFFF0000" } release { minifyEnabled true @@ -91,6 +94,8 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release buildConfigField "String", "ACRA_URL", '"https://ankidroid.org/acra/report"' + resValue 'color', 'anki_foreground_icon_color_0', "#FF29B6F6" + resValue 'color', 'anki_foreground_icon_color_1', "#FF0288D1" } } diff --git a/AnkiDroid/src/main/res/drawable-v24/ic_launcher_foreground.xml b/AnkiDroid/src/main/res/drawable-v24/ic_launcher_foreground.xml index 2f527609283b..a1c98182a898 100644 --- a/AnkiDroid/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/AnkiDroid/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -18,8 +18,8 @@ android:endY="140.15863" android:endX="164.13487" android:type="linear"> - - + + From 750ba9b187f4c7a8c3b46af8d03bf820def9c29e Mon Sep 17 00:00:00 2001 From: Aditya-Srivastav <54016427+4D17Y4@users.noreply.github.com> Date: Fri, 2 Apr 2021 03:57:58 +0530 Subject: [PATCH 023/171] Don't show "increase new card limit" in CustomStudy dialog when no new cards' (#8338) * Removed increase new card limit when no new cards * Added Regression Test * used string from resources and added en qualifier in regression test * added annotations Co-authored-by: Mike Hardy --- .../ichi2/anki/dialogs/CustomStudyDialog.java | 15 ++++++++++-- .../anki/dialogs/CustomStudyDialogTest.java | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java index 22c28f9401d8..f9473553122e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java @@ -350,8 +350,19 @@ private int[] getListIds(int dialogId) { switch (dialogId) { case CONTEXT_MENU_STANDARD: // Standard context menu - return new int[] {CUSTOM_STUDY_NEW, CUSTOM_STUDY_REV, CUSTOM_STUDY_FORGOT, CUSTOM_STUDY_AHEAD, - CUSTOM_STUDY_RANDOM, CUSTOM_STUDY_PREVIEW, CUSTOM_STUDY_TAGS}; + ArrayList dialogOptions = new ArrayList(); + dialogOptions.add(CUSTOM_STUDY_NEW); + dialogOptions.add(CUSTOM_STUDY_REV); + dialogOptions.add(CUSTOM_STUDY_FORGOT); + dialogOptions.add(CUSTOM_STUDY_AHEAD); + dialogOptions.add(CUSTOM_STUDY_RANDOM); + dialogOptions.add(CUSTOM_STUDY_PREVIEW); + dialogOptions.add(CUSTOM_STUDY_TAGS); + if (col.getSched().newCount() == 0) { + // If no new cards we wont show CUSTOM_STUDY_NEW + dialogOptions.remove(Integer.valueOf(CUSTOM_STUDY_NEW)); + } + return ContextMenuHelper.integerListToArray(dialogOptions); case CONTEXT_MENU_LIMITS: // Special custom study options to show when the daily study limit has been reached if (col.getSched().newDue() && col.getSched().revDue()) { diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java index a2a3d931c413..624c02b6d435 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java @@ -22,13 +22,17 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anki.AnkiActivity; import com.ichi2.anki.CardTemplateBrowserAppearanceEditor; +import com.ichi2.anki.R; import com.ichi2.anki.RobolectricTest; import com.ichi2.anki.dialogs.CustomStudyDialog.CustomStudyListener; import com.ichi2.libanki.Deck; import com.ichi2.utils.JSONObject; +import org.hamcrest.Matchers; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; import androidx.annotation.NonNull; import androidx.fragment.app.testing.FragmentScenario; @@ -71,6 +75,25 @@ public void learnAheadCardsRegressionTest() { } + @Test + @Ignore("#8406") + @Config(qualifiers = "en") + public void increaseNewCardLimitRegressionTest(){ + // #8338 - Regression Test + CustomStudyDialog standard = CustomStudyDialog.newInstance(CustomStudyDialog.CONTEXT_MENU_STANDARD, 1); + + FragmentScenario scenarioStandard = getDialogScenario(standard); + + scenarioStandard.moveToState(Lifecycle.State.STARTED); + + scenarioStandard.onFragment(f -> { + MaterialDialog dialog = (MaterialDialog)f.getDialog(); + assertThat(dialog,notNullValue()); + assertThat(dialog.getItems(), Matchers.not(Matchers.hasItem(getResourceString(R.string.custom_study_increase_new_limit)))); + }); + } + + @NonNull private FragmentScenario getDialogScenario(CustomStudyDialog args) { FragmentScenario scenario = FragmentScenario.launch(CustomStudyDialogForTesting.class, args.getArguments()); From 1a7102af2f726327f2bb99ea387bba79ef6965d1 Mon Sep 17 00:00:00 2001 From: Ben Hamrick <70414817+bhamrickatmills@users.noreply.github.com> Date: Thu, 1 Apr 2021 15:37:57 -0700 Subject: [PATCH 024/171] Fix Login page Anki Icon Flicker (#8419) * Fix condition to display anki logo Co-authored-by: iserrano --- .../main/java/com/ichi2/anki/MyAccount.java | 56 +++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java b/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java index 110066e19ce7..4283fd4a912c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java @@ -19,14 +19,14 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.res.Configuration; -import android.hardware.SensorManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; + +import androidx.annotation.NonNull; import androidx.appcompat.widget.Toolbar; import android.view.KeyEvent; -import android.view.OrientationEventListener; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; @@ -65,7 +65,6 @@ public class MyAccount extends AnkiActivity { Toolbar mToolbar = null; private TextInputLayout mPasswordLayout; - private OrientationEventListener myOrientationEventListener; private ImageView mAnkidroidLogo; private void switchToState(int newState) { @@ -117,8 +116,11 @@ protected void onCreate(Bundle savedInstanceState) { switchToState(STATE_LOG_IN); } - // listener for handling future change in orientation - initializeOrientationListener(); + if (isScreenSmall() && this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { + mAnkidroidLogo.setVisibility(View.GONE); + } else { + mAnkidroidLogo.setVisibility(View.VISIBLE); + } } @@ -319,49 +321,22 @@ protected String getHumanReadableLoginErrorMessage(Exception exception) { return exception.getLocalizedMessage(); } - public void handleCurrentOrientationState(int orientation) { - if ((orientation == Configuration.ORIENTATION_UNDEFINED) || orientation == Configuration.ORIENTATION_LANDSCAPE) { - mAnkidroidLogo.setVisibility(View.GONE); - } else { - mAnkidroidLogo.setVisibility(View.VISIBLE); - } + private boolean isScreenSmall() { + return (this.getApplicationContext().getResources().getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK) + < Configuration.SCREENLAYOUT_SIZE_LARGE; } - public void checkOrientation(int orientation) { - // if device is in horizontal mode then screen might not have enough space for ankidroid logo - // so we will invisible logo for horizontal mode only - if ((orientation == OrientationEventListener.ORIENTATION_UNKNOWN) || (orientation < 100) || (orientation > 280)) { + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (isScreenSmall() && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { mAnkidroidLogo.setVisibility(View.GONE); } else { mAnkidroidLogo.setVisibility(View.VISIBLE); } } - public void initializeOrientationListener() { - myOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) { - @Override - public void onOrientationChanged(int orientation) { - checkOrientation(orientation); - } - }; - } - - @Override - protected void onResume() { - super.onResume(); - // handle current state - handleCurrentOrientationState(getResources().getConfiguration().orientation); - myOrientationEventListener.enable(); - } - - - @Override - protected void onPause() { - super.onPause(); - myOrientationEventListener.disable(); - } - - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { @@ -371,5 +346,4 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { } return super.onKeyDown(keyCode, event); } - } From 3db30e871f040d0a1f146b4e39e56b2d78fb046d Mon Sep 17 00:00:00 2001 From: AnkiDroid Translations Date: Thu, 1 Apr 2021 23:18:23 +0000 Subject: [PATCH 025/171] Updated strings from Crowdin --- AnkiDroid/src/main/res/values-af/01-core.xml | 1 + AnkiDroid/src/main/res/values-am/01-core.xml | 1 + AnkiDroid/src/main/res/values-ar/01-core.xml | 1 + AnkiDroid/src/main/res/values-az/01-core.xml | 1 + AnkiDroid/src/main/res/values-be/01-core.xml | 1 + AnkiDroid/src/main/res/values-bg/01-core.xml | 1 + AnkiDroid/src/main/res/values-bn/01-core.xml | 1 + AnkiDroid/src/main/res/values-ca/01-core.xml | 1 + AnkiDroid/src/main/res/values-ckb/01-core.xml | 1 + AnkiDroid/src/main/res/values-cs/01-core.xml | 1 + AnkiDroid/src/main/res/values-da/01-core.xml | 1 + AnkiDroid/src/main/res/values-de/01-core.xml | 1 + AnkiDroid/src/main/res/values-el/01-core.xml | 1 + AnkiDroid/src/main/res/values-eo/01-core.xml | 1 + .../src/main/res/values-es-rAR/01-core.xml | 1 + .../src/main/res/values-es-rES/01-core.xml | 1 + AnkiDroid/src/main/res/values-et/01-core.xml | 1 + AnkiDroid/src/main/res/values-eu/01-core.xml | 1 + AnkiDroid/src/main/res/values-fa/01-core.xml | 1 + AnkiDroid/src/main/res/values-fi/01-core.xml | 1 + AnkiDroid/src/main/res/values-fil/01-core.xml | 1 + AnkiDroid/src/main/res/values-fr/01-core.xml | 1 + AnkiDroid/src/main/res/values-fy/01-core.xml | 1 + AnkiDroid/src/main/res/values-ga/01-core.xml | 1 + AnkiDroid/src/main/res/values-gl/01-core.xml | 1 + AnkiDroid/src/main/res/values-got/01-core.xml | 1 + AnkiDroid/src/main/res/values-gu/01-core.xml | 1 + AnkiDroid/src/main/res/values-heb/01-core.xml | 1 + AnkiDroid/src/main/res/values-hi/01-core.xml | 1 + AnkiDroid/src/main/res/values-hr/01-core.xml | 1 + AnkiDroid/src/main/res/values-hu/01-core.xml | 1 + AnkiDroid/src/main/res/values-hy/01-core.xml | 1 + AnkiDroid/src/main/res/values-ind/01-core.xml | 1 + AnkiDroid/src/main/res/values-is/01-core.xml | 1 + AnkiDroid/src/main/res/values-it/01-core.xml | 1 + AnkiDroid/src/main/res/values-ja/01-core.xml | 1 + AnkiDroid/src/main/res/values-jv/01-core.xml | 1 + AnkiDroid/src/main/res/values-ka/01-core.xml | 1 + AnkiDroid/src/main/res/values-kk/01-core.xml | 1 + AnkiDroid/src/main/res/values-km/01-core.xml | 1 + AnkiDroid/src/main/res/values-ko/01-core.xml | 1 + AnkiDroid/src/main/res/values-ku/01-core.xml | 1 + AnkiDroid/src/main/res/values-ky/01-core.xml | 1 + AnkiDroid/src/main/res/values-lt/01-core.xml | 1 + AnkiDroid/src/main/res/values-lv/01-core.xml | 1 + AnkiDroid/src/main/res/values-mk/01-core.xml | 1 + AnkiDroid/src/main/res/values-mn/01-core.xml | 1 + AnkiDroid/src/main/res/values-mr/01-core.xml | 1 + AnkiDroid/src/main/res/values-ms/01-core.xml | 1 + AnkiDroid/src/main/res/values-my/01-core.xml | 1 + AnkiDroid/src/main/res/values-nl/01-core.xml | 1 + AnkiDroid/src/main/res/values-nn/01-core.xml | 1 + AnkiDroid/src/main/res/values-no/01-core.xml | 1 + AnkiDroid/src/main/res/values-or/01-core.xml | 1 + AnkiDroid/src/main/res/values-pa/01-core.xml | 1 + AnkiDroid/src/main/res/values-pl/01-core.xml | 1 + .../src/main/res/values-pt-rBR/01-core.xml | 1 + .../src/main/res/values-pt-rPT/01-core.xml | 1 + AnkiDroid/src/main/res/values-ro/01-core.xml | 1 + AnkiDroid/src/main/res/values-ru/01-core.xml | 1 + AnkiDroid/src/main/res/values-sat/01-core.xml | 1 + AnkiDroid/src/main/res/values-sk/01-core.xml | 1 + AnkiDroid/src/main/res/values-sl/01-core.xml | 1 + AnkiDroid/src/main/res/values-sq/01-core.xml | 1 + AnkiDroid/src/main/res/values-sr/01-core.xml | 1 + AnkiDroid/src/main/res/values-ss/01-core.xml | 1 + AnkiDroid/src/main/res/values-sv/01-core.xml | 1 + AnkiDroid/src/main/res/values-sw/01-core.xml | 1 + AnkiDroid/src/main/res/values-ta/01-core.xml | 1 + AnkiDroid/src/main/res/values-te/01-core.xml | 1 + AnkiDroid/src/main/res/values-tg/01-core.xml | 1 + AnkiDroid/src/main/res/values-tgl/01-core.xml | 1 + AnkiDroid/src/main/res/values-th/01-core.xml | 1 + AnkiDroid/src/main/res/values-ti/01-core.xml | 1 + AnkiDroid/src/main/res/values-tn/01-core.xml | 1 + AnkiDroid/src/main/res/values-tr/01-core.xml | 1 + AnkiDroid/src/main/res/values-ts/01-core.xml | 1 + AnkiDroid/src/main/res/values-tt/01-core.xml | 9 ++-- .../src/main/res/values-tt/02-strings.xml | 4 +- .../src/main/res/values-tt/03-dialogs.xml | 12 ++--- .../src/main/res/values-tt/04-network.xml | 8 ++-- .../src/main/res/values-tt/06-statistics.xml | 44 +++++++++---------- .../src/main/res/values-tt/07-cardbrowser.xml | 6 +-- .../src/main/res/values-tt/09-backup.xml | 2 +- .../src/main/res/values-tt/10-preferences.xml | 26 +++++------ .../src/main/res/values-tt/11-arrays.xml | 8 ++-- .../res/values-tt/16-multimedia-editor.xml | 18 ++++---- .../main/res/values-tt/17-model-manager.xml | 2 +- .../main/res/values-tt/18-standard-models.xml | 4 +- AnkiDroid/src/main/res/values-uk/01-core.xml | 3 +- AnkiDroid/src/main/res/values-ur/01-core.xml | 1 + AnkiDroid/src/main/res/values-uz/01-core.xml | 1 + AnkiDroid/src/main/res/values-ve/01-core.xml | 1 + AnkiDroid/src/main/res/values-vi/01-core.xml | 1 + AnkiDroid/src/main/res/values-wo/01-core.xml | 1 + AnkiDroid/src/main/res/values-xh/01-core.xml | 1 + AnkiDroid/src/main/res/values-yue/01-core.xml | 1 + .../src/main/res/values-zh-rCN/01-core.xml | 1 + .../src/main/res/values-zh-rTW/01-core.xml | 1 + AnkiDroid/src/main/res/values-zu/01-core.xml | 1 + .../marketdescription-tt-RU.txt | 2 +- 101 files changed, 162 insertions(+), 73 deletions(-) diff --git a/AnkiDroid/src/main/res/values-af/01-core.xml b/AnkiDroid/src/main/res/values-af/01-core.xml index db21f755b93b..4e4db5d0b79d 100644 --- a/AnkiDroid/src/main/res/values-af/01-core.xml +++ b/AnkiDroid/src/main/res/values-af/01-core.xml @@ -154,6 +154,7 @@ Pasmaak studie sessie Hernoem die bestaande pasmaak kaartpak eers Hierdie pak is leeg + Deck Search Add card Invalid deck name Hierdie pak is leeg. Druk die + knoppie om nuwe inhoud by te voeg. diff --git a/AnkiDroid/src/main/res/values-am/01-core.xml b/AnkiDroid/src/main/res/values-am/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-am/01-core.xml +++ b/AnkiDroid/src/main/res/values-am/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ar/01-core.xml b/AnkiDroid/src/main/res/values-ar/01-core.xml index 3c7eab28beec..1ce950068b85 100644 --- a/AnkiDroid/src/main/res/values-ar/01-core.xml +++ b/AnkiDroid/src/main/res/values-ar/01-core.xml @@ -174,6 +174,7 @@ جلسة دراسة مخصصة أعد تسمية رزمة الدراسة المخصصة الموجودة أولًا هذه الرزمة فارغة + Deck Search إضافة بِطاقة اسم رزمة غير صالح هذه الرزمة فارغة. اضغط زر + لإضافة محتوى جديد. diff --git a/AnkiDroid/src/main/res/values-az/01-core.xml b/AnkiDroid/src/main/res/values-az/01-core.xml index 027904fc655c..9238fe47e76e 100644 --- a/AnkiDroid/src/main/res/values-az/01-core.xml +++ b/AnkiDroid/src/main/res/values-az/01-core.xml @@ -154,6 +154,7 @@ Şəxsi öyrənmə sessiyası Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-be/01-core.xml b/AnkiDroid/src/main/res/values-be/01-core.xml index 9ca0486243bc..ba2041d840f7 100644 --- a/AnkiDroid/src/main/res/values-be/01-core.xml +++ b/AnkiDroid/src/main/res/values-be/01-core.xml @@ -164,6 +164,7 @@ Спецыяльная навучальная сесія Спачатку дайце новую назву бягучай спецыяльнай калодзе Гэта калода пустая + Deck Search Add card Invalid deck name Гэта калода пустая. Націсніце кнопку +, каб дадаць новае змесціва. diff --git a/AnkiDroid/src/main/res/values-bg/01-core.xml b/AnkiDroid/src/main/res/values-bg/01-core.xml index 2cf5235ca2e0..dff26959107f 100644 --- a/AnkiDroid/src/main/res/values-bg/01-core.xml +++ b/AnkiDroid/src/main/res/values-bg/01-core.xml @@ -154,6 +154,7 @@ Допълнително време за учене Първо преименувайте съществуващото тесте за учене Това тесте е празно + Deck Search Добавяне на карта Невалидно име на тесте Това тесте е празно. Натиснете бутона + за добавяне на ново съдържание. diff --git a/AnkiDroid/src/main/res/values-bn/01-core.xml b/AnkiDroid/src/main/res/values-bn/01-core.xml index 4dd93f276138..77be0c911484 100644 --- a/AnkiDroid/src/main/res/values-bn/01-core.xml +++ b/AnkiDroid/src/main/res/values-bn/01-core.xml @@ -154,6 +154,7 @@ নিজস্ব গবেষণা অধিবেশন বর্তমান স্বনির্ধারিত গবেষণা ডেক প্রথম পুনঃনামকরণ করুন এই ডেক ফাঁকা। + Deck Search Add card Invalid deck name এই ডেক খালি আছে। প্রেস + বাটন নতুন উপাদান যোগ করতে। diff --git a/AnkiDroid/src/main/res/values-ca/01-core.xml b/AnkiDroid/src/main/res/values-ca/01-core.xml index 85e25123a5c1..382b5e7200df 100644 --- a/AnkiDroid/src/main/res/values-ca/01-core.xml +++ b/AnkiDroid/src/main/res/values-ca/01-core.xml @@ -154,6 +154,7 @@ Sessió d\'estudi personalitzat Si us plau, primer canvieu de nom el paquet d\'estudi a mida Aquest paquet és buit + Deck Search Add card Invalid deck name Aquest paquet és buit. Premeu el botó + per afegir nou contingut. diff --git a/AnkiDroid/src/main/res/values-ckb/01-core.xml b/AnkiDroid/src/main/res/values-ckb/01-core.xml index 9bb4bd28429e..98cb018e2c19 100644 --- a/AnkiDroid/src/main/res/values-ckb/01-core.xml +++ b/AnkiDroid/src/main/res/values-ckb/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-cs/01-core.xml b/AnkiDroid/src/main/res/values-cs/01-core.xml index 6b731d716f70..569b5aaafbd6 100644 --- a/AnkiDroid/src/main/res/values-cs/01-core.xml +++ b/AnkiDroid/src/main/res/values-cs/01-core.xml @@ -164,6 +164,7 @@ Dodatečné procvičování Nejdříve přejmenujte stávající balíček vlastního učení Tento balíček je prázdný + Deck Search Přidat kartu Neplatný název balíčku Tento balíček je prázdný. Stiskněte tlačítko +, abyste přidali nový obsah. diff --git a/AnkiDroid/src/main/res/values-da/01-core.xml b/AnkiDroid/src/main/res/values-da/01-core.xml index 686ef88bdfbb..61221196fd34 100644 --- a/AnkiDroid/src/main/res/values-da/01-core.xml +++ b/AnkiDroid/src/main/res/values-da/01-core.xml @@ -154,6 +154,7 @@ Tilpasset studiesession Omdøb det eksisterende tilpassede dæk først Dette kortsæt er tomt + Deck Search Tilføj kort Ugyldigt dæknavn Dette dæk er tomt. Tryk på + knappen for at tilføje nyt indhold. diff --git a/AnkiDroid/src/main/res/values-de/01-core.xml b/AnkiDroid/src/main/res/values-de/01-core.xml index e0ffd5832e58..623161c68abe 100644 --- a/AnkiDroid/src/main/res/values-de/01-core.xml +++ b/AnkiDroid/src/main/res/values-de/01-core.xml @@ -154,6 +154,7 @@ Benutzerdefinierte Sitzung Bitte benennen Sie zunächst den existierenden Stapel »Benutzerdefinierte Sitzung« um Dieser Stapel ist leer + Deck Search Karte hinzufügen Ungültiger Stapelname Dieser Stapel ist leer. Berühren Sie die »+«-Schaltfläche, um neue Inhalte hinzuzufügen. diff --git a/AnkiDroid/src/main/res/values-el/01-core.xml b/AnkiDroid/src/main/res/values-el/01-core.xml index 0eef1c5e2e66..22a48ca60345 100644 --- a/AnkiDroid/src/main/res/values-el/01-core.xml +++ b/AnkiDroid/src/main/res/values-el/01-core.xml @@ -154,6 +154,7 @@ Προσαρμοσμένη μελέτη λειτουργίας Παρακαλούμε μετονομάστε την υπάρχουσα τράπουλα Προσαρμοσμένης Μελέτης πρώτα. Αυτή η τράπουλα είναι κενή + Deck Search Add card Invalid deck name Αυτή η τράπουλα είναι κενή. Πατήστε το κουμπί + για να προσθέσετε πριεχόμενο. diff --git a/AnkiDroid/src/main/res/values-eo/01-core.xml b/AnkiDroid/src/main/res/values-eo/01-core.xml index 0544ce4d57ba..1ed3bfd9f998 100644 --- a/AnkiDroid/src/main/res/values-eo/01-core.xml +++ b/AnkiDroid/src/main/res/values-eo/01-core.xml @@ -154,6 +154,7 @@ Propra lernado Unue ŝanĝu nomon de kartaro por propra lernado Tiu ĉi kartaro estas malplena + Deck Search Aldoni karton Malĝusta nomo por kartaro Tiu ĉi kartaro estas malplena. Frapetu la butonon “+” por aldoni novan enhavon. diff --git a/AnkiDroid/src/main/res/values-es-rAR/01-core.xml b/AnkiDroid/src/main/res/values-es-rAR/01-core.xml index b6659d3e6071..59ef9e4acfa6 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/01-core.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/01-core.xml @@ -154,6 +154,7 @@ Sesión de estudio personalizado Renombre primero el mazo de estudio personalizado existente Este mazo está vacío + Deck Search Añadir tarjeta Nombre de mazo no válido Este mazo está vacío. Pulse el botón + para añadir contenido nuevo. diff --git a/AnkiDroid/src/main/res/values-es-rES/01-core.xml b/AnkiDroid/src/main/res/values-es-rES/01-core.xml index 323c1c8785e5..d3ba3ff3e09c 100644 --- a/AnkiDroid/src/main/res/values-es-rES/01-core.xml +++ b/AnkiDroid/src/main/res/values-es-rES/01-core.xml @@ -154,6 +154,7 @@ Sesión de estudio personalizado Renombra el mazo de estudio personalizado primero Este mazo está vacío + Deck Search Añadir tarjeta Nombre de mazo no válido Este mazo está vacío. Pulsa el botón + para añadir contenido nuevo. diff --git a/AnkiDroid/src/main/res/values-et/01-core.xml b/AnkiDroid/src/main/res/values-et/01-core.xml index 957aaebf8eb2..6a5e0a84bba1 100644 --- a/AnkiDroid/src/main/res/values-et/01-core.xml +++ b/AnkiDroid/src/main/res/values-et/01-core.xml @@ -154,6 +154,7 @@ Kohandatud õppesessioon Esmalt nimeta ümber juba olemasolev täiendava õppe kaardipakk See kaardipakk on tühi + Deck Search Add card Vigane kaardipaki nimi See kaardipakk on tühi. Vajuta + nupule uue sisu lisamiseks. diff --git a/AnkiDroid/src/main/res/values-eu/01-core.xml b/AnkiDroid/src/main/res/values-eu/01-core.xml index 0c354cb8c5a7..e64b134ee056 100644 --- a/AnkiDroid/src/main/res/values-eu/01-core.xml +++ b/AnkiDroid/src/main/res/values-eu/01-core.xml @@ -154,6 +154,7 @@ Ikasketa saio pertsonalizatua Aldatu izena dagoen ikasketa sortari aurretik Sorta hau hutsik dago + Deck Search Add card Invalid deck name Sorta hau hutsik dago. Sakatu + botoia edukia gehitzeko. diff --git a/AnkiDroid/src/main/res/values-fa/01-core.xml b/AnkiDroid/src/main/res/values-fa/01-core.xml index d8f88670bb68..43caa2fcc521 100644 --- a/AnkiDroid/src/main/res/values-fa/01-core.xml +++ b/AnkiDroid/src/main/res/values-fa/01-core.xml @@ -154,6 +154,7 @@ جلسه مطالعه سفارشی لطفا ابتدا دسته مطالعه سفارشی موجود را مجددا نام گذاری کنید. این دسته خالی است + Deck Search اضافه کردن کارت نام دسته نامعتبر است این دسته خالی است. دکمه + را فشار دهید تا مطلب جدید اضافه کنید. diff --git a/AnkiDroid/src/main/res/values-fi/01-core.xml b/AnkiDroid/src/main/res/values-fi/01-core.xml index 88564bf2670f..4212218ad486 100644 --- a/AnkiDroid/src/main/res/values-fi/01-core.xml +++ b/AnkiDroid/src/main/res/values-fi/01-core.xml @@ -154,6 +154,7 @@ Mukautettu istunto Nimeä olemassa oleva mukautettu pakka ensin Tämä pakka on tyhjä + Deck Search Add card Pätemätön pakka nimi Tämä pakka on tyhjä. Paina + painiketta lisätäksesi uutta sisältöä. diff --git a/AnkiDroid/src/main/res/values-fil/01-core.xml b/AnkiDroid/src/main/res/values-fil/01-core.xml index 869c73e81b2e..d81900e683f8 100644 --- a/AnkiDroid/src/main/res/values-fil/01-core.xml +++ b/AnkiDroid/src/main/res/values-fil/01-core.xml @@ -155,6 +155,7 @@ mga natitirang minuto I-custom ang study session Baguhin muna ang pangalan ng existing custom study deck Ang deck na ito ay walang laman + Deck Search Add card Invalid deck name Ang deck na ito ay walang laman. Pindotin ang + na button para magdagdag ng laman. diff --git a/AnkiDroid/src/main/res/values-fr/01-core.xml b/AnkiDroid/src/main/res/values-fr/01-core.xml index c92416f5f6f7..219e13ace7c6 100644 --- a/AnkiDroid/src/main/res/values-fr/01-core.xml +++ b/AnkiDroid/src/main/res/values-fr/01-core.xml @@ -154,6 +154,7 @@ Session d\'étude personnalisée Merci de renommer d\'abord le paquet d\'étude personnalisée. Le paquet est vide + Deck Search Ajouter une carte Nom de paquet invalide Le paquet est vide. touchez le bouton + pour créer du contenu. diff --git a/AnkiDroid/src/main/res/values-fy/01-core.xml b/AnkiDroid/src/main/res/values-fy/01-core.xml index be0046f33053..ea11c6d70b85 100644 --- a/AnkiDroid/src/main/res/values-fy/01-core.xml +++ b/AnkiDroid/src/main/res/values-fy/01-core.xml @@ -154,6 +154,7 @@ Oanpaste stúdzjesessy Hernoem de besteande stúdzjeset earst This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ga/01-core.xml b/AnkiDroid/src/main/res/values-ga/01-core.xml index 42193ba0cade..181623d9f204 100644 --- a/AnkiDroid/src/main/res/values-ga/01-core.xml +++ b/AnkiDroid/src/main/res/values-ga/01-core.xml @@ -169,6 +169,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-gl/01-core.xml b/AnkiDroid/src/main/res/values-gl/01-core.xml index b9da7b109ba5..ffc49676db06 100644 --- a/AnkiDroid/src/main/res/values-gl/01-core.xml +++ b/AnkiDroid/src/main/res/values-gl/01-core.xml @@ -154,6 +154,7 @@ Sesión de estudo personalizado Por favor, renomee a baralla de estudo personalizado existente primeiro. Esta baralla está baleira + Deck Search Add card Invalid deck name Esta baralla está baleira. Preme no botón + para engadir contido novo. diff --git a/AnkiDroid/src/main/res/values-got/01-core.xml b/AnkiDroid/src/main/res/values-got/01-core.xml index 5830efaffe2a..76cfc1137a21 100644 --- a/AnkiDroid/src/main/res/values-got/01-core.xml +++ b/AnkiDroid/src/main/res/values-got/01-core.xml @@ -154,6 +154,7 @@ Anþara laiseinasaisjo Gif þata frumist niujata namo du wisandon laiseinawikon This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-gu/01-core.xml b/AnkiDroid/src/main/res/values-gu/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-gu/01-core.xml +++ b/AnkiDroid/src/main/res/values-gu/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-heb/01-core.xml b/AnkiDroid/src/main/res/values-heb/01-core.xml index 1c833f182ef6..f0d9eaf007d1 100644 --- a/AnkiDroid/src/main/res/values-heb/01-core.xml +++ b/AnkiDroid/src/main/res/values-heb/01-core.xml @@ -165,6 +165,7 @@ שיעור למידה מותאם ראשית, ערוך שם חפיסה חדשה החפיסה ריקה + Deck Search הוספת קלף שם חפיסה לא חוקי החפיסה ריקה. לחץ על כפתור ה+ להוספת תוכן חדש. diff --git a/AnkiDroid/src/main/res/values-hi/01-core.xml b/AnkiDroid/src/main/res/values-hi/01-core.xml index c53c1d54a092..ac2927a980fe 100644 --- a/AnkiDroid/src/main/res/values-hi/01-core.xml +++ b/AnkiDroid/src/main/res/values-hi/01-core.xml @@ -154,6 +154,7 @@ कस्टम अध्ययन सत्र कृपया पहले मौजूदा कस्टम अध्ययन डेक का नाम बदलें। इस डेक खाली है + Deck Search कार्ड जोड़ें अमान्य डेक नाम यह डेक खाली है । नई सामग्री जोड़ने के लिए + बटन दबाएँ. diff --git a/AnkiDroid/src/main/res/values-hr/01-core.xml b/AnkiDroid/src/main/res/values-hr/01-core.xml index fe843bf40441..877a60aba290 100644 --- a/AnkiDroid/src/main/res/values-hr/01-core.xml +++ b/AnkiDroid/src/main/res/values-hr/01-core.xml @@ -159,6 +159,7 @@ Prilagođena sesija učenja Prvo preimenujte postojeću prilagođenu skupinu za učenje Ova skupina je prazna + Deck Search Add card Invalid deck name Ova skupina je prazna. Pritisnite tipku + kako biste dodali novi sadržaj. diff --git a/AnkiDroid/src/main/res/values-hu/01-core.xml b/AnkiDroid/src/main/res/values-hu/01-core.xml index 5194a5ea034c..8681fa50e4fd 100644 --- a/AnkiDroid/src/main/res/values-hu/01-core.xml +++ b/AnkiDroid/src/main/res/values-hu/01-core.xml @@ -154,6 +154,7 @@ Egyéni tanulás pakli Kérlek, előbb nevezd át a meglévő, Egyéni tanulás paklit Ez a pakli üres + Deck Search Kártya hozzáadása Érvénytelen paklinév Ez a pakli üres. Adj hozzá új tartalmat a \"+\" megnyomásával. diff --git a/AnkiDroid/src/main/res/values-hy/01-core.xml b/AnkiDroid/src/main/res/values-hy/01-core.xml index 1ad61ceef9a7..8bf4496df530 100644 --- a/AnkiDroid/src/main/res/values-hy/01-core.xml +++ b/AnkiDroid/src/main/res/values-hy/01-core.xml @@ -154,6 +154,7 @@ Ուսուցման լրացուցիչ աշխատաշրջան Սկզբում վերանվանել լրացուցիչ ուսուցման առկա կապուկը Այս կապուկը դատարկ է + Deck Search Add card Invalid deck name Կապուկը դատարկ է: Սեղմեք + կոճակը նոր բովանդակություն ավելացնելու համար: diff --git a/AnkiDroid/src/main/res/values-ind/01-core.xml b/AnkiDroid/src/main/res/values-ind/01-core.xml index bbd631e58d5f..b0883f91784c 100644 --- a/AnkiDroid/src/main/res/values-ind/01-core.xml +++ b/AnkiDroid/src/main/res/values-ind/01-core.xml @@ -149,6 +149,7 @@ Sesi belajar kustom Ubah nama dek belajar kustom yang ada Dek ini kosong + Deck Search Add card Nama dek tidak valid Dek Ini kosong. Tekan tombol + untuk menambah konten baru. diff --git a/AnkiDroid/src/main/res/values-is/01-core.xml b/AnkiDroid/src/main/res/values-is/01-core.xml index 1ba9d3ad382e..0704f91409d3 100644 --- a/AnkiDroid/src/main/res/values-is/01-core.xml +++ b/AnkiDroid/src/main/res/values-is/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-it/01-core.xml b/AnkiDroid/src/main/res/values-it/01-core.xml index d0d9eb246d63..10254c78e0fc 100644 --- a/AnkiDroid/src/main/res/values-it/01-core.xml +++ b/AnkiDroid/src/main/res/values-it/01-core.xml @@ -154,6 +154,7 @@ Sessione di Studio Personalizzato Rinomina dapprima il mazzo di Studio Personalizzato esistente. Questo mazzo è vuoto + Deck Search Aggiungi carta Nome mazzo non valido Questo mazzo è vuoto. Premi il pulsante + per aggiungere nuovi contenuti. diff --git a/AnkiDroid/src/main/res/values-ja/01-core.xml b/AnkiDroid/src/main/res/values-ja/01-core.xml index 67db16809b84..35888e44ea6a 100644 --- a/AnkiDroid/src/main/res/values-ja/01-core.xml +++ b/AnkiDroid/src/main/res/values-ja/01-core.xml @@ -149,6 +149,7 @@ カスタム学習セッション 最初に、既存のカスタム学習デッキの名前を変更してください このデッキにはカードがありません + Deck Search カードの追加 無効なデッキ名です。 このデッキにはカードが入っていません。「+」ボタンをタップしてカードを追加してください。 diff --git a/AnkiDroid/src/main/res/values-jv/01-core.xml b/AnkiDroid/src/main/res/values-jv/01-core.xml index 86c4c87b9863..4088b283a29d 100644 --- a/AnkiDroid/src/main/res/values-jv/01-core.xml +++ b/AnkiDroid/src/main/res/values-jv/01-core.xml @@ -149,6 +149,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ka/01-core.xml b/AnkiDroid/src/main/res/values-ka/01-core.xml index 618c57474fb0..d36b2f40cbf2 100644 --- a/AnkiDroid/src/main/res/values-ka/01-core.xml +++ b/AnkiDroid/src/main/res/values-ka/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-kk/01-core.xml b/AnkiDroid/src/main/res/values-kk/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-kk/01-core.xml +++ b/AnkiDroid/src/main/res/values-kk/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-km/01-core.xml b/AnkiDroid/src/main/res/values-km/01-core.xml index 16e46693fe77..c7dc3a70fbb5 100644 --- a/AnkiDroid/src/main/res/values-km/01-core.xml +++ b/AnkiDroid/src/main/res/values-km/01-core.xml @@ -149,6 +149,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ko/01-core.xml b/AnkiDroid/src/main/res/values-ko/01-core.xml index ef4abdc56624..3ca05d53f416 100644 --- a/AnkiDroid/src/main/res/values-ko/01-core.xml +++ b/AnkiDroid/src/main/res/values-ko/01-core.xml @@ -149,6 +149,7 @@ 맞춤 학습 세션 기존의 맞춤 학습 덱의 이름을 먼저 변경하세요. 이 카드 묶음은 비어있습니다. + Deck Search 카드追加 無效한 덱名입니다 이 카드 묶음은 비어있습니다. + 버튼을 눌러 새 컨텐츠를 추가하세요. diff --git a/AnkiDroid/src/main/res/values-ku/01-core.xml b/AnkiDroid/src/main/res/values-ku/01-core.xml index 9bb4bd28429e..98cb018e2c19 100644 --- a/AnkiDroid/src/main/res/values-ku/01-core.xml +++ b/AnkiDroid/src/main/res/values-ku/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ky/01-core.xml b/AnkiDroid/src/main/res/values-ky/01-core.xml index 15fec481d17d..5a933e8ed8d9 100644 --- a/AnkiDroid/src/main/res/values-ky/01-core.xml +++ b/AnkiDroid/src/main/res/values-ky/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-lt/01-core.xml b/AnkiDroid/src/main/res/values-lt/01-core.xml index 7f627a952c2b..0afd7dce5d2d 100644 --- a/AnkiDroid/src/main/res/values-lt/01-core.xml +++ b/AnkiDroid/src/main/res/values-lt/01-core.xml @@ -164,6 +164,7 @@ Individualus mokymosi seansas Prašome pirmiausiai pervadinti esamą individualaus mokymosi rinkinį Šis rinkinys yra tuščias + Deck Search Add card Blogas rinkinio vardas Šis rinkinys yra tuščias. Norėdami pridėti naują turinį, spauskite +. diff --git a/AnkiDroid/src/main/res/values-lv/01-core.xml b/AnkiDroid/src/main/res/values-lv/01-core.xml index 0a4bf2b89c48..f24b2c1cfbb0 100644 --- a/AnkiDroid/src/main/res/values-lv/01-core.xml +++ b/AnkiDroid/src/main/res/values-lv/01-core.xml @@ -159,6 +159,7 @@ Pielāgota pētījuma sesija Lūdzu vispirms pārsauciet eksistējošo Pielāgoto pētījuma Deku. This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-mk/01-core.xml b/AnkiDroid/src/main/res/values-mk/01-core.xml index 5fddabf8a4c2..96cd38f5a24e 100644 --- a/AnkiDroid/src/main/res/values-mk/01-core.xml +++ b/AnkiDroid/src/main/res/values-mk/01-core.xml @@ -154,6 +154,7 @@ Прилагодена студиска сесија Прво преименувајте ја постоечката прилагодена студија на deck Овој deck е празен + Deck Search Add card Невалидно име за шпил Овој deck е празен. Притиснете го копчето + за да додадете нова содржина. diff --git a/AnkiDroid/src/main/res/values-mn/01-core.xml b/AnkiDroid/src/main/res/values-mn/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-mn/01-core.xml +++ b/AnkiDroid/src/main/res/values-mn/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-mr/01-core.xml b/AnkiDroid/src/main/res/values-mr/01-core.xml index 1f373ae1e5d1..173b2e546e80 100644 --- a/AnkiDroid/src/main/res/values-mr/01-core.xml +++ b/AnkiDroid/src/main/res/values-mr/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ms/01-core.xml b/AnkiDroid/src/main/res/values-ms/01-core.xml index 4a91136d16e1..27fe6c8920fb 100644 --- a/AnkiDroid/src/main/res/values-ms/01-core.xml +++ b/AnkiDroid/src/main/res/values-ms/01-core.xml @@ -149,6 +149,7 @@ Adat belajar sesi Menamakan ada adat belajar dek pertama Dek ini adalah kosong + Deck Search Add card Invalid deck name Dek ini adalah kosong. Tekan butang + untuk menambah kandungan baru. diff --git a/AnkiDroid/src/main/res/values-my/01-core.xml b/AnkiDroid/src/main/res/values-my/01-core.xml index 890622a3b6df..3e6aee4acb26 100644 --- a/AnkiDroid/src/main/res/values-my/01-core.xml +++ b/AnkiDroid/src/main/res/values-my/01-core.xml @@ -149,6 +149,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-nl/01-core.xml b/AnkiDroid/src/main/res/values-nl/01-core.xml index c40893e4c324..fdf67c4219bf 100644 --- a/AnkiDroid/src/main/res/values-nl/01-core.xml +++ b/AnkiDroid/src/main/res/values-nl/01-core.xml @@ -154,6 +154,7 @@ Aangepaste studiesessie Gelieve eerst de bestaande aangepaste studieset te hernoemen. Deze leerset is leeg + Deck Search Kaart toevoegen Ongeldige setnaam Deze leerset is leeg. Druk op de + knop om nieuwe inhoud toe te voegen. diff --git a/AnkiDroid/src/main/res/values-nn/01-core.xml b/AnkiDroid/src/main/res/values-nn/01-core.xml index 451332052d1b..e121a5bf23b4 100644 --- a/AnkiDroid/src/main/res/values-nn/01-core.xml +++ b/AnkiDroid/src/main/res/values-nn/01-core.xml @@ -154,6 +154,7 @@ Tilpasset studiesesjon Gi den eksisterende tilpassede studiestokken nytt navn først Denne kortstokken er tom + Deck Search Add card Invalid deck name Denne kortstokken er tom. Bruk +-knappen for å legge til nytt innhold. diff --git a/AnkiDroid/src/main/res/values-no/01-core.xml b/AnkiDroid/src/main/res/values-no/01-core.xml index 451332052d1b..e121a5bf23b4 100644 --- a/AnkiDroid/src/main/res/values-no/01-core.xml +++ b/AnkiDroid/src/main/res/values-no/01-core.xml @@ -154,6 +154,7 @@ Tilpasset studiesesjon Gi den eksisterende tilpassede studiestokken nytt navn først Denne kortstokken er tom + Deck Search Add card Invalid deck name Denne kortstokken er tom. Bruk +-knappen for å legge til nytt innhold. diff --git a/AnkiDroid/src/main/res/values-or/01-core.xml b/AnkiDroid/src/main/res/values-or/01-core.xml index f6d6361a588b..9831d7ecd0d1 100644 --- a/AnkiDroid/src/main/res/values-or/01-core.xml +++ b/AnkiDroid/src/main/res/values-or/01-core.xml @@ -154,6 +154,7 @@ ଇଚ୍ଛାରୂପିତ ଅଧ୍ୟୟନ ଅଧିବେଶନ ଆଗ ସାମ୍ପ୍ରତିକ ଇଚ୍ଛାରୂପିତ ଅଧ୍ୟୟନାଧିବେଶନକୁ ପୁନଃନାମିତ କରନ୍ତୁ ତାସଖଣ୍ଡଟି ଖାଲି ଅଛି + Deck Search କାର୍ଡ ଯୋଡ଼ନ୍ତୁ ଅବୈଧ ତାସଖଣ୍ଡ ନାମ ଏହି ଡେକ୍ ଖାଲି ଅଛି। ନୂତନ ବିଷୟବସ୍ତୁ ଯୋଗ କରିବା ପାଇଁ + ବଟନ୍ ଦବାନ୍ତୁ। diff --git a/AnkiDroid/src/main/res/values-pa/01-core.xml b/AnkiDroid/src/main/res/values-pa/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-pa/01-core.xml +++ b/AnkiDroid/src/main/res/values-pa/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-pl/01-core.xml b/AnkiDroid/src/main/res/values-pl/01-core.xml index 16ce8ee0776c..bfcb423958d8 100644 --- a/AnkiDroid/src/main/res/values-pl/01-core.xml +++ b/AnkiDroid/src/main/res/values-pl/01-core.xml @@ -164,6 +164,7 @@ Sesja nauki niestandardowej Najpierw zmień nazwę istniejącej talii nauki niestandardowej. Ta talia jest pusta + Deck Search Dodaj kartę Nieprawidłowa nazwa talii Talia jest pusta. Wybierz przycisk + żeby dodać karty. diff --git a/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml b/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml index 62106807fa4a..8e3965d64306 100644 --- a/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml +++ b/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml @@ -154,6 +154,7 @@ Sessão de estudo personalizado Por favor renomeie o baralho de estudo personalizado existente primeiro Este baralho está vazio + Deck Search Adicionar cartão Nome do baralho inválido Este baralho está vazio. Pressione o botão + para adicionar novos conteúdos. diff --git a/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml b/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml index 32eb5d4dc887..308dd19451e8 100644 --- a/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml +++ b/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml @@ -154,6 +154,7 @@ Sessão de estudo personalizado Renomear primeiro o baralho de estudo personalizado existente Este baralho está vazio + Deck Search Add card Invalid deck name Este baralho está vazio. Toque no botão \"+\" para adicionar novo conteúdo. diff --git a/AnkiDroid/src/main/res/values-ro/01-core.xml b/AnkiDroid/src/main/res/values-ro/01-core.xml index 6c689e990233..11a8e028e35d 100644 --- a/AnkiDroid/src/main/res/values-ro/01-core.xml +++ b/AnkiDroid/src/main/res/values-ro/01-core.xml @@ -159,6 +159,7 @@ Sesiune de studiu personalizata Vă rugăm să redenumiţi mai inati pachetul de Studiu Personalizat . This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ru/01-core.xml b/AnkiDroid/src/main/res/values-ru/01-core.xml index 422d935b3756..cb09151c1d1a 100644 --- a/AnkiDroid/src/main/res/values-ru/01-core.xml +++ b/AnkiDroid/src/main/res/values-ru/01-core.xml @@ -164,6 +164,7 @@ Дополнительная учебная сессия Сначала переименуйте существующую дополнительную колоду Эта колода пуста + Deck Search Добавить карточку Недопустимое название колоды Эта колода пуста. Нажмите кнопку +, чтобы добавить что-нибудь. diff --git a/AnkiDroid/src/main/res/values-sat/01-core.xml b/AnkiDroid/src/main/res/values-sat/01-core.xml index a6679408c0d5..cf661605e9b6 100644 --- a/AnkiDroid/src/main/res/values-sat/01-core.xml +++ b/AnkiDroid/src/main/res/values-sat/01-core.xml @@ -154,6 +154,7 @@ ᱱᱤᱡᱮᱛᱮ ᱯᱟᱲᱦᱟᱣᱜ ᱥᱚᱢᱚᱭ ᱛᱮᱭᱟᱨ ᱢᱮ ᱢᱟᱲᱟᱝ ᱠᱷᱚᱱ ᱢᱮᱱᱟᱜ ᱱᱤᱡᱮᱛᱮ ᱯᱟᱲᱦᱟᱣ ᱰᱮᱠ ᱟᱜ ᱧᱩᱛᱩᱢ ᱵᱚᱫᱚᱞ ᱢᱮ ᱱᱚᱶᱟ ᱰᱮᱠ ᱫᱚ ᱠᱷᱟᱹᱞᱤ ᱜᱮᱭᱟ + Deck Search ᱠᱟᱰ ᱥᱮᱞᱮᱫᱽ ᱢᱮ ᱰᱮᱠ ᱧᱩᱛᱩᱢ ᱵᱷᱩᱞ ᱱᱚᱶᱟ ᱰᱮᱠ ᱫᱚ ᱠᱷᱟᱹᱞᱤ ᱜᱮᱭᱟ ᱾ + ᱵᱚᱴᱚᱱ ᱯᱨᱮᱥ ᱠᱟᱛᱮ ᱱᱟᱶᱟ ᱡᱤᱱᱥᱚ ᱥᱮᱞᱮᱫ ᱪᱷᱚᱭ ᱢᱮ ᱾ diff --git a/AnkiDroid/src/main/res/values-sk/01-core.xml b/AnkiDroid/src/main/res/values-sk/01-core.xml index a0618285cb2a..f124b9a98ce2 100644 --- a/AnkiDroid/src/main/res/values-sk/01-core.xml +++ b/AnkiDroid/src/main/res/values-sk/01-core.xml @@ -164,6 +164,7 @@ Vlastné štúdium Prosím premenujte najprv existujúci balíček Vlastné štúdium. Tento balíček je prázdny + Deck Search Add card Neplatný názov balíčka Tento balíček je prázdny. Stlačte + pre pridávanie obsahu. diff --git a/AnkiDroid/src/main/res/values-sl/01-core.xml b/AnkiDroid/src/main/res/values-sl/01-core.xml index 5aba93843d4c..e631d6559fc1 100644 --- a/AnkiDroid/src/main/res/values-sl/01-core.xml +++ b/AnkiDroid/src/main/res/values-sl/01-core.xml @@ -164,6 +164,7 @@ Seja učenja po meri Najprej preimenujte obstoječi komplet za učenje po meri. Ta komplet je prazen + Deck Search Add card Invalid deck name Ta komplet je prazen. Pritisnite na gumb +, da dodate novo vsebino. diff --git a/AnkiDroid/src/main/res/values-sq/01-core.xml b/AnkiDroid/src/main/res/values-sq/01-core.xml index f96c14f3c47c..78e1f7a76cec 100644 --- a/AnkiDroid/src/main/res/values-sq/01-core.xml +++ b/AnkiDroid/src/main/res/values-sq/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-sr/01-core.xml b/AnkiDroid/src/main/res/values-sr/01-core.xml index 80764c936032..f40c4fd5c174 100644 --- a/AnkiDroid/src/main/res/values-sr/01-core.xml +++ b/AnkiDroid/src/main/res/values-sr/01-core.xml @@ -159,6 +159,7 @@ Посебна сесија проучавања Прво преименујте постојеће специјалне шпилове. Овај шпил је празан + Deck Search Додај карту Invalid deck name Овај шпил је празан. Притисните дугме +, да бисте додали нови садржај. diff --git a/AnkiDroid/src/main/res/values-ss/01-core.xml b/AnkiDroid/src/main/res/values-ss/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-ss/01-core.xml +++ b/AnkiDroid/src/main/res/values-ss/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-sv/01-core.xml b/AnkiDroid/src/main/res/values-sv/01-core.xml index 8507e6a90828..2a8f486d219f 100644 --- a/AnkiDroid/src/main/res/values-sv/01-core.xml +++ b/AnkiDroid/src/main/res/values-sv/01-core.xml @@ -154,6 +154,7 @@ Anpassad studiesession Var god döp om den existerande anpassade studiekortleken först. Den här leken är tom + Deck Search Lägg till kort Ogiltigt kortleksnamn Den här leken är tom. Använd +-knappen för att lägga till nytt innehåll. diff --git a/AnkiDroid/src/main/res/values-sw/01-core.xml b/AnkiDroid/src/main/res/values-sw/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-sw/01-core.xml +++ b/AnkiDroid/src/main/res/values-sw/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ta/01-core.xml b/AnkiDroid/src/main/res/values-ta/01-core.xml index fc4e1c129280..6f54a7220b93 100644 --- a/AnkiDroid/src/main/res/values-ta/01-core.xml +++ b/AnkiDroid/src/main/res/values-ta/01-core.xml @@ -154,6 +154,7 @@ தனிப்பயன் ஆய்வு கூட்டம் தற்போதுள்ள விருப்ப ஆய்வு கட்டு முதலில் மறுபெயரிடு இந்த கட்டு காலியாக உள்ளது + Deck Search Add card Invalid deck name இந்த கட்டு காலியாக உள்ளது. பத்திரிகை, + புதிய உள்ளடக்கம் சேர்க்க பொத்தான். diff --git a/AnkiDroid/src/main/res/values-te/01-core.xml b/AnkiDroid/src/main/res/values-te/01-core.xml index b3ffaf7c1b70..d0063f534efd 100644 --- a/AnkiDroid/src/main/res/values-te/01-core.xml +++ b/AnkiDroid/src/main/res/values-te/01-core.xml @@ -154,6 +154,7 @@ కస్టమ్ అధ్యయనం సెషన్ మొదటి సానుకూల అధ్యయనం డెక్ యొక్క పేరు మార్చండి ఈ డెక్ ఖాళీగా ఉంది + Deck Search Add card Invalid deck name ఈ డెక్ ఖాళీగా ఉంది. క్రొత్త కంటెంట్ని జోడించడానికి + బటన్ నొక్కండి. diff --git a/AnkiDroid/src/main/res/values-tg/01-core.xml b/AnkiDroid/src/main/res/values-tg/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-tg/01-core.xml +++ b/AnkiDroid/src/main/res/values-tg/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-tgl/01-core.xml b/AnkiDroid/src/main/res/values-tgl/01-core.xml index c22e5f6ccc40..557f43283716 100644 --- a/AnkiDroid/src/main/res/values-tgl/01-core.xml +++ b/AnkiDroid/src/main/res/values-tgl/01-core.xml @@ -164,6 +164,7 @@ Pinagaralang %1$d mga baraha %2$s ngayong araw Pinasadyang sesyon ng pagaaral Palitan muna ang pangalan ng pinasadyang paaaral ng deck Walang laman ang deck na ito + Deck Search Add card Invalid deck name Walang laman ang deck na ito. Pindutin and + na butones upang magdagdag ng bago. diff --git a/AnkiDroid/src/main/res/values-th/01-core.xml b/AnkiDroid/src/main/res/values-th/01-core.xml index 207739f3061b..c154eee85aef 100644 --- a/AnkiDroid/src/main/res/values-th/01-core.xml +++ b/AnkiDroid/src/main/res/values-th/01-core.xml @@ -150,6 +150,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ti/01-core.xml b/AnkiDroid/src/main/res/values-ti/01-core.xml index 89e43901cb79..80311d6a979d 100644 --- a/AnkiDroid/src/main/res/values-ti/01-core.xml +++ b/AnkiDroid/src/main/res/values-ti/01-core.xml @@ -154,6 +154,7 @@ መጽናዕቲ ብዝጠዕም ምምዕርራይ መጀመርያ ዝነበረ ምምዕርራይ መጽናዕቲ ባይታ ከምባሓዱሽ ሰዩም። እዚ ባይታ እዚ ጥርሑ እዩ ዘሎ። + Deck Search Add card Invalid deck name ባይታ ጥርሑ ኣሎ። መልጎም + ጠውቅ ሓዲሽ ትሕዝቶ ክትውስኹ። diff --git a/AnkiDroid/src/main/res/values-tn/01-core.xml b/AnkiDroid/src/main/res/values-tn/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-tn/01-core.xml +++ b/AnkiDroid/src/main/res/values-tn/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-tr/01-core.xml b/AnkiDroid/src/main/res/values-tr/01-core.xml index 73d2ae93c50a..f3a0d990c279 100644 --- a/AnkiDroid/src/main/res/values-tr/01-core.xml +++ b/AnkiDroid/src/main/res/values-tr/01-core.xml @@ -154,6 +154,7 @@ Kişiselleştirilmiş çalışma oturumu Lütfen mevcut özelleştirilmiş çalışma destesini yeniden adlandırın. Bu deste boş + Deck Search Kart ekle Geçersiz deste adı Bu deste boş. Yeni içerik eklemek için + düğmesine basın. diff --git a/AnkiDroid/src/main/res/values-ts/01-core.xml b/AnkiDroid/src/main/res/values-ts/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-ts/01-core.xml +++ b/AnkiDroid/src/main/res/values-ts/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-tt/01-core.xml b/AnkiDroid/src/main/res/values-tt/01-core.xml index 37c3623c175b..88d6bd545ecc 100644 --- a/AnkiDroid/src/main/res/values-tt/01-core.xml +++ b/AnkiDroid/src/main/res/values-tt/01-core.xml @@ -104,10 +104,10 @@ Төс Төс кую Төссез - Кызыл төс - Кызыл-сары төс - Яшел төс - Зәңгәр төс + Кызыл байрак + Кызыл-сары байрак + Яшел байрак + Зәңгәр байрак Тегларны үзгәртү Бу язуны һәм бөтен аның кәртләрен бетерергәме?\n%s Текстны сайлау @@ -149,6 +149,7 @@ Шәхси өйрәнү сессиясе Башта булган артык өйрәнү колода исемен үзгәртегез Бу колода буш + Deck Search Add card Шундый колода исеме була алмый Бу колода буш. Берәрнәрсә өстәү өчен + төймәсенә басыгыз. diff --git a/AnkiDroid/src/main/res/values-tt/02-strings.xml b/AnkiDroid/src/main/res/values-tt/02-strings.xml index 4b86f7083edd..7454146a5d21 100644 --- a/AnkiDroid/src/main/res/values-tt/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tt/02-strings.xml @@ -222,10 +222,10 @@ %s days - %s months + %s ай - %s years + %s ел Disable Overwrite - Remove + Бетерү Repair Replace Restore - Set + Урнаштыру Look up The path specified wasn’t a valid directory @@ -145,8 +145,8 @@ All cards - New - Due + Яңа + Вакыт Finding empty cards… Do you want to cancel? diff --git a/AnkiDroid/src/main/res/values-tt/04-network.xml b/AnkiDroid/src/main/res/values-tt/04-network.xml index cbc738948f8a..fa65810a659f 100644 --- a/AnkiDroid/src/main/res/values-tt/04-network.xml +++ b/AnkiDroid/src/main/res/values-tt/04-network.xml @@ -51,12 +51,12 @@ Email address Password - Log in + Керү Don’t have an AnkiWeb account? It’s free! Note: AnkiWeb is not affiliated with AnkiDroid - Sign up + Теркәлү Logged in as - Log out + Чыгу Logging in… Invalid email address or password Reset password @@ -65,7 +65,7 @@ Options for XXX You must either upload this collection or download it from Ankiweb, as they can’t be merged. The collection on one side will be overwritten. Upload - Download + Йөкләп алу Overwrite your local collection? Overwrite your collection on the server? Preparing syncing… diff --git a/AnkiDroid/src/main/res/values-tt/06-statistics.xml b/AnkiDroid/src/main/res/values-tt/06-statistics.xml index 933d7a17240a..24927fe44369 100644 --- a/AnkiDroid/src/main/res/values-tt/06-statistics.xml +++ b/AnkiDroid/src/main/res/values-tt/06-statistics.xml @@ -57,15 +57,15 @@ Cumulative percentage Cumulative correct % Answers - Minutes - Hours + Минутлар + Сәгатьләр Percentage - 1 month - 1 year + 1 ай + 1 ел deck life collection Time of day - Day of week + Атна көне % correct Reviews Answer type @@ -102,35 +102,35 @@ Highest ease: <b>%.0f%%</b> Select timescale - Days - Weeks - Months + Көннәр + Атналар + Айлар - Today + Бүген Overview Forecast Review count Review time - Added + Өстәлгән Intervals Hourly breakdown Weekly breakdown Answer buttons - 4AM - 10AM - 4PM - 10PM - 3AM + 4:00 + 10:00 + 16:00 + 22:00 + 3:00 - Sun - Mon - Tue - Wed - Thu - Fri - Sat + Якш + Дүш + Сиш + Чәр + Сиш + Җом + Шим 1 diff --git a/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml index a39b45157ac9..d04a3eaa6871 100644 --- a/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml @@ -65,7 +65,7 @@ Change display order Choose display order Select a field twice to reverse - Due + Вакыт Tags No sorting (faster) @@ -81,7 +81,7 @@ Select all Select none - (new) + (яңа) (filtered) (learning) No cards found in deck ‘%s’ @@ -94,7 +94,7 @@ Added First Review Latest Review - Interval + Интервал Ease Reviews Lapses diff --git a/AnkiDroid/src/main/res/values-tt/09-backup.xml b/AnkiDroid/src/main/res/values-tt/09-backup.xml index 5bdd1159c286..f2bc25bd5fc8 100644 --- a/AnkiDroid/src/main/res/values-tt/09-backup.xml +++ b/AnkiDroid/src/main/res/values-tt/09-backup.xml @@ -47,7 +47,7 @@ Full sync from server Overwrite your collection with the one from AnkiWeb? This will drop all your learning progress and added information since your last sync. Error handling - Options + Көйләүләр Retry opening Repair database diff --git a/AnkiDroid/src/main/res/values-tt/10-preferences.xml b/AnkiDroid/src/main/res/values-tt/10-preferences.xml index 1602926af8db..23e2350249c3 100644 --- a/AnkiDroid/src/main/res/values-tt/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tt/10-preferences.xml @@ -51,8 +51,8 @@ Appearance Change themes and default font Themes - Fonts - Gestures + Шрифтлар + Ишарәләр Use taps and swipes instead of buttons Actions Advanced @@ -61,7 +61,7 @@ Workarounds Plugins About AnkiDroid - Version: + Версия: Stroke width Type answer into the card @@ -79,13 +79,13 @@ Display media filenames in the card browser question/answer fields AnkiDroid directory Fullscreen mode - Off + Сүнгән Hide the system bars Hide the system bars and answer buttons You must enable gestures to hide the answer buttons Answer buttons position - Top + Өске Bottom Enable gestures @@ -113,7 +113,7 @@ Touch bottom center Touch bottom right Volume up - Volume down + Тавышны киметү eReader Also use kanji/katakana, emoji/kao-moji buttons for scrolling ‘%s’ Menu @@ -134,10 +134,10 @@ Display synchronization status Change the sync icon when changes can be uploaded Day theme - Night theme + Төнге режим Default font Default font applicability - Language + Тел System language Notifications Notify when @@ -149,7 +149,7 @@ Automatic display answer Show answer automatically without user input. Delay includes time for automatically played audio files. Time to show answer - XXX s + XXX сек Time to show next question Select language Safe display mode @@ -172,7 +172,7 @@ New card position Learn ahead limit Timebox time limit - XXX min + XXX мин New timezone handling Start of next day (0 is midnight, 23 is 11PM) XXX hours past midnight @@ -227,7 +227,7 @@ General Reminders Steps (in minutes) - Order + Тәртипкә салу New cards/day Graduating interval XXX day(s) @@ -261,7 +261,7 @@ Rename Restore defaults Restore current options group to default values? - Add + Кушу Create a copy of the current options group Remove current options group? Set for all subdecks @@ -271,7 +271,7 @@ Set current options group for all of this deck’s subdecks? Browser and editor font Filter - Search + Эзләү Limit to cards selected by Reschedule diff --git a/AnkiDroid/src/main/res/values-tt/11-arrays.xml b/AnkiDroid/src/main/res/values-tt/11-arrays.xml index 636d468a5e97..d117edde212a 100644 --- a/AnkiDroid/src/main/res/values-tt/11-arrays.xml +++ b/AnkiDroid/src/main/res/values-tt/11-arrays.xml @@ -37,7 +37,7 @@ Always report Never report - Ask me + Миннән сорау Suspend card @@ -72,12 +72,12 @@ Always - Light + Ак Plain - Black - Dark + Кара + Караңгы xx-small diff --git a/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml b/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml index 110759a6bf15..83e27679a190 100644 --- a/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml +++ b/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml @@ -46,7 +46,7 @@ From To - Translate + Тәрҗемә итү Translating online @@ -57,7 +57,7 @@ Install ColorDict first - Load + Йөкләү Looking up pronunciation Pronunciation failed @@ -65,8 +65,8 @@ Powered by Beolingus - Text - Image + Текст + Рәсем Audio recording Audio Clip Done @@ -74,9 +74,9 @@ Search for: Pronunciation - Clear + Чистарту Clone from - Translation + Тәрҗемә Input some text first Dictionary usually expects one word @@ -88,8 +88,8 @@ Gallery - Camera - Library + Камера + Китапханә Downloading @@ -113,7 +113,7 @@ Unable to create recorder tool bar Editing field - Translation + Тәрҗемә No connection diff --git a/AnkiDroid/src/main/res/values-tt/17-model-manager.xml b/AnkiDroid/src/main/res/values-tt/17-model-manager.xml index 521ddaa6c702..9938d3045551 100644 --- a/AnkiDroid/src/main/res/values-tt/17-model-manager.xml +++ b/AnkiDroid/src/main/res/values-tt/17-model-manager.xml @@ -55,7 +55,7 @@ Remember last input when adding Updating fields Sort by this field - copy + күчереп алу Are you sure you wish to delete this note type? Are you sure you wish to delete this field? diff --git a/AnkiDroid/src/main/res/values-tt/18-standard-models.xml b/AnkiDroid/src/main/res/values-tt/18-standard-models.xml index 802df611f732..fe1e1af408ec 100644 --- a/AnkiDroid/src/main/res/values-tt/18-standard-models.xml +++ b/AnkiDroid/src/main/res/values-tt/18-standard-models.xml @@ -20,8 +20,8 @@ - Front - Back + Алгы + Баш тарту Text Extra Add Reverse diff --git a/AnkiDroid/src/main/res/values-uk/01-core.xml b/AnkiDroid/src/main/res/values-uk/01-core.xml index a8e8534faf6a..7b7ad0c73be3 100644 --- a/AnkiDroid/src/main/res/values-uk/01-core.xml +++ b/AnkiDroid/src/main/res/values-uk/01-core.xml @@ -116,7 +116,7 @@ Відкласти Призупинити Видалити запис - Позначка + Прапорець Позначити картку Без позначки Червоний прапорець @@ -164,6 +164,7 @@ Додаткове навчання Спочатку перейменуйте колоду додаткового навчання Ця колода порожня + Deck Search Додати картку Некоректна назва колоди Ця колода порожня. Натисніть на кнопку +, щоб додати новий вміст. diff --git a/AnkiDroid/src/main/res/values-ur/01-core.xml b/AnkiDroid/src/main/res/values-ur/01-core.xml index eb30e4ef6147..ad0bfe06a659 100644 --- a/AnkiDroid/src/main/res/values-ur/01-core.xml +++ b/AnkiDroid/src/main/res/values-ur/01-core.xml @@ -154,6 +154,7 @@ اپنی مرضی کے مطابق تعلیمی سیشن پہلے موجودہ مخصوص مطالعہ ڈیک کو نیا نام دیں یہ ڈیک خالی ہے + Deck Search Add card Invalid deck name یہ ڈیک خالی ہے ۔ + کے بٹن کو دبا کر نیا مواد ڈالیں. diff --git a/AnkiDroid/src/main/res/values-uz/01-core.xml b/AnkiDroid/src/main/res/values-uz/01-core.xml index f8ccf6a9c33b..653a32783b29 100644 --- a/AnkiDroid/src/main/res/values-uz/01-core.xml +++ b/AnkiDroid/src/main/res/values-uz/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-ve/01-core.xml b/AnkiDroid/src/main/res/values-ve/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-ve/01-core.xml +++ b/AnkiDroid/src/main/res/values-ve/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-vi/01-core.xml b/AnkiDroid/src/main/res/values-vi/01-core.xml index 88a37ac89037..f2859fce345a 100644 --- a/AnkiDroid/src/main/res/values-vi/01-core.xml +++ b/AnkiDroid/src/main/res/values-vi/01-core.xml @@ -149,6 +149,7 @@ Phiên học tuỳ chọn Đổi tên bộ thẻ học tuỳ chọn hiện hữu trước nhất Bộ thẻ này rỗng + Deck Search Thêm thẻ Tên bộ thẻ không hợp lệ Bộ thẻ này rỗng. Bấm bút + để thêm nội dung mới. diff --git a/AnkiDroid/src/main/res/values-wo/01-core.xml b/AnkiDroid/src/main/res/values-wo/01-core.xml index 86c4c87b9863..4088b283a29d 100644 --- a/AnkiDroid/src/main/res/values-wo/01-core.xml +++ b/AnkiDroid/src/main/res/values-wo/01-core.xml @@ -149,6 +149,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-xh/01-core.xml b/AnkiDroid/src/main/res/values-xh/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-xh/01-core.xml +++ b/AnkiDroid/src/main/res/values-xh/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-yue/01-core.xml b/AnkiDroid/src/main/res/values-yue/01-core.xml index 8083d07717fa..ae20a9067871 100644 --- a/AnkiDroid/src/main/res/values-yue/01-core.xml +++ b/AnkiDroid/src/main/res/values-yue/01-core.xml @@ -149,6 +149,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml b/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml index af06402edebd..8477e0d1cb93 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml @@ -149,6 +149,7 @@ 自定义学习的进程 请先重命名现有的自定义学习牌组。 此列表为空 + Deck Search 添加卡片 无效的牌组名称 这个牌组是空的,请点击 + 按钮来添加内容。 diff --git a/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml b/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml index e751dcc29a86..5e8d2841fefd 100644 --- a/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml +++ b/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml @@ -150,6 +150,7 @@ 自訂學程 請先重新命名現有的「自訂學習」牌組。 這個牌組是空的 + Deck Search 新增卡片 無效牌組名稱 這個牌組是空的。按下 + 按鈕增加新內容。 diff --git a/AnkiDroid/src/main/res/values-zu/01-core.xml b/AnkiDroid/src/main/res/values-zu/01-core.xml index 2cdddc9f6a84..abd44cdb28e8 100644 --- a/AnkiDroid/src/main/res/values-zu/01-core.xml +++ b/AnkiDroid/src/main/res/values-zu/01-core.xml @@ -154,6 +154,7 @@ Custom study session Rename the existing custom study deck first This deck is empty + Deck Search Add card Invalid deck name This deck is empty. Press the + button to add new content. diff --git a/docs/marketing/localized_description/marketdescription-tt-RU.txt b/docs/marketing/localized_description/marketdescription-tt-RU.txt index cadb8e016e64..479f9bcd9a43 100644 --- a/docs/marketing/localized_description/marketdescription-tt-RU.txt +++ b/docs/marketing/localized_description/marketdescription-tt-RU.txt @@ -16,7 +16,7 @@ Add material through the desktop application Anki or directly through AnkiDroid. • progress widget • detailed statistics • syncing with AnkiWeb -• open source +• ачык код ★ Additional features: • write answers (optional) From 393a959a4406ce856f841cf4f6fb9c02294d0f68 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Fri, 2 Apr 2021 00:13:48 +0000 Subject: [PATCH 026/171] Bumped version to 2.15alpha38 @branch-specific --- AnkiDroid/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 4c74aab63904..591dab567a61 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -50,8 +50,8 @@ 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=21500137 - versionName="2.15alpha37" + versionCode=21500138 + versionName="2.15alpha38" minSdkVersion 21 //noinspection OldTargetApi - also performed in api/build.fradle targetSdkVersion 29 // change .travis.yml platform download at same time From 952ad3d31ddd7a55ba23a4434b144c9ea7d862ea Mon Sep 17 00:00:00 2001 From: Piyush Goel <46752548+Arnold2381@users.noreply.github.com> Date: Fri, 2 Apr 2021 21:12:04 +0530 Subject: [PATCH 027/171] lint: Enforce 'm' prefix rule for fields in AbstractFlashcardViewer (#8435) --- .../ichi2/anki/AbstractFlashcardViewer.java | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index a40df5370302..69f50836de66 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -326,7 +326,7 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i /** * Swipe Detection */ - private GestureDetector gestureDetector; + private GestureDetector mGestureDetector; private MyGestureDetector mGestureDetectorImpl; private boolean mLinkOverridesTouchGesture; @@ -369,7 +369,7 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i * If we get 2 crashes on the same card, then we likely have an infinite loop and want to exit gracefully. */ @Nullable - private Long lastCrashingCardId = null; + private Long mLastCrashingCardId = null; /** Reference to the parent of the cardFrame to allow regeneration of the cardFrame in case of crash */ private ViewGroup mCardFrameParent; @@ -399,8 +399,8 @@ public void handleMessage(Message msg) { } }; - private final Handler longClickHandler = new Handler(); - private final Runnable longClickTestRunnable = new Runnable() { + private final Handler mLongClickHandler = new Handler(); + private final Runnable mLongClickTestRunnable = new Runnable() { @Override public void run() { Timber.i("AbstractFlashcardViewer:: onEmulatedLongClick"); @@ -410,10 +410,10 @@ public void run() { UIUtils.showThemedToast(AbstractFlashcardViewer.this, lookupHint, false); } CompatHelper.getCompat().vibrate(AnkiDroidApp.getInstance().getApplicationContext(), 50); - longClickHandler.postDelayed(startLongClickAction, 300); + mLongClickHandler.postDelayed(mStartLongClickAction, 300); } }; - private final Runnable startLongClickAction = new Runnable() { + private final Runnable mStartLongClickAction = new Runnable() { @Override public void run() { executeCommand(mGestureLongclick); @@ -467,24 +467,24 @@ public void onClick(View view) { private final View.OnTouchListener mGestureListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - if (gestureDetector.onTouchEvent(event)) { + if (mGestureDetector.onTouchEvent(event)) { return true; } if (!mDisableClipboard) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mTouchStarted = true; - longClickHandler.postDelayed(longClickTestRunnable, 800); + mLongClickHandler.postDelayed(mLongClickTestRunnable, 800); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_MOVE: if (mTouchStarted) { - longClickHandler.removeCallbacks(longClickTestRunnable); + mLongClickHandler.removeCallbacks(mLongClickTestRunnable); mTouchStarted = false; } break; default: - longClickHandler.removeCallbacks(longClickTestRunnable); + mLongClickHandler.removeCallbacks(mLongClickTestRunnable); mTouchStarted = false; break; } @@ -857,7 +857,7 @@ protected String contentForCloze(String txt, int idx) { private final Handler mTimerHandler = new Handler(); - private final Runnable removeChosenAnswerText = new Runnable() { + private final Runnable mRemoveChosenAnswerText = new Runnable() { @Override public void run() { mChosenAnswer.setText(""); @@ -986,8 +986,8 @@ protected void onPause() { mTimeoutHandler.removeCallbacks(mShowAnswerTask); mTimeoutHandler.removeCallbacks(mShowQuestionTask); - longClickHandler.removeCallbacks(longClickTestRunnable); - longClickHandler.removeCallbacks(startLongClickAction); + mLongClickHandler.removeCallbacks(mLongClickTestRunnable); + mLongClickHandler.removeCallbacks(mStartLongClickAction); pauseTimer(); mSoundPlayer.stopSounds(); @@ -1252,7 +1252,7 @@ private boolean hasLoadedCardContent() { public GestureDetector getGestureDetector() { - return gestureDetector; + return mGestureDetector; } @@ -1452,8 +1452,8 @@ protected void answerCard(@Consts.BUTTON_TYPE int ease) { } // remove chosen answer hint after a while - mTimerHandler.removeCallbacks(removeChosenAnswerText); - mTimerHandler.postDelayed(removeChosenAnswerText, sShowChosenAnswerLength); + mTimerHandler.removeCallbacks(mRemoveChosenAnswerText); + mTimerHandler.postDelayed(mRemoveChosenAnswerText, sShowChosenAnswerLength); mSoundPlayer.stopSounds(); mCurrentEase = ease; @@ -1508,7 +1508,7 @@ protected void initLayout() { // Initialize swipe mGestureDetectorImpl = mLinkOverridesTouchGesture ? new LinkDetectingGestureDetector() : new MyGestureDetector(); - gestureDetector = new GestureDetector(this, mGestureDetectorImpl); + mGestureDetector = new GestureDetector(this, mGestureDetectorImpl); mEaseButtonsLayout = findViewById(R.id.ease_buttons); @@ -1650,7 +1650,7 @@ private void flipOrAnswerCard(int cardOrdinal) { private boolean webViewRendererLastCrashedOnCard(long cardId) { - return lastCrashingCardId != null && lastCrashingCardId == cardId; + return mLastCrashingCardId != null && mLastCrashingCardId == cardId; } @@ -2856,9 +2856,9 @@ protected void closeReviewer(int result, boolean saveDeck) { mTimeoutHandler.removeCallbacks(mShowAnswerTask); mTimeoutHandler.removeCallbacks(mShowQuestionTask); - mTimerHandler.removeCallbacks(removeChosenAnswerText); - longClickHandler.removeCallbacks(longClickTestRunnable); - longClickHandler.removeCallbacks(startLongClickAction); + mTimerHandler.removeCallbacks(mRemoveChosenAnswerText); + mLongClickHandler.removeCallbacks(mLongClickTestRunnable); + mLongClickHandler.removeCallbacks(mStartLongClickAction); AbstractFlashcardViewer.this.setResult(result); @@ -2896,12 +2896,12 @@ protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { super.onScrollChanged(horiz, vert, oldHoriz, oldVert); if (Math.abs(horiz - oldHoriz) > Math.abs(vert - oldVert)) { mIsXScrolling = true; - scrollHandler.removeCallbacks(scrollXRunnable); - scrollHandler.postDelayed(scrollXRunnable, 300); + mScrollHandler.removeCallbacks(mScrollXRunnable); + mScrollHandler.postDelayed(mScrollXRunnable, 300); } else { mIsYScrolling = true; - scrollHandler.removeCallbacks(scrollYRunnable); - scrollHandler.postDelayed(scrollYRunnable, 300); + mScrollHandler.removeCallbacks(mScrollYRunnable); + mScrollHandler.postDelayed(mScrollYRunnable, 300); } } @@ -2942,9 +2942,9 @@ private ViewParent findScrollParent(View current) { return null; } - private final Handler scrollHandler = new Handler(); - private final Runnable scrollXRunnable = () -> mIsXScrolling = false; - private final Runnable scrollYRunnable = () -> mIsYScrolling = false; + private final Handler mScrollHandler = new Handler(); + private final Runnable mScrollXRunnable = () -> mIsXScrolling = false; + private final Runnable mScrollYRunnable = () -> mIsYScrolling = false; } @@ -3027,7 +3027,7 @@ public boolean onDoubleTap(MotionEvent e) { @Override public boolean onSingleTapUp(MotionEvent e) { if (mTouchStarted) { - longClickHandler.removeCallbacks(longClickTestRunnable); + mLongClickHandler.removeCallbacks(mLongClickTestRunnable); mTouchStarted = false; } return false; @@ -3700,7 +3700,7 @@ public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) // If we get here, the error is non-fatal and we should re-render the WebView // This logic may need to be better defined. The card could have changed by the time we get here. - lastCrashingCardId = mCurrentCard.getId(); + mLastCrashingCardId = mCurrentCard.getId(); String nonFatalError = getResources().getString(R.string.webview_crash_nonfatal, errorCauseString); From c91dd1793abe8032ff65f16cc884ab784b1d330b Mon Sep 17 00:00:00 2001 From: Vaibhavi Lokegaonkar <53178543+Vaibhavi1707@users.noreply.github.com> Date: Sat, 3 Apr 2021 00:27:33 +0530 Subject: [PATCH 028/171] Avoid returning 0 SQL window size accidentally (#8433) * SQL window size calculation fixed Co-authored-by: Mike Hardy --- AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java index 1fbcef3c9669..83c0999e42f7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java @@ -332,10 +332,8 @@ private int getChunk() { // We have the ability to look into our sqlite implementation on Android and use it's value // as a ceiling. Try it, with a reasonable fallback in case of failure SupportSQLiteDatabase db = mDb.getDatabase(); - if (! (db instanceof DatabaseChangeDecorator)) { - return sChunk; - } - String db_name = ((DatabaseChangeDecorator) db).getWrapped().getClass().getName(); + String db_name = (db instanceof DatabaseChangeDecorator) ? ((DatabaseChangeDecorator) db).getWrapped().getClass().getName() : null; + if ("io.requery.android.database.sqlite.SQLiteDatabase".equals(db_name)) { try { Field cursorWindowSize = io.requery.android.database.CursorWindow.class.getDeclaredField("sDefaultCursorWindowSize"); @@ -353,6 +351,7 @@ private int getChunk() { } // reduce the actual size a little bit. + // In case db is not an instance of DatabaseChangeDecorator, sChunk evaluated on default window size sChunk = (int) (sCursorWindowSize * 15. / 16.); return sChunk; } From a18fc25f8babb93ba73abcd9f30c6ab172793026 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 31 Mar 2021 01:46:22 +0200 Subject: [PATCH 029/171] NF: Split Dismiss task This avoid a huge case that is not even exhaustive, far more readable as each class concentrate on what matters --- .../ichi2/anki/AbstractFlashcardViewer.java | 15 +- .../main/java/com/ichi2/anki/Reviewer.java | 12 +- .../java/com/ichi2/async/CollectionTask.java | 144 +++++++++++------- .../ichi2/anki/ReviewerKeyboardInputTest.java | 13 +- 4 files changed, 109 insertions(+), 75 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 69f50836de66..43fc15711a09 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -148,7 +148,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; @@ -1387,7 +1386,7 @@ protected void showDeleteNoteDialog() { .onPositive((dialog, which) -> { Timber.i("AbstractFlashcardViewer:: OK button pressed to delete note %d", mCurrentCard.getNid()); mSoundPlayer.stopSounds(); - dismiss(Collection.DismissType.DELETE_NOTE); + dismiss(new CollectionTask.DeleteNote(mCurrentCard)); }) .build().show(); } @@ -2635,16 +2634,16 @@ public boolean executeCommand(@ViewerCommandDef int which) { lookUpOrSelectText(); return true; case COMMAND_BURY_CARD: - dismiss(Collection.DismissType.BURY_CARD); + dismiss(new CollectionTask.BuryCard(mCurrentCard)); return true; case COMMAND_BURY_NOTE: - dismiss(Collection.DismissType.BURY_NOTE); + dismiss(new CollectionTask.BuryNote(mCurrentCard)); return true; case COMMAND_SUSPEND_CARD: - dismiss(Collection.DismissType.SUSPEND_CARD); + dismiss(new CollectionTask.SuspendCard(mCurrentCard)); return true; case COMMAND_SUSPEND_NOTE: - dismiss(Collection.DismissType.SUSPEND_NOTE); + dismiss(new CollectionTask.SuspendNote(mCurrentCard)); return true; case COMMAND_DELETE: showDeleteNoteDialog(); @@ -3277,9 +3276,9 @@ protected void onFlag(Card card, @FlagDef int flag) { } */ } - protected void dismiss(Collection.DismissType type) { + protected void dismiss(CollectionTask.DismissNote dismiss) { blockControls(false); - TaskManager.launchCollectionTask(new CollectionTask.DismissNote(mCurrentCard, type), mDismissCardHandler); + TaskManager.launchCollectionTask(dismiss, mDismissCardHandler); } /** Signals from a WebView represent actions with no parameters */ diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index b3722c7f3906..3991fe872f40 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -342,13 +342,13 @@ public boolean onOptionsItemSelected(MenuItem item) { Timber.i("Reviewer:: Bury button pressed"); if (!MenuItemCompat.getActionProvider(item).hasSubMenu()) { Timber.d("Bury card due to no submenu"); - dismiss(DismissType.BURY_CARD); + dismiss(new CollectionTask.BuryCard(mCurrentCard)); } } else if (itemId == R.id.action_suspend) { Timber.i("Reviewer:: Suspend button pressed"); if (!MenuItemCompat.getActionProvider(item).hasSubMenu()) { Timber.d("Suspend card due to no submenu"); - dismiss(DismissType.SUSPEND_CARD); + dismiss(new CollectionTask.SuspendCard(mCurrentCard)); } } else if (itemId == R.id.action_delete) { Timber.i("Reviewer:: Delete note button pressed"); @@ -1306,10 +1306,10 @@ public void onPrepareSubMenu(SubMenu subMenu) { public boolean onMenuItemClick(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.action_suspend_card) { - dismiss(DismissType.SUSPEND_CARD); + dismiss(new CollectionTask.SuspendCard(mCurrentCard)); return true; } else if (itemId == R.id.action_suspend_note) { - dismiss(DismissType.SUSPEND_NOTE); + dismiss(new CollectionTask.SuspendNote(mCurrentCard)); return true; } return false; @@ -1354,10 +1354,10 @@ public void onPrepareSubMenu(SubMenu subMenu) { public boolean onMenuItemClick(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.action_bury_card) { - dismiss(DismissType.BURY_CARD); + dismiss(new CollectionTask.BuryCard(mCurrentCard)); return true; } else if (itemId == R.id.action_bury_note) { - dismiss(DismissType.BURY_NOTE); + dismiss(new CollectionTask.BuryNote(mCurrentCard)); return true; } return false; diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index a763c7670500..e46316b62ea8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -478,75 +478,27 @@ public UndoDeleteNote(Note note, ArrayList allCs, @NonNull Card card) { } } - - public static class DismissNote extends Task { + public static abstract class DismissNote extends Task { private final Card card; - private final Collection.DismissType type; - public DismissNote(Card card, Collection.DismissType type) { + public DismissNote(Card card) { this.card = card; - this.type = type; } + protected abstract void actualTask(Collection col, Card card); + protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener collectionTask) { - AbstractSched sched = col.getSched(); - Note note = card.note(); try { col.getDb().executeInTransaction(() -> { - sched.deferReset(); - switch (type) { - case BURY_CARD: - // collect undo information - col.markUndo(revertToProvidedState(BURY_CARD, card)); - // then bury - sched.buryCards(new long[] {card.getId()}); - break; - case BURY_NOTE: - // collect undo information - col.markUndo(revertToProvidedState(BURY_NOTE, card)); - // then bury - sched.buryNote(note.getId()); - break; - case SUSPEND_CARD: - // collect undo information - Card suspendedCard = card.clone(); - col.markUndo(new UndoSuspendCard(suspendedCard)); - // suspend card - if (card.getQueue() == Consts.QUEUE_TYPE_SUSPENDED) { - sched.unsuspendCards(new long[] {card.getId()}); - } else { - sched.suspendCards(new long[] {card.getId()}); - } - break; - case SUSPEND_NOTE: { - // collect undo information - ArrayList cards = note.cards(); - long[] cids = new long[cards.size()]; - for (int i = 0; i < cards.size(); i++) { - cids[i] = cards.get(i).getId(); - } - col.markUndo(revertToProvidedState(SUSPEND_NOTE, card)); - // suspend note - sched.suspendCards(cids); - break; - } - - case DELETE_NOTE: { - // collect undo information - ArrayList allCs = note.cards(); - col.markUndo(new UndoDeleteNote(note, allCs, card)); - // delete note - col.remNotes(new long[] {note.getId()}); - break; - } - } + col.getSched().deferReset(); + actualTask(col, card); // With sHadCardQueue set, getCard() resets the scheduler prior to getting the next card collectionTask.doProgress(col.getSched().getCard()); }); } catch (RuntimeException e) { - Timber.e(e, "doInBackgroundDismissNote - RuntimeException on dismissing note, dismiss type %s", type); + Timber.e(e, "doInBackgroundDismissNote - RuntimeException on dismissing note, dismiss type %s", this.getClass()); AnkiDroidApp.sendExceptionReport(e, "doInBackgroundDismissNote"); return False; } @@ -554,6 +506,88 @@ protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener cards = card.note().cards(); + long[] cids = new long[cards.size()]; + for (int i = 0; i < cards.size(); i++) { + cids[i] = cards.get(i).getId(); + } + col.markUndo(revertToProvidedState(SUSPEND_NOTE, card)); + // suspend note + col.getSched().suspendCards(cids); + } + } + + public static class DeleteNote extends DismissNote { + public DeleteNote(Card card) { + super(card); + } + + @Override + protected void actualTask(Collection col, Card card) { + Note note = card.note(); + // collect undo information + ArrayList allCs = note.cards(); + col.markUndo(new UndoDeleteNote(note, allCs, card)); + // delete note + col.remNotes(new long[] {note.getId()}); + } + } + protected static class UndoSuspendCardMulti extends Undoable { private final Card[] cards; diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerKeyboardInputTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerKeyboardInputTest.java index 15b5280a7508..1cf5198ced69 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerKeyboardInputTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerKeyboardInputTest.java @@ -19,6 +19,7 @@ import android.view.KeyEvent; import com.ichi2.anki.reviewer.ReviewerUi; +import com.ichi2.async.CollectionTask; import com.ichi2.libanki.Card; import com.ichi2.libanki.Collection; @@ -271,7 +272,7 @@ static class KeyboardInputTestReviewer extends Reviewer { private int mAnswerButtonCount = 4; private boolean mEditedCard; private boolean mMarkedCard; - private Collection.DismissType mDismissType; + private CollectionTask.DismissNote mDismissType; private boolean mUndoCalled; private boolean mReplayAudioCalled; private ControlBlock mControlsAreBlocked = ControlBlock.UNBLOCKED; @@ -454,12 +455,12 @@ public boolean getUndoCalled() { } public boolean getSuspendNoteCalled() { - return mDismissType == Collection.DismissType.SUSPEND_NOTE; + return mDismissType instanceof CollectionTask.SuspendNote; } public boolean getBuryNoteCalled() { - return mDismissType == Collection.DismissType.BURY_NOTE; + return mDismissType instanceof CollectionTask.BuryNote; } @@ -473,8 +474,8 @@ public boolean getEditCardCalled() { } @Override - protected void dismiss(Collection.DismissType type) { - this.mDismissType = type; + protected void dismiss(CollectionTask.DismissNote dismiss) { + this.mDismissType = dismiss; } @Override @@ -489,7 +490,7 @@ protected void onMark(Card card) { public boolean getSuspendCardCalled() { - return mDismissType == Collection.DismissType.SUSPEND_CARD; + return mDismissType instanceof CollectionTask.SuspendCard; } From 91f539adf01ff8bee605c86508abe630b8370977 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 31 Mar 2021 02:08:15 +0200 Subject: [PATCH 030/171] NF: class UndoReview In next commit, it will be useful since we'll use Undoable's class for test --- .../java/com/ichi2/libanki/Collection.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java index 83c0999e42f7..636e7688bd62 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java @@ -1383,16 +1383,27 @@ public void markUndo(@NonNull Undoable undo) { } } + private static class UndoReview extends Undoable { + private final boolean mWasLeech; + @NonNull private final Card mClonedCard; + public UndoReview(boolean wasLeech, @NonNull Card clonedCard) { + super(REVIEW); + mClonedCard = clonedCard; + mWasLeech = wasLeech; + } + + @NonNull + @Override + public Card undo(@NonNull Collection col) { + col.getSched().undoReview(mClonedCard, mWasLeech); + return mClonedCard; + } + } + public void markReview(Card card) { boolean wasLeech = card.note().hasTag("leech"); Card clonedCard = card.clone(); - Undoable undoableReview = new Undoable(REVIEW) { - public @Nullable Card undo(@NonNull Collection col) { - col.getSched().undoReview(clonedCard, wasLeech); - return clonedCard; - } - }; - markUndo(undoableReview); + markUndo(new UndoReview(wasLeech, clonedCard)); } /** From 48eaec826892b940182fb6bdee7a53c2d44ea53e Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 31 Mar 2021 02:00:05 +0200 Subject: [PATCH 031/171] NF: remove the enum DismissType It was redundant with Undoable --- .../main/java/com/ichi2/anki/Reviewer.java | 1 - .../java/com/ichi2/async/CollectionTask.java | 49 ++++++++---------- .../java/com/ichi2/libanki/Collection.java | 51 ++++--------------- .../main/java/com/ichi2/libanki/Undoable.java | 25 ++++----- .../test/java/com/ichi2/libanki/UndoTest.java | 4 +- 5 files changed, 47 insertions(+), 83 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index 3991fe872f40..5b9d59aeaffb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -76,7 +76,6 @@ import com.ichi2.compat.CompatHelper; import com.ichi2.libanki.Card; import com.ichi2.libanki.Collection; -import com.ichi2.libanki.Collection.DismissType; import com.ichi2.libanki.Consts; import com.ichi2.libanki.Decks; import com.ichi2.libanki.Utils; diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index e46316b62ea8..4afbcbd19cec 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -87,17 +87,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import timber.log.Timber; import static com.ichi2.async.TaskManager.setLatestInstance; import static com.ichi2.libanki.Card.deepCopyCardArray; -import static com.ichi2.libanki.Collection.DismissType.BURY_CARD; -import static com.ichi2.libanki.Collection.DismissType.BURY_NOTE; -import static com.ichi2.libanki.Collection.DismissType.REPOSITION_CARDS; -import static com.ichi2.libanki.Collection.DismissType.RESCHEDULE_CARDS; -import static com.ichi2.libanki.Collection.DismissType.RESET_CARDS; -import static com.ichi2.libanki.Collection.DismissType.SUSPEND_NOTE; import static com.ichi2.libanki.Undoable.*; import static com.ichi2.utils.BooleanGetter.False; import static com.ichi2.utils.BooleanGetter.True; @@ -437,7 +432,7 @@ private static class UndoSuspendCard extends Undoable { public UndoSuspendCard(Card suspendedCard) { - super(Collection.DismissType.SUSPEND_CARD); + super(R.string.menu_suspend_card); this.suspendedCard = suspendedCard; } @@ -457,7 +452,7 @@ private static class UndoDeleteNote extends Undoable { public UndoDeleteNote(Note note, ArrayList allCs, @NonNull Card card) { - super(Collection.DismissType.DELETE_NOTE); + super(R.string.menu_delete_note); this.note = note; this.allCs = allCs; this.card = card; @@ -514,7 +509,7 @@ public BuryCard(Card card) { @Override protected void actualTask(Collection col, Card card) { // collect undo information - col.markUndo(revertToProvidedState(BURY_CARD, card)); + col.markUndo(revertToProvidedState(R.string.menu_bury_card, card)); // then bury col.getSched().buryCards(new long[] {card.getId()}); } @@ -528,7 +523,7 @@ public BuryNote(Card card) { @Override protected void actualTask(Collection col, Card card) { // collect undo information - col.markUndo(revertToProvidedState(BURY_NOTE, card)); + col.markUndo(revertToProvidedState(R.string.menu_bury_note, card)); // then bury col.getSched().buryNote(card.note().getId()); } @@ -566,7 +561,7 @@ protected void actualTask(Collection col, Card card) { for (int i = 0; i < cards.size(); i++) { cids[i] = cards.get(i).getId(); } - col.markUndo(revertToProvidedState(SUSPEND_NOTE, card)); + col.markUndo(revertToProvidedState(R.string.menu_suspend_note, card)); // suspend note col.getSched().suspendCards(cids); } @@ -597,7 +592,7 @@ protected static class UndoSuspendCardMulti extends Undoable { * otherwise the action was "Unsuspend") */ public UndoSuspendCardMulti(Card[] cards, boolean[] originalSuspended, boolean hasUnsuspended) { - super((hasUnsuspended) ? Collection.DismissType.SUSPEND_CARD_MULTI: Collection.DismissType.UNSUSPEND_CARD_MULTI); + super((hasUnsuspended) ? R.string.menu_suspend_card : R.string.card_browser_unsuspend_card); this.cards = cards; this.originalSuspended = originalSuspended; } @@ -642,7 +637,7 @@ private static class UndoDeleteNoteMulti extends Undoable { public UndoDeleteNoteMulti(Note[] notesArr, List allCards) { - super(Collection.DismissType.DELETE_NOTE_MULTI); + super(R.string.card_browser_delete_card); this.notesArr = notesArr; this.allCards = allCards; } @@ -673,7 +668,7 @@ private static class UndoChangeDeckMulti extends Undoable { public UndoChangeDeckMulti(Card[] cards, long[] originalDids) { - super(Collection.DismissType.CHANGE_DECK_MULTI); + super(R.string.undo_action_change_deck_multi); this.cards = cards; this.originalDids = originalDids; } @@ -702,7 +697,7 @@ private static class UndoMarkNoteMulti extends Undoable { /** @param hasUnmarked whether there were any unmarked card (in which card the action was "mark", * otherwise the action was "Unmark") */ public UndoMarkNoteMulti(List originalMarked, List originalUnmarked, boolean hasUnmarked) { - super((hasUnmarked) ? Collection.DismissType.MARK_NOTE_MULTI : Collection.DismissType.UNMARK_NOTE_MULTI); + super((hasUnmarked) ? R.string.card_browser_mark_card : R.string.card_browser_unmark_card); this.originalMarked = originalMarked; this.originalUnmarked = originalUnmarked; } @@ -721,14 +716,14 @@ private static class UndoRepositionRescheduleResetCards extends Undoable { private final Card[] cards_copied; - public UndoRepositionRescheduleResetCards(Collection.DismissType type, Card[] cards_copied) { - super(type); + public UndoRepositionRescheduleResetCards(@StringRes int undoNameId, Card[] cards_copied) { + super(undoNameId); this.cards_copied = cards_copied; } public @Nullable Card undo(@NonNull Collection col) { - Timber.i("Undoing action of type %s on %d cards", getDismissType(), cards_copied.length); + Timber.i("Undoing action of type %s on %d cards", getClass(), cards_copied.length); for (Card card : cards_copied) { card.flush(false); } @@ -968,22 +963,22 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa } private abstract static class RescheduleRepositionReset extends DismissNotes { - private final Collection.DismissType mType; - public RescheduleRepositionReset(List cardIds, Collection.DismissType type) { + @StringRes private final int mUndoNameId; + public RescheduleRepositionReset(List cardIds, @StringRes int undoNameId) { super(cardIds); - mType = type; + mUndoNameId = undoNameId; } protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { AbstractSched sched = col.getSched(); // collect undo information, sensitive to memory pressure, same for all 3 cases try { - Timber.d("Saving undo information of type %s on %d cards", mType, cards.length); + Timber.d("Saving undo information of type %s on %d cards", getClass(), cards.length); Card[] cards_copied = deepCopyCardArray(cards, collectionTask); - Undoable repositionRescheduleResetCards = new UndoRepositionRescheduleResetCards(mType, cards_copied); + Undoable repositionRescheduleResetCards = new UndoRepositionRescheduleResetCards(mUndoNameId, cards_copied); col.markUndo(repositionRescheduleResetCards); } catch (CancellationException ce) { - Timber.i(ce, "Cancelled while handling type %s, skipping undo", mType); + Timber.i(ce, "Cancelled while handling type %s, skipping undo", mUndoNameId); } actualActualTask(sched); // In all cases schedule a new card so Reviewer doesn't sit on the old one @@ -998,7 +993,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa public static class RescheduleCards extends RescheduleRepositionReset { private final int mSchedule; public RescheduleCards(List cardIds, int schedule) { - super(cardIds, RESCHEDULE_CARDS); + super(cardIds, R.string.card_editor_reschedule_card); this.mSchedule = schedule; } @@ -1011,7 +1006,7 @@ protected void actualActualTask(AbstractSched sched) { public static class RepositionCards extends RescheduleRepositionReset { private final int mPosition; public RepositionCards(List cardIds, int position) { - super(cardIds, REPOSITION_CARDS); + super(cardIds, R.string.card_editor_reposition_card); this.mPosition = position; } @@ -1023,7 +1018,7 @@ protected void actualActualTask(AbstractSched sched) { public static class ResetCards extends RescheduleRepositionReset { public ResetCards(List cardIds) { - super(cardIds, RESET_CARDS); + super(cardIds, R.string.card_editor_reset_card); } @Override diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java index 636e7688bd62..5506407e2dcc 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java @@ -87,7 +87,6 @@ import timber.log.Timber; import static com.ichi2.async.CancelListener.isCancelled; -import static com.ichi2.libanki.Collection.DismissType.REVIEW; import static com.ichi2.libanki.Consts.DECK_DYN; // Anki maintains a cache of used tags so it can quickly present a list of tags @@ -155,39 +154,6 @@ public class Collection { "'curModel': null, " + "'nextPos': 1, " + "'sortType': \"noteFld\", " + "'sortBackwards': False, 'addToCur': True }"; // add new to currently selected deck? - public enum DismissType { - REVIEW(R.string.undo_action_review), - BURY_CARD(R.string.menu_bury_card), - BURY_NOTE(R.string.menu_bury_note), - SUSPEND_CARD(R.string.menu_suspend_card), - SUSPEND_CARD_MULTI(R.string.menu_suspend_card), - UNSUSPEND_CARD_MULTI(R.string.card_browser_unsuspend_card), - SUSPEND_NOTE(R.string.menu_suspend_note), - DELETE_NOTE(R.string.menu_delete_note), - DELETE_NOTE_MULTI(R.string.card_browser_delete_card), - CHANGE_DECK_MULTI(R.string.undo_action_change_deck_multi), - MARK_NOTE_MULTI(R.string.card_browser_mark_card), - UNMARK_NOTE_MULTI(R.string.card_browser_unmark_card), - FLAG(R.string.menu_flag), - REPOSITION_CARDS(R.string.card_editor_reposition_card), - RESCHEDULE_CARDS(R.string.card_editor_reschedule_card), - RESET_CARDS(R.string.card_editor_reset_card); - - @StringRes - private final int mUndoNameId; - - DismissType(int undoNameId) { - this.mUndoNameId = undoNameId; - } - - private Locale getLocale(Resources resources) { - return LanguageUtil.getLocaleCompat(resources); - } - public String getString(Resources res) { - return res.getString(mUndoNameId).toLowerCase(getLocale(res)); - } - } - private static final int UNDO_SIZE_MAX = 20; @VisibleForTesting @@ -1350,16 +1316,16 @@ public void clearUndo() { /** Undo menu item name, or "" if undo unavailable. */ @VisibleForTesting - public @Nullable DismissType undoType() { + public @Nullable Undoable undoType() { if (mUndo.size() > 0) { - return mUndo.getLast().getDismissType(); + return mUndo.getLast(); } return null; } public String undoName(Resources res) { - DismissType type = undoType(); + Undoable type = undoType(); if (type != null) { - return type.getString(res); + return type.name(res); } return ""; } @@ -1371,23 +1337,24 @@ public boolean undoAvailable() { public @Nullable Card undo() { Undoable lastUndo = mUndo.removeLast(); - Timber.d("undo() of type %s", lastUndo.getDismissType()); + Timber.d("undo() of type %s", lastUndo.getClass()); return lastUndo.undo(this); } public void markUndo(@NonNull Undoable undo) { - Timber.d("markUndo() of type %s", undo.getDismissType()); + Timber.d("markUndo() of type %s", undo.getClass()); mUndo.add(undo); while (mUndo.size() > UNDO_SIZE_MAX) { mUndo.removeFirst(); } } - private static class UndoReview extends Undoable { + @VisibleForTesting + public static class UndoReview extends Undoable { private final boolean mWasLeech; @NonNull private final Card mClonedCard; public UndoReview(boolean wasLeech, @NonNull Card clonedCard) { - super(REVIEW); + super(R.string.undo_action_review); mClonedCard = clonedCard; mWasLeech = wasLeech; } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Undoable.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Undoable.java index 1bd4f122921c..81e1d077cf7c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Undoable.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Undoable.java @@ -2,30 +2,31 @@ import android.content.res.Resources; -import com.ichi2.libanki.Collection.DismissType; +import com.ichi2.utils.LanguageUtil; import java.util.List; +import java.util.Locale; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import timber.log.Timber; public abstract class Undoable { - private final DismissType mDt; + @StringRes public final int mUndoNameId; /** * For all descendants, we assume that a card/note/object passed as argument is never going to be changed again. * It's the caller reponsability to clone the object if necessary.*/ - public Undoable(DismissType dt) { - mDt = dt; + public Undoable(@StringRes int undoNameId) { + mUndoNameId = undoNameId; } - public String name(Resources res) { - return mDt.getString(res); + private Locale getLocale(Resources resources) { + return LanguageUtil.getLocaleCompat(resources); } - - public DismissType getDismissType() { - return mDt; + public String name(Resources res) { + return res.getString(mUndoNameId).toLowerCase(getLocale(res)); } /** @@ -34,13 +35,13 @@ public DismissType getDismissType() { * Returned positive integers are card id. Those ids is the card that was discarded and that may be sent back to the reviewer.*/ public abstract @Nullable Card undo(@NonNull Collection col); - public static @NonNull Undoable revertToProvidedState (DismissType dt, Card card){ + public static @NonNull Undoable revertToProvidedState (@StringRes int undoNameId, Card card){ Note note = card.note(); List cards = note.cards(); - return new Undoable(dt) { + return new Undoable(undoNameId) { public @Nullable Card undo(@NonNull Collection col) { - Timber.i("Undo: %s", dt); + Timber.i("Undo: %d", undoNameId); for (Card cc : cards) { cc.flush(false); } diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/UndoTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/UndoTest.java index 9dca433706c3..e11a30b0033b 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/UndoTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/UndoTest.java @@ -12,6 +12,8 @@ import static com.ichi2.libanki.Consts.COUNT_REMAINING; import static com.ichi2.libanki.Consts.QUEUE_TYPE_LRN; import static com.ichi2.libanki.Consts.QUEUE_TYPE_NEW; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -120,7 +122,7 @@ public void test_review() throws Exception { // performing a normal op will clear the review queue c = col.getSched().getCard(); col.getSched().answerCard(c, 3); - assertEquals(Collection.DismissType.REVIEW, col.undoType()); + assertThat(col.undoType(), is(instanceOf(Collection.UndoReview.class))); col.save("foo"); // Upstream, "save" can be undone. This test fails here because it's not the case in AnkiDroid assumeThat(col.undoName(getTargetContext().getResources()), is("foo")); From 20fe1e54d046d9e524ac2feb1a825191e6a11123 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Fri, 2 Apr 2021 17:19:27 +0100 Subject: [PATCH 032/171] Ignore Invalid field naming for wildplot It's not worth worrying about the m variable prefix as we're not going to edit external library code Issue 8387 --- .../main/java/com/wildplot/android/parsing/Atom.java | 11 ++++++++++- .../android/parsing/AtomTypes/FunctionXAtom.java | 9 ++++++++- .../android/parsing/AtomTypes/FunctionXYAtom.java | 9 ++++++++- .../android/parsing/AtomTypes/MathFunctionAtom.java | 3 +++ .../android/parsing/AtomTypes/NumberAtom.java | 3 +++ .../android/parsing/AtomTypes/VariableAtom.java | 3 +++ .../android/parsing/AtomTypes/XVariableAtom.java | 3 +++ .../android/parsing/AtomTypes/YVariableAtom.java | 3 +++ .../java/com/wildplot/android/parsing/Expression.java | 3 +++ .../java/com/wildplot/android/parsing/Factor.java | 3 +++ .../main/java/com/wildplot/android/parsing/Pow.java | 3 +++ .../main/java/com/wildplot/android/parsing/Term.java | 3 +++ .../com/wildplot/android/parsing/TopLevelParser.java | 3 +++ .../java/com/wildplot/android/rendering/BarGraph.java | 3 +++ .../wildplot/android/rendering/DrawableContainer.java | 3 +++ .../wildplot/android/rendering/LegendDrawable.java | 3 +++ .../java/com/wildplot/android/rendering/Lines.java | 9 ++++++++- .../wildplot/android/rendering/MultiScreenPart.java | 3 +++ .../java/com/wildplot/android/rendering/PieChart.java | 3 +++ .../com/wildplot/android/rendering/PlotSheet.java | 7 ++++++- .../java/com/wildplot/android/rendering/XAxis.java | 3 +++ .../java/com/wildplot/android/rendering/XGrid.java | 3 +++ .../java/com/wildplot/android/rendering/YAxis.java | 3 +++ .../java/com/wildplot/android/rendering/YGrid.java | 3 +++ .../android/rendering/graphics/wrapper/ColorWrap.java | 3 +++ .../rendering/graphics/wrapper/FontMetricsWrap.java | 3 +++ .../rendering/graphics/wrapper/GraphicsWrap.java | 2 ++ .../rendering/graphics/wrapper/StrokeWrap.java | 3 +++ 28 files changed, 108 insertions(+), 5 deletions(-) diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Atom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Atom.java index e2801799e591..6bcf1299901e 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Atom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Atom.java @@ -15,8 +15,17 @@ ****************************************************************************************/ package com.wildplot.android.parsing; -import com.wildplot.android.parsing.AtomTypes.*; +import android.annotation.SuppressLint; +import com.wildplot.android.parsing.AtomTypes.FunctionXAtom; +import com.wildplot.android.parsing.AtomTypes.FunctionXYAtom; +import com.wildplot.android.parsing.AtomTypes.MathFunctionAtom; +import com.wildplot.android.parsing.AtomTypes.NumberAtom; +import com.wildplot.android.parsing.AtomTypes.VariableAtom; +import com.wildplot.android.parsing.AtomTypes.XVariableAtom; +import com.wildplot.android.parsing.AtomTypes.YVariableAtom; + +@SuppressLint("FieldNamingPatternDetector") public class Atom implements TreeElement { private final TopLevelParser parser; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXAtom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXAtom.java index 9a77735d0ca8..c82db7bc2a8d 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXAtom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXAtom.java @@ -15,10 +15,17 @@ ****************************************************************************************/ package com.wildplot.android.parsing.AtomTypes; -import com.wildplot.android.parsing.*; +import android.annotation.SuppressLint; + +import com.wildplot.android.parsing.Atom; +import com.wildplot.android.parsing.Expression; +import com.wildplot.android.parsing.ExpressionFormatException; +import com.wildplot.android.parsing.TopLevelParser; +import com.wildplot.android.parsing.TreeElement; import java.util.regex.Pattern; +@SuppressLint("FieldNamingPatternDetector") public class FunctionXAtom implements TreeElement { private Atom.AtomType atomType = Atom.AtomType.FUNCTION_X; private final TopLevelParser parser; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXYAtom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXYAtom.java index 6d082a835762..16ee3636f90a 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXYAtom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/FunctionXYAtom.java @@ -16,13 +16,20 @@ package com.wildplot.android.parsing.AtomTypes; -import com.wildplot.android.parsing.*; +import android.annotation.SuppressLint; + +import com.wildplot.android.parsing.Atom; +import com.wildplot.android.parsing.Expression; +import com.wildplot.android.parsing.ExpressionFormatException; +import com.wildplot.android.parsing.TopLevelParser; +import com.wildplot.android.parsing.TreeElement; import java.util.regex.Pattern; /** * @author Michael Goldbach */ +@SuppressLint("FieldNamingPatternDetector") public class FunctionXYAtom implements TreeElement { private Atom.AtomType atomType = Atom.AtomType.FUNCTION_X; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/MathFunctionAtom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/MathFunctionAtom.java index 95140774c9c9..07fa64205baf 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/MathFunctionAtom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/MathFunctionAtom.java @@ -15,12 +15,15 @@ ****************************************************************************************/ package com.wildplot.android.parsing.AtomTypes; +import android.annotation.SuppressLint; + import com.wildplot.android.parsing.Expression; import com.wildplot.android.parsing.ExpressionFormatException; import com.wildplot.android.parsing.TopLevelParser; import com.wildplot.android.parsing.TreeElement; +@SuppressLint("FieldNamingPatternDetector") public class MathFunctionAtom implements TreeElement { private final TopLevelParser parser; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/NumberAtom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/NumberAtom.java index 84a187e98e00..fff9338c38ff 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/NumberAtom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/NumberAtom.java @@ -15,12 +15,15 @@ ****************************************************************************************/ package com.wildplot.android.parsing.AtomTypes; +import android.annotation.SuppressLint; + import com.wildplot.android.parsing.Atom; import com.wildplot.android.parsing.ExpressionFormatException; import com.wildplot.android.parsing.TreeElement; import timber.log.Timber; +@SuppressLint("FieldNamingPatternDetector") public class NumberAtom implements TreeElement { private Atom.AtomType atomType = Atom.AtomType.NUMBER; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/VariableAtom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/VariableAtom.java index da5a2e24ba48..ee3c84262ced 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/VariableAtom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/VariableAtom.java @@ -15,6 +15,8 @@ ****************************************************************************************/ package com.wildplot.android.parsing.AtomTypes; +import android.annotation.SuppressLint; + import com.wildplot.android.parsing.Atom; import com.wildplot.android.parsing.ExpressionFormatException; import com.wildplot.android.parsing.TopLevelParser; @@ -23,6 +25,7 @@ import java.util.regex.Pattern; +@SuppressLint("FieldNamingPatternDetector") public class VariableAtom implements TreeElement { private Atom.AtomType atomType = Atom.AtomType.NUMBER; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/XVariableAtom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/XVariableAtom.java index 94dfdd8c7994..a584ed1d2525 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/XVariableAtom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/XVariableAtom.java @@ -15,11 +15,14 @@ ****************************************************************************************/ package com.wildplot.android.parsing.AtomTypes; +import android.annotation.SuppressLint; + import com.wildplot.android.parsing.Atom; import com.wildplot.android.parsing.ExpressionFormatException; import com.wildplot.android.parsing.TopLevelParser; import com.wildplot.android.parsing.TreeElement; +@SuppressLint("FieldNamingPatternDetector") public class XVariableAtom implements TreeElement { private final Atom.AtomType atomType = Atom.AtomType.VARIABLE; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/YVariableAtom.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/YVariableAtom.java index 53e66dfeb80c..d89f85486255 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/YVariableAtom.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/AtomTypes/YVariableAtom.java @@ -15,11 +15,14 @@ ****************************************************************************************/ package com.wildplot.android.parsing.AtomTypes; +import android.annotation.SuppressLint; + import com.wildplot.android.parsing.Atom; import com.wildplot.android.parsing.ExpressionFormatException; import com.wildplot.android.parsing.TopLevelParser; import com.wildplot.android.parsing.TreeElement; +@SuppressLint("FieldNamingPatternDetector") public class YVariableAtom implements TreeElement { private final Atom.AtomType atomType = Atom.AtomType.VARIABLE; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Expression.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Expression.java index 2bf47daa4759..e4b0ff8e7388 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Expression.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Expression.java @@ -16,6 +16,9 @@ package com.wildplot.android.parsing; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class Expression implements TreeElement { private final TopLevelParser parser; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Factor.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Factor.java index a4affe98a166..20972d5abdc6 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Factor.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Factor.java @@ -16,6 +16,9 @@ package com.wildplot.android.parsing; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class Factor implements TreeElement { private final TopLevelParser parser; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Pow.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Pow.java index 85430b5ec4d1..2e361a78d987 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Pow.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Pow.java @@ -16,6 +16,9 @@ package com.wildplot.android.parsing; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class Pow implements TreeElement { private final TopLevelParser parser; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Term.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Term.java index de764b78d41d..75fd3a97539e 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/Term.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/Term.java @@ -16,6 +16,9 @@ package com.wildplot.android.parsing; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class Term implements TreeElement { private final TopLevelParser parser; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/parsing/TopLevelParser.java b/AnkiDroid/src/main/java/com/wildplot/android/parsing/TopLevelParser.java index 8b6aafba2b06..59f7635f38ba 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/parsing/TopLevelParser.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/parsing/TopLevelParser.java @@ -15,12 +15,15 @@ ****************************************************************************************/ package com.wildplot.android.parsing; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.interfaces.Function2D; import com.wildplot.android.rendering.interfaces.Function3D; import java.util.HashMap; import java.util.regex.Pattern; +@SuppressLint("FieldNamingPatternDetector") public class TopLevelParser implements Function2D, Function3D, Cloneable { private final HashMap parserRegister; private final HashMap varMap = new HashMap<>(2); // Number form initVarMap diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/BarGraph.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/BarGraph.java index 47997464ef05..f9976abb9a4f 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/BarGraph.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/BarGraph.java @@ -15,6 +15,8 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.ColorWrap; import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap; @@ -25,6 +27,7 @@ /** * BarGraph uses a point matrix or a function to render bar graphs on PlotSheet object */ +@SuppressLint("FieldNamingPatternDetector") public class BarGraph implements Drawable, Legendable { private String mName = ""; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/DrawableContainer.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/DrawableContainer.java index 0222579314b3..c0d1ef84f416 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/DrawableContainer.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/DrawableContainer.java @@ -15,12 +15,15 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; import com.wildplot.android.rendering.interfaces.Drawable; import java.util.Vector; +@SuppressLint("FieldNamingPatternDetector") public class DrawableContainer implements Drawable { private final Vector drawableVector = new Vector<>(); diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/LegendDrawable.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/LegendDrawable.java index b5de44fee045..29722af687f1 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/LegendDrawable.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/LegendDrawable.java @@ -15,12 +15,15 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.ColorWrap; import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; import com.wildplot.android.rendering.interfaces.Drawable; import com.wildplot.android.rendering.interfaces.Legendable; +@SuppressLint("FieldNamingPatternDetector") public class LegendDrawable implements Drawable, Legendable { private String mName = ""; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/Lines.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/Lines.java index 16ceff63d81c..76f79ed41237 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/Lines.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/Lines.java @@ -15,7 +15,13 @@ ****************************************************************************************/ package com.wildplot.android.rendering; -import com.wildplot.android.rendering.graphics.wrapper.*; +import android.annotation.SuppressLint; + +import com.wildplot.android.rendering.graphics.wrapper.BasicStrokeWrap; +import com.wildplot.android.rendering.graphics.wrapper.ColorWrap; +import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; +import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap; +import com.wildplot.android.rendering.graphics.wrapper.StrokeWrap; import com.wildplot.android.rendering.interfaces.Drawable; import com.wildplot.android.rendering.interfaces.Legendable; @@ -24,6 +30,7 @@ * The LinesPoints objects draw points from a data array and connect them with lines on. * These LinesPoints are drawn onto a PlotSheet object */ +@SuppressLint("FieldNamingPatternDetector") public class Lines implements Drawable, Legendable { private boolean mHasShadow = false; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/MultiScreenPart.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/MultiScreenPart.java index b43f3cb5bff4..6d0e45be5b61 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/MultiScreenPart.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/MultiScreenPart.java @@ -15,6 +15,8 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.interfaces.Drawable; import java.util.Vector; @@ -23,6 +25,7 @@ * This class is used to store informations for a certain plot in a multi-plot sheet. * The informations are the drawables for this plotsheet and the x and y limitations */ +@SuppressLint("FieldNamingPatternDetector") public class MultiScreenPart { private final double[] xRange; private final double[] yRange; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/PieChart.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/PieChart.java index f3f13fe82836..2625d8189588 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/PieChart.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/PieChart.java @@ -15,6 +15,8 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.ColorWrap; import com.wildplot.android.rendering.graphics.wrapper.FontMetricsWrap; import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; @@ -24,6 +26,7 @@ import androidx.annotation.NonNull; +@SuppressLint("FieldNamingPatternDetector") public class PieChart implements Drawable, Legendable { // First sector starts at 12 o'clock. private static final float FIRST_SECTOR_OFFSET = -90; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/PlotSheet.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/PlotSheet.java index 5216095e7c25..8c4787270ada 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/PlotSheet.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/PlotSheet.java @@ -16,9 +16,13 @@ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; import android.graphics.Typeface; -import com.wildplot.android.rendering.graphics.wrapper.*; +import com.wildplot.android.rendering.graphics.wrapper.ColorWrap; +import com.wildplot.android.rendering.graphics.wrapper.FontMetricsWrap; +import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; +import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap; import com.wildplot.android.rendering.interfaces.Drawable; import com.wildplot.android.rendering.interfaces.Legendable; @@ -35,6 +39,7 @@ * This is a sheet that is used to plot mathematical functions including coordinate systems and optional extras like * legends and descriptors. Additionally all conversions from image to plot coordinates are done here */ +@SuppressLint("FieldNamingPatternDetector") public class PlotSheet implements Drawable { protected Typeface typeface = Typeface.DEFAULT; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/XAxis.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/XAxis.java index 395f40e8d141..f48837182da7 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/XAxis.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/XAxis.java @@ -16,6 +16,8 @@ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.FontMetricsWrap; import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap; @@ -26,6 +28,7 @@ /** * This Class represents a Drawable x-axis */ +@SuppressLint("FieldNamingPatternDetector") public class XAxis implements Drawable { private boolean isIntegerNumbering = false; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/XGrid.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/XGrid.java index 9b04ac1abc41..a2119ba311f1 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/XGrid.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/XGrid.java @@ -15,6 +15,8 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.ColorWrap; import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap; @@ -24,6 +26,7 @@ /** * This class represents grid lines parallel to the x-axis */ +@SuppressLint("FieldNamingPatternDetector") public class XGrid implements Drawable { /** diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/YAxis.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/YAxis.java index cbb9e6f10c00..af0368054f61 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/YAxis.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/YAxis.java @@ -15,6 +15,8 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.FontMetricsWrap; import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap; @@ -25,6 +27,7 @@ /** * This Class represents a Drawable x-axis */ +@SuppressLint("FieldNamingPatternDetector") public class YAxis implements Drawable { private boolean mHasNumbersRotated = false; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/YGrid.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/YGrid.java index 1e7fe8eae5d5..655c2ea44fa7 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/YGrid.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/YGrid.java @@ -15,6 +15,8 @@ ****************************************************************************************/ package com.wildplot.android.rendering; +import android.annotation.SuppressLint; + import com.wildplot.android.rendering.graphics.wrapper.ColorWrap; import com.wildplot.android.rendering.graphics.wrapper.GraphicsWrap; import com.wildplot.android.rendering.graphics.wrapper.RectangleWrap; @@ -24,6 +26,7 @@ /** * This class represents grid lines parallel to the y-axis */ +@SuppressLint("FieldNamingPatternDetector") public class YGrid implements Drawable { /** diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/ColorWrap.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/ColorWrap.java index c7bdd02797e9..f47636e6d568 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/ColorWrap.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/ColorWrap.java @@ -15,6 +15,9 @@ ****************************************************************************************/ package com.wildplot.android.rendering.graphics.wrapper; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class ColorWrap { //android.graphics.Color private final int colorValue; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/FontMetricsWrap.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/FontMetricsWrap.java index 48d5ff9e540e..e1abdce56c4f 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/FontMetricsWrap.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/FontMetricsWrap.java @@ -15,6 +15,9 @@ ****************************************************************************************/ package com.wildplot.android.rendering.graphics.wrapper; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class FontMetricsWrap { private final GraphicsWrap g; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/GraphicsWrap.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/GraphicsWrap.java index 82daba53df86..b1421e723c7c 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/GraphicsWrap.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/GraphicsWrap.java @@ -15,6 +15,7 @@ ****************************************************************************************/ package com.wildplot.android.rendering.graphics.wrapper; +import android.annotation.SuppressLint; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; @@ -26,6 +27,7 @@ * * @author Michael Goldbach */ +@SuppressLint("FieldNamingPatternDetector") public class GraphicsWrap { private final Canvas canvas; private final Paint paint; diff --git a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/StrokeWrap.java b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/StrokeWrap.java index 86781c77a8f1..ba18d44e1d77 100644 --- a/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/StrokeWrap.java +++ b/AnkiDroid/src/main/java/com/wildplot/android/rendering/graphics/wrapper/StrokeWrap.java @@ -15,6 +15,9 @@ ****************************************************************************************/ package com.wildplot.android.rendering.graphics.wrapper; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class StrokeWrap { private final float strokeSize; From a2eb2093ab29943b07b3452ab96eedc018234298 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 31 Mar 2021 07:00:54 +0200 Subject: [PATCH 033/171] NF: Introduce List assertion This way, it uses same tools as array equality and will show descriptive message about the difference --- .../java/com/ichi2/libanki/ModelTest.java | 109 +++++++++--------- .../java/com/ichi2/testutils/AnkiAssert.java | 3 +- .../test/java/com/ichi2/utils/ListUtil.java | 80 +++++++++++++ 3 files changed, 137 insertions(+), 55 deletions(-) create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/ListUtil.java diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.java index 6441f977d37a..bf7b2588856b 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.java @@ -25,6 +25,7 @@ import static com.ichi2.libanki.Models.REQ_ALL; import static com.ichi2.libanki.Models.REQ_ANY; import static com.ichi2.libanki.Utils.stripHTML; +import static com.ichi2.utils.ListUtil.assertListEquals; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; @@ -199,7 +200,7 @@ public void test_cloze_empty() { Models mm = col.getModels(); Model cloze_model = mm.byName("Cloze"); mm.setCurrent(cloze_model); - assertEquals(new ArrayList<>(Arrays.asList(0, 1)), Models.availOrds(cloze_model, new String[]{"{{c1::Empty}} and {{c2::}}", ""})); + assertListEquals(Arrays.asList(0, 1), Models.availOrds(cloze_model, new String[]{"{{c1::Empty}} and {{c2::}}", ""})); } @@ -482,10 +483,10 @@ public void regression_test_pipe() { @Test public void test_getNamesOfFieldContainingCloze() { - assertEquals(new ArrayList<>(), Models.getNamesOfFieldsContainingCloze("")); + assertListEquals(new ArrayList<>(), Models.getNamesOfFieldsContainingCloze("")); String example = "{{cloze::foo}} <%cloze:bar%>"; - assertEquals(Arrays.asList("foo", "bar"), Models.getNamesOfFieldsContainingCloze(example)); - assertEquals(Arrays.asList("foo", "bar"), Models.getNamesOfFieldsContainingCloze(example)); + assertListEquals(Arrays.asList("foo", "bar"), Models.getNamesOfFieldsContainingCloze(example)); + assertListEquals(Arrays.asList("foo", "bar"), Models.getNamesOfFieldsContainingCloze(example)); } @Test @@ -509,32 +510,32 @@ public void avail_standard_order_test() { Model basic = mm.byName("Basic"); Model reverse = mm.byName("Basic (and reversed card)"); - assertEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", ""})); - assertEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", "Back"})); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(basic, new String[]{"Foo", ""})); - assertEquals(new ArrayList<>(Arrays.asList()), Models._availStandardOrds(basic, new String[]{" \t ", ""})); - assertEquals(new ArrayList<>(), Models._availStandardOrds(reverse, new String[]{"", ""})); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(reverse, new String[]{"Foo", ""})); - assertEquals(new ArrayList<>(Arrays.asList(0, 1)), Models._availStandardOrds(reverse, new String[]{"Foo", "Bar"})); - assertEquals(new ArrayList<>(Arrays.asList(1)), Models._availStandardOrds(reverse, new String[]{" \t ", "Bar"})); - - assertEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", ""}, false) ); - assertEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", "Back"}, false)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(basic, new String[]{"Foo", ""}, false)); - assertEquals(new ArrayList<>(Arrays.asList()), Models._availStandardOrds(basic, new String[]{" \t ", ""}, false)); - assertEquals(new ArrayList<>(), Models._availStandardOrds(reverse, new String[]{"", ""}, false)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(reverse, new String[]{"Foo", ""}, false)); - assertEquals(new ArrayList<>(Arrays.asList(0, 1)), Models._availStandardOrds(reverse, new String[]{"Foo", "Bar"}, false)); - assertEquals(new ArrayList<>(Arrays.asList(1)), Models._availStandardOrds(reverse, new String[]{" \t ", "Bar"}, false)); - - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(basic, new String[]{"", ""}, true) ); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(basic, new String[]{"", "Back"}, true)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(basic, new String[]{"Foo", ""}, true)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(basic, new String[]{" \t ", ""}, true)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(reverse, new String[]{"", ""}, true)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models._availStandardOrds(reverse, new String[]{"Foo", ""}, true)); - assertEquals(new ArrayList<>(Arrays.asList(0, 1)), Models._availStandardOrds(reverse, new String[]{"Foo", "Bar"}, true)); - assertEquals(new ArrayList<>(Arrays.asList(1)), Models._availStandardOrds(reverse, new String[]{" \t ", "Bar"}, true)); + assertListEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", ""})); + assertListEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", "Back"})); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(basic, new String[]{"Foo", ""})); + assertListEquals(Arrays.asList(), Models._availStandardOrds(basic, new String[]{" \t ", ""})); + assertListEquals(new ArrayList<>(), Models._availStandardOrds(reverse, new String[]{"", ""})); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(reverse, new String[]{"Foo", ""})); + assertListEquals(Arrays.asList(0, 1), Models._availStandardOrds(reverse, new String[]{"Foo", "Bar"})); + assertListEquals(Arrays.asList(1), Models._availStandardOrds(reverse, new String[]{" \t ", "Bar"})); + + assertListEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", ""}, false) ); + assertListEquals(new ArrayList<>(), Models._availStandardOrds(basic, new String[]{"", "Back"}, false)); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(basic, new String[]{"Foo", ""}, false)); + assertListEquals(Arrays.asList(), Models._availStandardOrds(basic, new String[]{" \t ", ""}, false)); + assertListEquals(new ArrayList<>(), Models._availStandardOrds(reverse, new String[]{"", ""}, false)); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(reverse, new String[]{"Foo", ""}, false)); + assertListEquals(Arrays.asList(0, 1), Models._availStandardOrds(reverse, new String[]{"Foo", "Bar"}, false)); + assertListEquals(Arrays.asList(1), Models._availStandardOrds(reverse, new String[]{" \t ", "Bar"}, false)); + + assertListEquals(Arrays.asList(0), Models._availStandardOrds(basic, new String[]{"", ""}, true) ); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(basic, new String[]{"", "Back"}, true)); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(basic, new String[]{"Foo", ""}, true)); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(basic, new String[]{" \t ", ""}, true)); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(reverse, new String[]{"", ""}, true)); + assertListEquals(Arrays.asList(0), Models._availStandardOrds(reverse, new String[]{"Foo", ""}, true)); + assertListEquals(Arrays.asList(0, 1), Models._availStandardOrds(reverse, new String[]{"Foo", "Bar"}, true)); + assertListEquals(Arrays.asList(1), Models._availStandardOrds(reverse, new String[]{" \t ", "Bar"}, true)); } @Test @@ -544,33 +545,33 @@ public void avail_ords_test() { Model basic = mm.byName("Basic"); Model reverse = mm.byName("Basic (and reversed card)"); - assertEquals(new ArrayList<>(), Models.availOrds(basic, new String[]{"", ""})); - assertEquals(new ArrayList<>(), Models.availOrds(basic, new String[]{"", "Back"})); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(basic, new String[]{"Foo", ""})); - assertEquals(new ArrayList<>(Arrays.asList()), Models.availOrds(basic, new String[]{" \t ", ""})); - assertEquals(new ArrayList<>(), Models.availOrds(reverse, new String[]{"", ""})); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(reverse, new String[]{"Foo", ""})); - assertEquals(new ArrayList<>(Arrays.asList(0, 1)), Models.availOrds(reverse, new String[]{"Foo", "Bar"})); - assertEquals(new ArrayList<>(Arrays.asList(1)), Models.availOrds(reverse, new String[]{" \t ", "Bar"})); + assertListEquals(new ArrayList<>(), Models.availOrds(basic, new String[]{"", ""})); + assertListEquals(new ArrayList<>(), Models.availOrds(basic, new String[]{"", "Back"})); + assertListEquals(Arrays.asList(0), Models.availOrds(basic, new String[]{"Foo", ""})); + assertListEquals(Arrays.asList(), Models.availOrds(basic, new String[]{" \t ", ""})); + assertListEquals(new ArrayList<>(), Models.availOrds(reverse, new String[]{"", ""})); + assertListEquals(Arrays.asList(0), Models.availOrds(reverse, new String[]{"Foo", ""})); + assertListEquals(Arrays.asList(0, 1), Models.availOrds(reverse, new String[]{"Foo", "Bar"})); + assertListEquals(Arrays.asList(1), Models.availOrds(reverse, new String[]{" \t ", "Bar"})); for (Models.AllowEmpty allow : new Models.AllowEmpty[] {Models.AllowEmpty.ONLY_CLOZE, Models.AllowEmpty.FALSE}) { - assertEquals(new ArrayList<>(), Models.availOrds(basic, new String[] {"", ""}, allow)); - assertEquals(new ArrayList<>(), Models.availOrds(basic, new String[] {"", "Back"}, allow)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(basic, new String[] {"Foo", ""}, allow)); - assertEquals(new ArrayList<>(Arrays.asList()), Models.availOrds(basic, new String[] {" \t ", ""}, allow)); - assertEquals(new ArrayList<>(), Models.availOrds(reverse, new String[] {"", ""}, allow)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(reverse, new String[] {"Foo", ""}, allow)); - assertEquals(new ArrayList<>(Arrays.asList(0, 1)), Models.availOrds(reverse, new String[] {"Foo", "Bar"}, allow)); - assertEquals(new ArrayList<>(Arrays.asList(1)), Models.availOrds(reverse, new String[] {" \t ", "Bar"}, allow)); + assertListEquals(new ArrayList<>(), Models.availOrds(basic, new String[] {"", ""}, allow)); + assertListEquals(new ArrayList<>(), Models.availOrds(basic, new String[] {"", "Back"}, allow)); + assertListEquals(Arrays.asList(0), Models.availOrds(basic, new String[] {"Foo", ""}, allow)); + assertListEquals(Arrays.asList(), Models.availOrds(basic, new String[] {" \t ", ""}, allow)); + assertListEquals(new ArrayList<>(), Models.availOrds(reverse, new String[] {"", ""}, allow)); + assertListEquals(Arrays.asList(0), Models.availOrds(reverse, new String[] {"Foo", ""}, allow)); + assertListEquals(Arrays.asList(0, 1), Models.availOrds(reverse, new String[] {"Foo", "Bar"}, allow)); + assertListEquals(Arrays.asList(1), Models.availOrds(reverse, new String[] {" \t ", "Bar"}, allow)); } - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(basic, new String[]{"", ""}, Models.AllowEmpty.TRUE)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(basic, new String[]{"", "Back"}, Models.AllowEmpty.TRUE)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(basic, new String[]{"Foo", ""}, Models.AllowEmpty.TRUE)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(basic, new String[]{" \t ", ""}, Models.AllowEmpty.TRUE)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(reverse, new String[]{"", ""}, Models.AllowEmpty.TRUE)); - assertEquals(new ArrayList<>(Arrays.asList(0)), Models.availOrds(reverse, new String[]{"Foo", ""}, Models.AllowEmpty.TRUE)); - assertEquals(new ArrayList<>(Arrays.asList(0, 1)), Models.availOrds(reverse, new String[]{"Foo", "Bar"}, Models.AllowEmpty.TRUE)); - assertEquals(new ArrayList<>(Arrays.asList(1)), Models.availOrds(reverse, new String[]{" \t ", "Bar"}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(0), Models.availOrds(basic, new String[]{"", ""}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(0), Models.availOrds(basic, new String[]{"", "Back"}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(0), Models.availOrds(basic, new String[]{"Foo", ""}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(0), Models.availOrds(basic, new String[]{" \t ", ""}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(0), Models.availOrds(reverse, new String[]{"", ""}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(0), Models.availOrds(reverse, new String[]{"Foo", ""}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(0, 1), Models.availOrds(reverse, new String[]{"Foo", "Bar"}, Models.AllowEmpty.TRUE)); + assertListEquals(Arrays.asList(1), Models.availOrds(reverse, new String[]{" \t ", "Bar"}, Models.AllowEmpty.TRUE)); } } diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java b/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java index 941ebd949533..fa8d1b16270e 100644 --- a/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/AnkiAssert.java @@ -10,6 +10,7 @@ import androidx.annotation.NonNull; +import static com.ichi2.utils.ListUtil.assertListEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -47,7 +48,7 @@ public static T assertThrows(Runnable r, Class clazz) { } public static void assertEqualsArrayList(T[] expected, List actual) { - assertEquals(Arrays.asList(expected), actual); + assertListEquals(Arrays.asList(expected), actual); } diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/ListUtil.java b/AnkiDroid/src/test/java/com/ichi2/utils/ListUtil.java new file mode 100644 index 000000000000..97efdaf36dcc --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/ListUtil.java @@ -0,0 +1,80 @@ +/**************************************************************************************** + * Copyright (c) 2021 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 . * + ****************************************************************************************/ +package com.ichi2.utils; + +import org.junit.Test; +import org.junit.internal.ArrayComparisonFailure; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThrows; + +public class ListUtil { + + + /** + * Asserts that two object lists are equal (same size and components in same order). If they are not, an + * {@link AssertionError} is thrown with the given message. It states "array" instead of list If + * expecteds and actuals are null, + * they are considered equal. + * + * @param message the identifying message for the {@link AssertionError} (null + * okay) + * @param expecteds Object list or list of arrays (multi-dimensional array) with + * expected values. + * @param actuals Object list or list of arrays (multi-dimensional array) with + * actual values + */ + public static void assertListEquals(String message, List expecteds, + List actuals) throws ArrayComparisonFailure { + Object[] expecteds_array = (expecteds == null) ? null : expecteds.toArray(); + Object[] actuals_array = (actuals == null) ? null : actuals.toArray(); + assertArrayEquals(message, expecteds_array, actuals_array); + } + + /** + * Asserts that two object arrays are equal. If they are not, an + * {@link AssertionError} is thrown. If expected and + * actual are null, they are considered + * equal. + * + * @param expecteds Object list or list of arrays (multi-dimensional array) with + * expected values + * @param actuals Object list or list of arrays (multi-dimensional array) with + * actual values + */ + public static void assertListEquals(List expecteds, + List actuals) { + assertListEquals(null, expecteds, actuals); + } + + @Test + public void EqualsTest() { + assertThrows(ArrayComparisonFailure.class, () -> assertListEquals(Arrays.asList(2L, 3L), Arrays.asList(2L, 4L))); + assertThrows(ArrayComparisonFailure.class, () -> assertListEquals(Arrays.asList(2L, 3L), Arrays.asList(2L))); + assertThrows(ArrayComparisonFailure.class, () -> assertListEquals(Arrays.asList(2L, 3L), Arrays.asList(5L))); + assertThrows(ArrayComparisonFailure.class, () -> assertListEquals(Arrays.asList(2L, 3L), Arrays.asList(2L, 3L, 5L))); + assertThrows(AssertionError.class, () -> assertListEquals(Arrays.asList(2L, 3L), null)); + assertThrows(AssertionError.class, () -> assertListEquals(null, Arrays.asList(2L, 4L))); + assertListEquals(null, null); + assertListEquals(Arrays.asList(2L, 3L), Arrays.asList(2L, 3L)); + } + +} From 38134343a85b1dc9446f6fbbcbfbad404478996c Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 13 Jan 2021 20:33:00 +0100 Subject: [PATCH 034/171] NF: moving waitForAllToFinish to TaskManager --- .../main/java/com/ichi2/anki/DeckPicker.java | 4 +-- .../java/com/ichi2/async/CollectionTask.java | 21 ---------------- .../java/com/ichi2/async/TaskManager.java | 25 +++++++++++++++++++ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 093928c1ea51..86b5c380e62d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -124,7 +124,6 @@ import com.ichi2.libanki.Utils; import com.ichi2.libanki.importer.AnkiPackageImporter; import com.ichi2.libanki.sched.AbstractDeckTreeNode; -import com.ichi2.libanki.sched.DeckTreeNode; import com.ichi2.libanki.sync.CustomSyncServerUrlException; import com.ichi2.libanki.sync.Syncer; import com.ichi2.libanki.utils.TimeUtils; @@ -145,7 +144,6 @@ import java.io.File; import java.io.FileOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -862,7 +860,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent intent) } } else if (resultCode == Reviewer.RESULT_ABORT_AND_SYNC) { Timber.i("Obtained Abort and Sync result"); - CollectionTask.waitForAllToFinish(4); + TaskManager.waitForAllToFinish(4); sync(); } } else if (requestCode == REQUEST_PATH_UPDATE) { diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index 4afbcbd19cec..d687273ae8f0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -111,27 +111,6 @@ public abstract static class Task { */ private Context mContext; - /** - * Block the current thread until all CollectionTasks have finished. - * @param timeoutSeconds timeout in seconds - * @return whether all tasks exited successfully - */ - @SuppressWarnings("UnusedReturnValue") - public static boolean waitForAllToFinish(Integer timeoutSeconds) { - // HACK: This should be better - there is currently a race condition in sLatestInstance, and no means to obtain this information. - // This should work in all reasonable cases given how few tasks we have concurrently blocking. - boolean result; - result = TaskManager.waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - result &= TaskManager.waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - result &= TaskManager.waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - result &= TaskManager.waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - Timber.i("Waited for all tasks to finish"); - return result; - } /** Cancel the current task. * @return whether cancelling did occur.*/ diff --git a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java index 240a90fb3f76..f32a3d4f2f59 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java @@ -3,6 +3,8 @@ import android.content.res.Resources; import android.os.AsyncTask; +import com.ichi2.utils.ThreadUtil; + import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -141,6 +143,29 @@ public static ProgressCallback progressCallback(CollectionTask task, Resources r } + /** + * Block the current thread until all CollectionTasks have finished. + * @param timeoutSeconds timeout in seconds + * @return whether all tasks exited successfully + */ + @SuppressWarnings("UnusedReturnValue") + public static boolean waitForAllToFinish(Integer timeoutSeconds) { + // HACK: This should be better - there is currently a race condition in sLatestInstance, and no means to obtain this information. + // This should work in all reasonable cases given how few tasks we have concurrently blocking. + boolean result; + result = waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + result &= waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + result &= waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + result &= waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + Timber.i("Waited for all tasks to finish"); + return result; + } + + /** * Helper class for allowing inner function to publish progress of an AsyncTask. */ From ff4c760d5a48423623b910cca37524ce21d39b17 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 13 Jan 2021 20:54:32 +0100 Subject: [PATCH 035/171] NF: move addTask entirely in TaskManager The manager is supposde to know when a task is added, and deal with it itself --- AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java | 1 - AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index d687273ae8f0..32a2d81ec904 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -150,7 +150,6 @@ protected CollectionTask(Task task, TaskLi mTask = task; mListener = listener; mPreviousTask = previousTask; - TaskManager.addTask(this); } @Override diff --git a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java index f32a3d4f2f59..a08a97d6b1f7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java @@ -74,6 +74,7 @@ public static CollectionTask listener) { // Start new task CollectionTask newTask = new CollectionTask<>(task, listener, sLatestInstance); + addTask(newTask); newTask.execute(); return newTask; } From 94aa0358e1da6a33f0f02519f4105db20dd2d249 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Tue, 30 Mar 2021 05:35:50 +0200 Subject: [PATCH 036/171] NF: SingleTaskManager Spliting TaskManager into an abstract and a concrete part allow to change no call while still allowing to switch implementation. --- .../com/ichi2/async/SingleTaskManager.java | 166 ++++++++++++++++++ .../java/com/ichi2/async/TaskManager.java | 136 ++++++-------- 2 files changed, 216 insertions(+), 86 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/async/SingleTaskManager.java diff --git a/AnkiDroid/src/main/java/com/ichi2/async/SingleTaskManager.java b/AnkiDroid/src/main/java/com/ichi2/async/SingleTaskManager.java new file mode 100644 index 000000000000..2183ffe6e4ce --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/async/SingleTaskManager.java @@ -0,0 +1,166 @@ +package com.ichi2.async; + +import android.os.AsyncTask; + +import com.ichi2.utils.ThreadUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import timber.log.Timber; + +public class SingleTaskManager extends TaskManager { + + /** + * Tasks which are running or waiting to run. + * */ + private final List mTasks = Collections.synchronizedList(new LinkedList<>()); + + private void addTasks(CollectionTask task) { + mTasks.add(task); + } + + @Override + protected boolean removeTaskConcrete(CollectionTask task) { + return mTasks.remove(task); + } + + /** + * The most recently started {@link CollectionTask} instance. + */ + private CollectionTask mLatestInstance; + + protected void setLatestInstanceConcrete(CollectionTask task) { + mLatestInstance = task; + } + + + + + /** + * Starts a new {@link CollectionTask}, with no listener + *

+ * Tasks will be executed serially, in the order in which they are started. + *

+ * This method must be called on the main thread. + * + * @param task the task to execute + * @return the newly created task + */ + @Override + public CollectionTask launchCollectionTaskConcrete(CollectionTask.Task task) { + return launchCollectionTask(task, null); + } + + + + /** + * Starts a new {@link CollectionTask}, with a listener provided for callbacks during execution + *

+ * Tasks will be executed serially, in the order in which they are started. + *

+ * This method must be called on the main thread. + * + * @param task the task to execute + * @param listener to the status and result of the task, may be null + * @return the newly created task + */ + public CollectionTask + launchCollectionTaskConcrete(@NonNull CollectionTask.Task task, + @Nullable TaskListener listener) { + // Start new task + CollectionTask newTask = new CollectionTask<>(task, listener, mLatestInstance); + addTasks(newTask); + newTask.execute(); + return newTask; + } + + + /** + * Block the current thread until the currently running CollectionTask instance (if any) has finished. + */ + public void waitToFinishConcrete() { + waitToFinish(null); + } + + /** + * Block the current thread until the currently running CollectionTask instance (if any) has finished. + * @param timeoutSeconds timeout in seconds + * @return whether or not the previous task was successful or not + */ + @Override + public boolean waitToFinishConcrete(Integer timeoutSeconds) { + try { + if ((mLatestInstance != null) && (mLatestInstance.getStatus() != AsyncTask.Status.FINISHED)) { + Timber.d("CollectionTask: waiting for task %s to finish...", mLatestInstance.getTask().getClass()); + if (timeoutSeconds != null) { + mLatestInstance.get(timeoutSeconds, TimeUnit.SECONDS); + } else { + mLatestInstance.get(); + } + + } + return true; + } catch (Exception e) { + Timber.e(e, "Exception waiting for task to finish"); + return false; + } + } + + + /** Cancel the current task only if it's of type taskType */ + @Override + public void cancelCurrentlyExecutingTaskConcrete() { + CollectionTask latestInstance = mLatestInstance; + if (latestInstance != null) { + if (latestInstance.safeCancel()) { + Timber.i("Cancelled task %s", latestInstance.getTask().getClass()); + } + } + } + + /** Cancel all tasks of type taskType*/ + public void cancelAllTasksConcrete(Class taskType) { + int count = 0; + // safeCancel modifies mTasks, so iterate over a concrete copy + for (CollectionTask task: new ArrayList<>(mTasks)) { + if (task.getTask().getClass() != taskType) { + continue; + } + if (task.safeCancel()) { + count++; + } + } + if (count > 0) { + Timber.i("Cancelled %d instances of task %s", count, taskType); + } + } + + /** + * Block the current thread until all CollectionTasks have finished. + * @param timeoutSeconds timeout in seconds + * @return whether all tasks exited successfully + */ + @SuppressWarnings("UnusedReturnValue") + @Override + public boolean waitForAllToFinishConcrete(Integer timeoutSeconds) { + // HACK: This should be better - there is currently a race condition in sLatestInstance, and no means to obtain this information. + // This should work in all reasonable cases given how few tasks we have concurrently blocking. + boolean result; + result = waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + result &= waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + result &= waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + result &= waitToFinish(timeoutSeconds / 4); + ThreadUtil.sleep(10); + Timber.i("Waited for all tasks to finish"); + return result; + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java index a08a97d6b1f7..faf215e66d96 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java @@ -3,6 +3,7 @@ import android.content.res.Resources; import android.os.AsyncTask; +import com.ichi2.libanki.Collection; import com.ichi2.utils.ThreadUtil; import java.util.ArrayList; @@ -13,34 +14,28 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import timber.log.Timber; -public class TaskManager { +public abstract class TaskManager { + @NonNull private static TaskManager sTaskManager = new SingleTaskManager(); /** - * Tasks which are running or waiting to run. - * */ - private static final List sTasks = Collections.synchronizedList(new LinkedList<>()); - - protected static void addTask(CollectionTask task) { - sTasks.add(task); + * @param tm The new task manager + * @return The previous one. It may still have tasks running + */ + @VisibleForTesting + public static TaskManager setTaskManager(TaskManager tm) { + TaskManager previous = sTaskManager; + sTaskManager = tm; + return previous; } protected static boolean removeTask(CollectionTask task) { - return sTasks.remove(task); - } - - - /** - * The most recently started {@link CollectionTask} instance. - */ - private static CollectionTask sLatestInstance; - - protected static void setLatestInstance(CollectionTask task) { - sLatestInstance = task; + return sTaskManager.removeTaskConcrete(task); } - + protected abstract boolean removeTaskConcrete(CollectionTask task); /** * Starts a new {@link CollectionTask}, with no listener @@ -52,12 +47,19 @@ protected static void setLatestInstance(CollectionTask task) { * @param task the task to execute * @return the newly created task */ + protected static void setLatestInstance(CollectionTask task) { + sTaskManager.setLatestInstanceConcrete(task); + } + public static CollectionTask launchCollectionTask(CollectionTask.Task task) { - return launchCollectionTask(task, null); + return sTaskManager.launchCollectionTaskConcrete(task); } + public abstract CollectionTask launchCollectionTaskConcrete(CollectionTask.Task task); + protected abstract void setLatestInstanceConcrete(CollectionTask task); + /** * Starts a new {@link CollectionTask}, with a listener provided for callbacks during execution *

@@ -70,22 +72,22 @@ public static CollectionTask CollectionTask - launchCollectionTask(@NonNull CollectionTask.Task task, - @Nullable TaskListener listener) { - // Start new task - CollectionTask newTask = new CollectionTask<>(task, listener, sLatestInstance); - addTask(newTask); - newTask.execute(); - return newTask; + launchCollectionTask(@NonNull CollectionTask.Task task, + @Nullable TaskListener listener) { + return sTaskManager.launchCollectionTaskConcrete(task, listener); } + public abstract CollectionTask + launchCollectionTaskConcrete(@NonNull CollectionTask.Task task, + @Nullable TaskListener listener); /** * Block the current thread until the currently running CollectionTask instance (if any) has finished. */ public static void waitToFinish() { - waitToFinish(null); - } + sTaskManager.waitToFinishConcrete(); + }; + public abstract void waitToFinishConcrete(); /** * Block the current thread until the currently running CollectionTask instance (if any) has finished. @@ -93,56 +95,22 @@ public static void waitToFinish() { * @return whether or not the previous task was successful or not */ public static boolean waitToFinish(Integer timeoutSeconds) { - try { - if ((sLatestInstance != null) && (sLatestInstance.getStatus() != AsyncTask.Status.FINISHED)) { - Timber.d("CollectionTask: waiting for task %s to finish...", sLatestInstance.getTask().getClass()); - if (timeoutSeconds != null) { - sLatestInstance.get(timeoutSeconds, TimeUnit.SECONDS); - } else { - sLatestInstance.get(); - } - - } - return true; - } catch (Exception e) { - Timber.e(e, "Exception waiting for task to finish"); - return false; - } - } + return sTaskManager.waitToFinishConcrete(timeoutSeconds); + }; + public abstract boolean waitToFinishConcrete(Integer timeoutSeconds); /** Cancel the current task only if it's of type taskType */ public static void cancelCurrentlyExecutingTask() { - CollectionTask latestInstance = sLatestInstance; - if (latestInstance != null) { - if (latestInstance.safeCancel()) { - Timber.i("Cancelled task %s", latestInstance.getTask().getClass()); - } - } + sTaskManager.cancelCurrentlyExecutingTaskConcrete(); } + public abstract void cancelCurrentlyExecutingTaskConcrete(); /** Cancel all tasks of type taskType*/ public static void cancelAllTasks(Class taskType) { - int count = 0; - // safeCancel modifies sTasks, so iterate over a concrete copy - for (CollectionTask task: new ArrayList<>(sTasks)) { - if (task.getTask().getClass() != taskType) { - continue; - } - if (task.safeCancel()) { - count++; - } - } - if (count > 0) { - Timber.i("Cancelled %d instances of task %s", count, taskType); - } - } - - - public static ProgressCallback progressCallback(CollectionTask task, Resources res) { - return new ProgressCallback(task, res); + sTaskManager.cancelAllTasksConcrete(taskType); } - + public abstract void cancelAllTasksConcrete(Class taskType); /** * Block the current thread until all CollectionTasks have finished. @@ -151,25 +119,16 @@ public static ProgressCallback progressCallback(CollectionTask task, Resources r */ @SuppressWarnings("UnusedReturnValue") public static boolean waitForAllToFinish(Integer timeoutSeconds) { - // HACK: This should be better - there is currently a race condition in sLatestInstance, and no means to obtain this information. - // This should work in all reasonable cases given how few tasks we have concurrently blocking. - boolean result; - result = waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - result &= waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - result &= waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - result &= waitToFinish(timeoutSeconds / 4); - ThreadUtil.sleep(10); - Timber.i("Waited for all tasks to finish"); - return result; + return sTaskManager.waitToFinishConcrete(timeoutSeconds); } + public abstract boolean waitForAllToFinishConcrete(Integer timeoutSeconds); - /** - * Helper class for allowing inner function to publish progress of an AsyncTask. - */ + + + /** + * Helper class for allowing inner function to publish progress of an AsyncTask. + */ public static class ProgressCallback { private final Resources res; private final ProgressSender task; @@ -197,4 +156,9 @@ public void publishProgress(Progress value) { } } + + public static ProgressCallback progressCallback(CollectionTask task, Resources res) { + return new ProgressCallback(task, res); + } + } From e90c2dd64066325c52fa13154a9c934927c49fd7 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Thu, 14 Jan 2021 11:31:21 +0100 Subject: [PATCH 037/171] NF: introduce CollectionGetter interface --- .../java/com/ichi2/libanki/Collection.java | 11 ++- .../com/ichi2/libanki/CollectionGetter.java | 5 ++ .../java/com/ichi2/anki/RobolectricTest.java | 7 +- .../ichi2/async/ForegroundTaskManager.java | 69 +++++++++++++++++++ 4 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/libanki/CollectionGetter.java create mode 100644 AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java index 5506407e2dcc..c9c0c01d57a2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java @@ -98,7 +98,7 @@ @SuppressWarnings({"PMD.ExcessiveClassLength", "PMD.AvoidThrowingRawExceptionTypes","PMD.AvoidReassigningParameters", "PMD.NPathComplexity","PMD.MethodNamingConventions","PMD.AvoidBranchingStatementAsLastInLoop", "PMD.SwitchStmtsShouldHaveDefault","PMD.CollapsibleIfStatements","PMD.EmptyIfStmt","PMD.ExcessiveMethodLength"}) -public class Collection { +public class Collection implements CollectionGetter { private final Context mContext; @@ -2265,4 +2265,13 @@ public boolean getFailed() { public Time getTime() { return mTime; } + + + /** + * Allows a collection to be used as a CollectionGetter + * @return Itself. + */ + public Collection getCol() { + return this; + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/CollectionGetter.java b/AnkiDroid/src/main/java/com/ichi2/libanki/CollectionGetter.java new file mode 100644 index 000000000000..609f3ccd3e5d --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/CollectionGetter.java @@ -0,0 +1,5 @@ +package com.ichi2.libanki; + +public interface CollectionGetter { + Collection getCol(); +} diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java index 334d90a87558..06b9e8bfe786 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java @@ -31,6 +31,7 @@ import com.ichi2.compat.customtabs.CustomTabActivityHelper; import com.ichi2.libanki.Card; import com.ichi2.libanki.Collection; +import com.ichi2.libanki.CollectionGetter; import com.ichi2.libanki.Consts; import com.ichi2.libanki.DB; import com.ichi2.libanki.Model; @@ -77,7 +78,9 @@ import static org.hamcrest.Matchers.is; import static org.robolectric.Shadows.shadowOf; -public class RobolectricTest { +public class RobolectricTest implements CollectionGetter { + + private static boolean mBackground = true; private final ArrayList> controllersForCleanup = new ArrayList<>(); @@ -243,7 +246,7 @@ protected String getQuantityString(int res, int quantity, Object... formatArgs) /** A collection. Created one second ago, not near cutoff time. * Each time time is checked, it advance by 10 ms. Not enough to create any change visible to user, but ensure * we don't get two equal time.*/ - protected Collection getCol() { + public Collection getCol() { MockTime time = new MockTime(2020, 7, 7, 7, 0, 0, 0, 10); return CollectionHelper.getInstance().getCol(getTargetContext(), time); } diff --git a/AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java b/AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java new file mode 100644 index 000000000000..cbf4a808899c --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java @@ -0,0 +1,69 @@ +package com.ichi2.async; + +import com.ichi2.libanki.CollectionGetter; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class ForegroundTaskManager extends TaskManager { + private final CollectionGetter mColGetter; + + + public ForegroundTaskManager(CollectionGetter colGetter) { + mColGetter = colGetter; + } + + + @Override + protected boolean removeTask(CollectionTask task) { + return true; + } + + + @Override + protected void setLatestInstance(CollectionTask task) { + } + + + @Override + public CollectionTask launchCollectionTask( + @NonNull CollectionTask.Task task, + @Nullable TaskListener listener) { + CollectionTask ct = + new CollectionTask<>(task, listener, null); + ct.execute(); + try { + ct.wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return ct; + } + + + @Override + public void waitToFinish() { + } + + + @Override + public boolean waitToFinish(Integer timeoutSeconds) { + return true; + } + + + @Override + public void cancelCurrentlyExecutingTask() { + } + + + @Override + public void cancelAllTasks(Class taskType) { + } + + + @Override + public boolean waitForAllToFinish(Integer timeoutSeconds) { + return true; + } +} From c8a04e45c5dc729c738664b63a885ee62174d63d Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Wed, 13 Jan 2021 22:51:07 +0100 Subject: [PATCH 038/171] NF: Allow tests to execute everything in foreground --- .../java/com/ichi2/anki/RobolectricTest.java | 29 ++++++++ .../ichi2/async/ForegroundTaskManager.java | 74 +++++++++++++++---- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java index 06b9e8bfe786..0140f12f2046 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java @@ -26,6 +26,8 @@ import com.ichi2.anki.exception.ConfirmModSchemaException; import com.ichi2.anki.exception.FilteredAncestor; import com.ichi2.async.CollectionTask; +import com.ichi2.async.ForegroundTaskManager; +import com.ichi2.async.SingleTaskManager; import com.ichi2.async.TaskListener; import com.ichi2.async.TaskManager; import com.ichi2.compat.customtabs.CustomTabActivityHelper; @@ -170,10 +172,31 @@ public void tearDown() { //called on each AnkiDroidApp.onCreate(), and spams the build //there is no onDestroy(), so call it here. Timber.uprootAll(); + + runtTasksInBackground(); } } + /** + * Ensure that each task in backgrounds are executed immediately instead of being queued. + * This may help debugging test without requiring to guess where `advanceRobolectricLooper` are needed. + */ + public void runTasksInForeground() { + TaskManager.setTaskManager(new ForegroundTaskManager(this)); + mBackground = false; + } + + + /** + * Set back the standard background process + */ + public void runtTasksInBackground() { + TaskManager.setTaskManager(new SingleTaskManager()); + mBackground = true; + } + + protected void clickDialogButton(DialogAction button, boolean checkDismissed) { MaterialDialog dialog = (MaterialDialog)ShadowDialog.getLatestDialog(); dialog.getActionButton(button).performClick(); @@ -203,12 +226,18 @@ protected String getDialogText(boolean checkDismissed) { // Robolectric needs a manual advance with the new PAUSED looper mode public static void advanceRobolectricLooper() { + if (!mBackground) { + return; + } shadowOf(getMainLooper()).runToEndOfTasks(); shadowOf(getMainLooper()).idle(); shadowOf(getMainLooper()).runToEndOfTasks(); } // Robolectric needs some help sometimes in form of a manual kick, then a wait, to stabilize UI activity public static void advanceRobolectricLooperWithSleep() { + if (!mBackground) { + return; + } advanceRobolectricLooper(); try { Thread.sleep(500); } catch (Exception e) { Timber.e(e); } advanceRobolectricLooper(); diff --git a/AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java b/AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java index cbf4a808899c..afe3e7819cd5 100644 --- a/AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java +++ b/AnkiDroid/src/test/java/com/ichi2/async/ForegroundTaskManager.java @@ -1,9 +1,11 @@ package com.ichi2.async; import com.ichi2.libanki.CollectionGetter; +import com.ichi2.libanki.Collection; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import timber.log.Timber; public class ForegroundTaskManager extends TaskManager { private final CollectionGetter mColGetter; @@ -13,57 +15,97 @@ public ForegroundTaskManager(CollectionGetter colGetter) { mColGetter = colGetter; } - @Override - protected boolean removeTask(CollectionTask task) { + protected boolean removeTaskConcrete(CollectionTask task) { return true; } @Override - protected void setLatestInstance(CollectionTask task) { + public CollectionTask launchCollectionTaskConcrete(CollectionTask.Task task) { + return launchCollectionTaskConcrete(task, null); + } + + + @Override + protected void setLatestInstanceConcrete(CollectionTask task) { } @Override - public CollectionTask launchCollectionTask( + public CollectionTask launchCollectionTaskConcrete( @NonNull CollectionTask.Task task, @Nullable TaskListener listener) { - CollectionTask ct = - new CollectionTask<>(task, listener, null); - ct.execute(); + if (listener != null) { + listener.onPreExecute(); + } + final ResultBackground res; try { - ct.wait(); - } catch (InterruptedException e) { - throw new RuntimeException(e); + res = task.task(mColGetter.getCol(), new MockTaskManager<>(listener)); + } catch (Exception e) { + Timber.w(e, "A new failure may have something to do with running in the foreground."); + throw e; } - return ct; + if (listener != null) { + listener.onPostExecute(res); + } + return new EmptyTask<>(task, listener); } @Override - public void waitToFinish() { + public void waitToFinishConcrete() { } @Override - public boolean waitToFinish(Integer timeoutSeconds) { + public boolean waitToFinishConcrete(Integer timeoutSeconds) { return true; } @Override - public void cancelCurrentlyExecutingTask() { + public void cancelCurrentlyExecutingTaskConcrete() { } @Override - public void cancelAllTasks(Class taskType) { + public void cancelAllTasksConcrete(Class taskType) { } @Override - public boolean waitForAllToFinish(Integer timeoutSeconds) { + public boolean waitForAllToFinishConcrete(Integer timeoutSeconds) { return true; } + + public class MockTaskManager implements ProgressSenderAndCancelListener { + + private final @Nullable TaskListener mTaskListener; + + + public MockTaskManager(@Nullable TaskListener listener) { + mTaskListener = listener; + } + + + @Override + public boolean isCancelled() { + return false; + } + + + @Override + public void doProgress(@Nullable ProgressBackground value) { + mTaskListener.onProgressUpdate(value); + } + } + + public class EmptyTask extends + CollectionTask { + + protected EmptyTask(Task task, TaskListener listener) { + super(task, listener, null); + } + } } From 81097606651a52c2e0a4e4888a96a2ffb1f92433 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 3 Apr 2021 04:53:27 +0200 Subject: [PATCH 039/171] NF: correct a typo --- AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java index 0140f12f2046..bf2d0cbe3f6e 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/RobolectricTest.java @@ -67,6 +67,7 @@ import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.ichi2.utils.InMemorySQLiteOpenHelperFactory; import androidx.fragment.app.DialogFragment; @@ -173,7 +174,7 @@ public void tearDown() { //there is no onDestroy(), so call it here. Timber.uprootAll(); - runtTasksInBackground(); + runTasksInBackground(); } } @@ -191,7 +192,7 @@ public void runTasksInForeground() { /** * Set back the standard background process */ - public void runtTasksInBackground() { + public void runTasksInBackground() { TaskManager.setTaskManager(new SingleTaskManager()); mBackground = true; } From a58b5b4921176c6904a9b377823dbffb68a788e7 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 3 Apr 2021 08:44:46 +0200 Subject: [PATCH 040/171] NF: use list testing function in UniqueArrayListTest --- .../com/ichi2/utils/UniqueArrayListTest.java | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java index 7df32441f759..a18fb9e9c9d8 100644 --- a/AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/utils/UniqueArrayListTest.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.List; +import static com.ichi2.utils.ListUtil.assertListEquals; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -62,10 +63,6 @@ public class UniqueArrayListTest { "sd" ); - private void assertSameLists(List a, List b){ - assertThat(b, IsIterableContainingInOrder.contains(a.toArray())); - } - private void assertNotSameLists(List a, List b){ assertThat(b, not(IsIterableContainingInOrder.contains(a.toArray()))); } @@ -85,7 +82,7 @@ public void test_Sorting() { UniqueArrayList uniqueList = UniqueArrayList.from(longs); Collections.sort(longs); uniqueList.sort(); - assertSameLists(longs, uniqueList); + assertListEquals(longs, uniqueList); } @@ -95,13 +92,13 @@ public void test_uniqueness_after_sorting() { UniqueArrayList uniqueList = UniqueArrayList.from(longs); Collections.sort(longs); uniqueList.sort(); - assertSameLists(longs, uniqueList); + assertListEquals(longs, uniqueList); uniqueList.addAll(longs); uniqueList.add(10); uniqueList.add(5, 65); - assertSameLists(longs, uniqueList); + assertListEquals(longs, uniqueList); uniqueList.add(575757); assertNotSameLists(longs, uniqueList); @@ -162,7 +159,7 @@ public void test_add_not_existing() { uniqueArrayList.add("Z"); uniqueArrayList.add("f"); - assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); } @@ -174,7 +171,7 @@ public void test_add_existing() { uniqueArrayList.add("Z"); uniqueArrayList.add("f"); - assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); uniqueArrayList.add("a"); uniqueArrayList.add("Z"); @@ -186,7 +183,7 @@ public void test_add_existing() { uniqueArrayList.add("Z"); uniqueArrayList.add("f"); - assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); } @@ -201,7 +198,7 @@ public void test_set_not_existing() { String res = uniqueArrayList.set(1, "m"); - assertEquals(Arrays.asList("a", "m", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "m", "f"), uniqueArrayList); assertEquals("Z", res); } @@ -217,7 +214,7 @@ public void test_set_existing() { String res = uniqueArrayList.set(1, "a"); - assertEquals(Arrays.asList("a", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "f"), uniqueArrayList); assertEquals("Z", res); } @@ -229,7 +226,7 @@ public void test_set_will_remove_replaced_item() { uniqueArrayList.set(0, "b"); uniqueArrayList.add("a"); - assertSameLists(Arrays.asList("b", "a"), uniqueArrayList); + assertListEquals(Arrays.asList("b", "a"), uniqueArrayList); } @@ -243,7 +240,7 @@ public void test_addAll_no_change() { boolean res = uniqueArrayList.addAll(Arrays.asList("a", "Z", "f")); - assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); assertFalse(res); } @@ -258,7 +255,7 @@ public void test_addAll_full_change() { boolean res = uniqueArrayList.addAll(Arrays.asList("w", "x", "y")); - assertEquals(Arrays.asList("a", "Z", "f", "w", "x", "y"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f", "w", "x", "y"), uniqueArrayList); assertTrue(res); } @@ -273,7 +270,7 @@ public void test_addAll_partial_change() { boolean res = uniqueArrayList.addAll(Arrays.asList("f", "Y", "Z")); - assertEquals(Arrays.asList("a", "Z", "f", "Y"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f", "Y"), uniqueArrayList); assertTrue(res); } @@ -288,7 +285,7 @@ public void test_addAll_withIndex_no_change() { boolean res = uniqueArrayList.addAll(1, Arrays.asList("a", "Z", "f")); - assertEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f"), uniqueArrayList); assertFalse(res); } @@ -303,7 +300,7 @@ public void test_addAll_withIndex_full_change() { boolean res = uniqueArrayList.addAll(1, Arrays.asList("w", "x", "y")); - assertSameLists(Arrays.asList("a", "w", "x", "y", "Z", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "w", "x", "y", "Z", "f"), uniqueArrayList); assertTrue(res); } @@ -318,7 +315,7 @@ public void test_addAll_withIndex_partial_change() { boolean res = uniqueArrayList.addAll(1, Arrays.asList("f", "Y", "Z")); - assertSameLists(Arrays.asList("a", "Y", "Z", "f"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Y", "Z", "f"), uniqueArrayList); assertTrue(res); } @@ -333,7 +330,7 @@ public void test_addAll_withIndex_last_position() { boolean res = uniqueArrayList.addAll(uniqueArrayList.size(), Arrays.asList("w", "x", "y")); - assertSameLists(Arrays.asList("a", "Z", "f", "w", "x", "y"), uniqueArrayList); + assertListEquals(Arrays.asList("a", "Z", "f", "w", "x", "y"), uniqueArrayList); assertTrue(res); } @@ -344,13 +341,13 @@ public void test_addAll_order_1() { final List l1 = Arrays.asList(1, 2, 3, 4, 5); assertTrue(uniqueArrayList.addAll(l1)); - assertSameLists(l1, uniqueArrayList); + assertListEquals(l1, uniqueArrayList); final List l2 = Arrays.asList(5, 4, 3, 2, 1, 0); assertTrue(uniqueArrayList.addAll(0,l2)); final List l3 = Arrays.asList(0, 1, 2, 3, 4, 5); - assertSameLists(l3, uniqueArrayList); + assertListEquals(l3, uniqueArrayList); } @@ -360,12 +357,12 @@ public void test_addAll_order_2() { final List l1 = Arrays.asList(1, 2, 3, 4, 5); assertTrue(uniqueArrayList.addAll(l1)); - assertSameLists(l1, uniqueArrayList); + assertListEquals(l1, uniqueArrayList); final List l2 = Arrays.asList(0, 1, 2, 3, 4, 5); assertTrue(uniqueArrayList.addAll(0,l2)); - assertSameLists(l2, uniqueArrayList); + assertListEquals(l2, uniqueArrayList); } @@ -545,11 +542,11 @@ public void test_toArray() { List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); UniqueArrayList uniqueList = UniqueArrayList.from(longs); - final Object[] arr = Arrays.asList( + final List arr = Arrays.asList( 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L - ).toArray(); + ); - assertArrayEquals(arr, uniqueList.toArray()); + assertListEquals(arr, uniqueList); } @@ -558,13 +555,12 @@ public void test_toArrayType() { List longs = Arrays.asList(1L, 1L, 2L, 3L, 4L, 1L, 5L, 1L, 6L, 7L, 8L, 9L, 10L, 11L, 1L, 12L, 13L); UniqueArrayList uniqueList = UniqueArrayList.from(longs); - final Long[] arr = Arrays.asList( + final List arr = Arrays.asList( 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L, 12L, 13L - ).toArray(new Long[] {}); + ); - final Long[] arr_res = uniqueList.toArray(new Long[] {}); - assertArrayEquals(arr, arr_res); + assertListEquals(arr, uniqueList); - assertThat(arr_res[0], instanceOf(Long.class)); + assertThat(uniqueList.get(0), instanceOf(Long.class)); } } \ No newline at end of file From 34c9f25038d24e3bf9f338fa307b1bcb3610b5ba Mon Sep 17 00:00:00 2001 From: Ritvij Kumar Sharma Date: Sat, 3 Apr 2021 18:59:29 +0530 Subject: [PATCH 041/171] Prevent OR CardBrowser query from bypassing RestrictOnDeck (#8414) * added braces in search query and test for checking it --- .../main/java/com/ichi2/anki/CardBrowser.java | 13 +++++++++++-- .../java/com/ichi2/anki/CardBrowserTest.java | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java index e6d68d2f5166..ff964cf4b72a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java @@ -1500,9 +1500,12 @@ private void searchCards() { mSearchItem.expandActionView(); } if (mSearchTerms.contains("deck:")) { - searchText = mSearchTerms; + searchText = "(" + mSearchTerms + ")"; } else { - searchText = mRestrictOnDeck + mSearchTerms; + if (!"".equals(mSearchTerms)) + searchText = mRestrictOnDeck + "(" + mSearchTerms + ")"; + else + searchText = mRestrictOnDeck; } if (colIsOpen() && mCardsAdapter!= null) { // clear the existing card list @@ -2889,4 +2892,10 @@ void replaceSelectionWith(int[] positions) { mCheckedCards.clear(); checkCardsAtPositions(positions); } + + @VisibleForTesting + void searchCards(String searchQuery) { + mSearchTerms = searchQuery; + searchCards(); + } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java index 982b92fc4ed7..f2ee5e2a4406 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java @@ -509,6 +509,24 @@ public void rescheduleUndoTest() { assertUndoContains(b, R.string.deck_conf_cram_reschedule); } + /** 8027 */ + @Test + public void checkSearchString() { + addNoteUsingBasicModel("Hello", "John"); + long deck = addDeck("Deck 1"); + getCol().getDecks().select(deck); + Card c2 = addNoteUsingBasicModel("New", "world").firstCard(); + c2.setDid(deck); + c2.flush(); + + CardBrowser cardBrowser = getBrowserWithNoNewCards(); + cardBrowser.searchCards("world or hello"); + advanceRobolectricLooperWithSleep(); + + assertThat("Cardbrowser has Deck 1 as selected deck", cardBrowser.getSelectedDeckNameForUi(), is("Deck 1")); + assertThat("Results should only be from the selected deck", cardBrowser.getCardCount(), is(1)); + } + protected void assertUndoDoesNotContain(CardBrowser browser, @StringRes int resId) { ShadowActivity shadowActivity = shadowOf(browser); MenuItem item = shadowActivity.getOptionsMenu().findItem(R.id.action_undo); From eb665dea6aebc138ab7dc773181c39e828efa90a Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Sat, 3 Apr 2021 12:42:22 -0500 Subject: [PATCH 042/171] Call super method in DeckPicker.onRequestPermissionResult flagged by Android Studio as a lint / error --- AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index 5b9d59aeaffb..ced16d27ac26 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -506,8 +506,9 @@ private void toggleMicToolBar() { } } - public void onRequestPermissionsResult (int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if ( (requestCode == REQUEST_AUDIO_PERMISSION) && + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if ((requestCode == REQUEST_AUDIO_PERMISSION) && (permissions.length >= 1) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // Get get audio record permission, so we can create the record tool bar toggleMicToolBar(); From 39f8b910a99237a331b8b8b17898433dfc82b669 Mon Sep 17 00:00:00 2001 From: Vaibhav Singhal Date: Sun, 4 Apr 2021 01:30:06 +0530 Subject: [PATCH 043/171] Improved UI for the Add Decks Button (#8452) * Updated background color --- .../java/com/ichi2/anki/DeckPickerFloatingActionMenu.java | 4 ++++ .../src/main/res/drawable-v21/fab_label_background.xml | 8 ++++---- .../res/drawable/fab_label_background_black_pre21.xml | 8 ++++---- .../src/main/res/drawable/fab_label_background_pre21.xml | 8 ++++---- AnkiDroid/src/main/res/values/attrs.xml | 3 +-- AnkiDroid/src/main/res/values/colors.xml | 2 ++ AnkiDroid/src/main/res/values/theme_black.xml | 1 - AnkiDroid/src/main/res/values/theme_dark.xml | 1 - AnkiDroid/src/main/res/values/theme_light.xml | 1 - AnkiDroid/src/main/res/values/theme_plain.xml | 1 - 10 files changed, 19 insertions(+), 18 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java index f5e434f0b1de..d2a30a433ad2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java @@ -42,6 +42,7 @@ public class DeckPickerFloatingActionMenu { private boolean mIsFABOpen = false; private final DeckPicker mDeckPicker; + private LinearLayout mLinearLayout; public DeckPickerFloatingActionMenu(View view, DeckPicker deckPicker) { this.mDeckPicker = deckPicker; @@ -53,6 +54,7 @@ public DeckPickerFloatingActionMenu(View view, DeckPicker deckPicker) { mAddSharedButton = (FloatingActionButton)view.findViewById(R.id.add_shared_action); mAddDeckButton = (FloatingActionButton)view.findViewById(R.id.add_deck_action); mFabBGLayout = view.findViewById(R.id.fabBGLayout); + mLinearLayout = view.findViewById(R.id.deckpicker_view); mFabMain.setOnClickListener(new View.OnClickListener() { @Override @@ -128,6 +130,7 @@ public void setIsFABOpen(boolean mIsFABOpen) { private void showFloatingActionMenu() { + mLinearLayout.setAlpha(0.5f); mIsFABOpen = true; if (!animationDisabled()) { // Show with animation @@ -152,6 +155,7 @@ private void showFloatingActionMenu() { } protected void closeFloatingActionMenu() { + mLinearLayout.setAlpha(1f); mIsFABOpen = false; mFabBGLayout.setVisibility(View.GONE); if (!animationDisabled()) { diff --git a/AnkiDroid/src/main/res/drawable-v21/fab_label_background.xml b/AnkiDroid/src/main/res/drawable-v21/fab_label_background.xml index 025891f18f97..4c8f3874b721 100644 --- a/AnkiDroid/src/main/res/drawable-v21/fab_label_background.xml +++ b/AnkiDroid/src/main/res/drawable-v21/fab_label_background.xml @@ -1,11 +1,11 @@ - + + android:bottom="5dp"/> + android:radius="4dp"/> \ No newline at end of file diff --git a/AnkiDroid/src/main/res/drawable/fab_label_background_black_pre21.xml b/AnkiDroid/src/main/res/drawable/fab_label_background_black_pre21.xml index 0917ce7dc132..4c8f3874b721 100644 --- a/AnkiDroid/src/main/res/drawable/fab_label_background_black_pre21.xml +++ b/AnkiDroid/src/main/res/drawable/fab_label_background_black_pre21.xml @@ -1,11 +1,11 @@ - + + android:bottom="5dp"/> + android:radius="4dp"/> \ No newline at end of file diff --git a/AnkiDroid/src/main/res/drawable/fab_label_background_pre21.xml b/AnkiDroid/src/main/res/drawable/fab_label_background_pre21.xml index 940fb283cd3d..4c8f3874b721 100644 --- a/AnkiDroid/src/main/res/drawable/fab_label_background_pre21.xml +++ b/AnkiDroid/src/main/res/drawable/fab_label_background_pre21.xml @@ -1,11 +1,11 @@ - + + android:bottom="5dp"/> + android:radius="4dp"/> \ No newline at end of file diff --git a/AnkiDroid/src/main/res/values/attrs.xml b/AnkiDroid/src/main/res/values/attrs.xml index 202fd477d102..f6601007f8ab 100644 --- a/AnkiDroid/src/main/res/values/attrs.xml +++ b/AnkiDroid/src/main/res/values/attrs.xml @@ -112,8 +112,7 @@ - - + #fff diff --git a/AnkiDroid/src/main/res/values/colors.xml b/AnkiDroid/src/main/res/values/colors.xml index 88714ff7c61a..d42e9d77e624 100644 --- a/AnkiDroid/src/main/res/values/colors.xml +++ b/AnkiDroid/src/main/res/values/colors.xml @@ -112,5 +112,7 @@ #f45000 #ff5e13 + + #777 diff --git a/AnkiDroid/src/main/res/values/theme_black.xml b/AnkiDroid/src/main/res/values/theme_black.xml index 7a17cac8362e..27b8d899a018 100644 --- a/AnkiDroid/src/main/res/values/theme_black.xml +++ b/AnkiDroid/src/main/res/values/theme_black.xml @@ -96,7 +96,6 @@ #ff303030 #c8303030 @color/theme_black_primary_text - @color/theme_black_primary_light @drawable/fab_label_background_black_pre21 @drawable/nav_drawer_logo_black_theme diff --git a/AnkiDroid/src/main/res/values/theme_dark.xml b/AnkiDroid/src/main/res/values/theme_dark.xml index 3fe284a5a570..a7fe3f934b9c 100644 --- a/AnkiDroid/src/main/res/values/theme_dark.xml +++ b/AnkiDroid/src/main/res/values/theme_dark.xml @@ -99,7 +99,6 @@ @color/material_light_blue_700 @color/material_light_blue_900 @color/white - @color/material_grey_700 @drawable/fab_label_background_pre21 @drawable/nav_drawer_logo_dark_theme diff --git a/AnkiDroid/src/main/res/values/theme_light.xml b/AnkiDroid/src/main/res/values/theme_light.xml index 617d5f75a49b..a3ca038355af 100644 --- a/AnkiDroid/src/main/res/values/theme_light.xml +++ b/AnkiDroid/src/main/res/values/theme_light.xml @@ -113,7 +113,6 @@ APIs. It's visible when there aren't enough decks to fill the screen. @color/material_light_blue_700 @color/material_light_blue_900 @color/white - @color/material_grey_700 @drawable/fab_label_background_pre21 @drawable/nav_drawer_logo diff --git a/AnkiDroid/src/main/res/values/theme_plain.xml b/AnkiDroid/src/main/res/values/theme_plain.xml index 5db0d5ac04d8..8fac6727f41a 100644 --- a/AnkiDroid/src/main/res/values/theme_plain.xml +++ b/AnkiDroid/src/main/res/values/theme_plain.xml @@ -42,7 +42,6 @@ @color/theme_plain_accent #c8607d8b - @color/theme_plain_primary_dark @color/material_grey_600 @color/material_grey_500 From 6cb9fac7bd8d07c28c7cfcd945b9ab8fa863f09d Mon Sep 17 00:00:00 2001 From: Vaibhav Singhal Date: Sun, 4 Apr 2021 01:32:52 +0530 Subject: [PATCH 044/171] Implemented toast message when back is pressed (#8457) --- .../src/main/java/com/ichi2/anki/DeckPicker.java | 11 +++++++++-- AnkiDroid/src/main/res/values/02-strings.xml | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 86b5c380e62d..25f93996bf6c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -192,6 +192,8 @@ public class DeckPicker extends NavigationDrawerActivity implements // Short animation duration from system private int mShortAnimDuration; + private boolean mBackButtonPressedToExit = false; + private RelativeLayout mDeckPickerContent; private MaterialDialog mProgressDialog; @@ -1032,8 +1034,13 @@ public void onBackPressed() { if (mFloatingActionMenu.isFABOpen()) { mFloatingActionMenu.closeFloatingActionMenu(); } else { - automaticSync(); - finishWithAnimation(); + if (mBackButtonPressedToExit) { + automaticSync(); + finishWithAnimation(); + } else { + UIUtils.showThemedToast(this, getString(R.string.back_pressed_once), true); + } + mBackButtonPressedToExit = true; } } } diff --git a/AnkiDroid/src/main/res/values/02-strings.xml b/AnkiDroid/src/main/res/values/02-strings.xml index 22a27ce30ff8..7e52a650bfdd 100644 --- a/AnkiDroid/src/main/res/values/02-strings.xml +++ b/AnkiDroid/src/main/res/values/02-strings.xml @@ -373,4 +373,6 @@ Email address is required Password is required + + Press back again to exit From 837a3b3fcee7dd9ecdebf500d5183a9f36f9f230 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Sat, 3 Apr 2021 15:08:58 -0500 Subject: [PATCH 045/171] chore: remove pre-API21 workaround for language icon in prefs the prefs screen can handle SVG drawables directly in API21+ --- .../main/java/com/ichi2/anki/Preferences.java | 19 ------------------- .../src/main/res/xml/preferences_general.xml | 1 + 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java index 360053f4b933..072d8ff7c7b0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java @@ -197,25 +197,6 @@ private void initSubscreen(String action, PreferenceContext listener) { mCategory.removePreference(mCheckBoxPref_Vibrate); mCategory.removePreference(mCheckBoxPref_Blink); } - try { - // This works on an API 19 emulator - // use icon= once we're past API 21 - // ---- - // android.content.res.Resources$NotFoundException: File res/drawable/ic_language_black_24dp.xml - // from drawable resource ID #0x7f0800d7. If the resource you are trying to use is a vector - // resource, you may be referencing it in an unsupported way. - // See AppCompatDelegate.setCompatVectorFromResourcesEnabled() for more info. - Drawable languageIcon = VectorDrawableCompat.create( - getResources(), - R.drawable.ic_language_black_24dp, - getTheme()); - - screen.findPreference("language").setIcon(languageIcon); - } catch (Exception e) { - Timber.w(e, "Failed to set language icon"); - } - - // Build languages initializeLanguageDialog(screen); break; diff --git a/AnkiDroid/src/main/res/xml/preferences_general.xml b/AnkiDroid/src/main/res/xml/preferences_general.xml index 63fd610ddfa4..29c40ef13dbb 100644 --- a/AnkiDroid/src/main/res/xml/preferences_general.xml +++ b/AnkiDroid/src/main/res/xml/preferences_general.xml @@ -59,6 +59,7 @@ Date: Sat, 3 Apr 2021 12:30:13 -0500 Subject: [PATCH 046/171] Add software render preference, enable by default on Android 8/8.1 Related #7369 #8160 --- .../src/main/java/com/ichi2/anki/DeckPicker.java | 12 ++++++++++++ AnkiDroid/src/main/res/values/10-preferences.xml | 2 ++ AnkiDroid/src/main/res/xml/preferences_advanced.xml | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 25f93996bf6c..bc45dd3fe6fb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -37,6 +37,7 @@ import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -1146,6 +1147,17 @@ public void addNote() { private void showStartupScreensAndDialogs(SharedPreferences preferences, int skip) { + // For Android 8/8.1 we want to use software rendering by default or the Reviewer UI is broken #7369 + if (CompatHelper.getSdkVersion() == Build.VERSION_CODES.O || + CompatHelper.getSdkVersion() == Build.VERSION_CODES.O_MR1) { + if (!preferences.contains("softwareRender")) { + Timber.i("Android 8/8.1 detected with no render preference. Turning on software render."); + preferences.edit().putBoolean("softwareRender", true).apply(); + } else { + Timber.i("Android 8/8.1 detected, software render preference already exists."); + } + } + if (!BackupManager.enoughDiscSpace(CollectionHelper.getCurrentAnkiDroidDirectory(this))) { Timber.i("Not enough space to do backup"); showDialogFragment(DeckPickerNoSpaceLeftDialog.newInstance()); diff --git a/AnkiDroid/src/main/res/values/10-preferences.xml b/AnkiDroid/src/main/res/values/10-preferences.xml index 792f3c6fa910..91304e8b40b8 100644 --- a/AnkiDroid/src/main/res/values/10-preferences.xml +++ b/AnkiDroid/src/main/res/values/10-preferences.xml @@ -133,6 +133,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/xml/preferences_advanced.xml b/AnkiDroid/src/main/res/xml/preferences_advanced.xml index 9f6059e1bb21..8cbf7bc26c60 100644 --- a/AnkiDroid/src/main/res/xml/preferences_advanced.xml +++ b/AnkiDroid/src/main/res/xml/preferences_advanced.xml @@ -52,6 +52,11 @@ + Date: Sat, 3 Apr 2021 14:32:37 -0500 Subject: [PATCH 047/171] dynamically enable software render on all Reviewer and Info views - determine the root view dynamically - collect all root view children in layout tree - iterate over all collected views and set layer type to software render Fixes #8160 --- .../ichi2/anki/AbstractFlashcardViewer.java | 2 +- .../src/main/java/com/ichi2/anki/Info.java | 3 ++ .../main/java/com/ichi2/anki/Reviewer.java | 12 +++++-- .../java/com/ichi2/utils/ViewGroupUtils.java | 32 +++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 43fc15711a09..3dc10c3efd85 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -1876,7 +1876,7 @@ private void setInterface() { recreateWebView(); } - private void recreateWebView() { + protected void recreateWebView() { if (mCardWebView == null) { mCardWebView = createWebView(); WebViewDebugging.initializeDebugging(AnkiDroidApp.getSharedPrefs(this)); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Info.java b/AnkiDroid/src/main/java/com/ichi2/anki/Info.java index 46355ec490a1..e0c18449c3c5 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Info.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Info.java @@ -35,6 +35,7 @@ import com.ichi2.utils.IntentUtil; import com.ichi2.utils.VersionUtils; +import com.ichi2.utils.ViewGroupUtils; import org.acra.util.Installation; @@ -104,6 +105,8 @@ public void onProgressChanged(WebView view, int progress) { String textColor = String.format("#%06X", (0xFFFFFF & typedArray.getColor(1, -1))); // Color to hex string webView.setBackgroundColor(backgroundColor); + ViewGroupUtils.setRenderWorkaround(this); + switch (mType) { case TYPE_ABOUT: { String[] content = res.getStringArray(R.array.about_content); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index ced16d27ac26..fb423496ae37 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -34,7 +34,6 @@ import android.os.Message; import android.text.SpannableString; import android.text.style.UnderlineSpan; -import android.util.Pair; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; @@ -71,9 +70,7 @@ import com.ichi2.anki.workarounds.FirefoxSnackbarWorkaround; import com.ichi2.anki.reviewer.ActionButtons; import com.ichi2.async.CollectionTask; -import com.ichi2.async.TaskListener; import com.ichi2.async.TaskManager; -import com.ichi2.compat.CompatHelper; import com.ichi2.libanki.Card; import com.ichi2.libanki.Collection; import com.ichi2.libanki.Consts; @@ -85,6 +82,7 @@ import com.ichi2.utils.FunctionalInterfaces.Consumer; import com.ichi2.utils.PairWithBoolean; import com.ichi2.utils.Permissions; +import com.ichi2.utils.ViewGroupUtils; import com.ichi2.widget.WidgetStatus; import java.lang.ref.WeakReference; @@ -207,6 +205,12 @@ protected WebView createWebView() { return ret; } + @Override + protected void recreateWebView() { + super.recreateWebView(); + ViewGroupUtils.setRenderWorkaround(this); + } + @Override protected boolean shouldDisplayMark() { boolean markValue = super.shouldDisplayMark(); @@ -296,6 +300,8 @@ protected void onCollectionLoaded(Collection col) { if (mPrefFullscreenReview) { setFullScreen(this); } + + ViewGroupUtils.setRenderWorkaround(this); } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.java b/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.java index 85b1a245ebfb..b7c14ccea83b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/ViewGroupUtils.java @@ -16,13 +16,17 @@ package com.ichi2.utils; +import android.app.Activity; import android.view.View; import android.view.ViewGroup; +import com.ichi2.anki.AnkiDroidApp; + import java.util.ArrayList; import java.util.List; import androidx.annotation.NonNull; +import timber.log.Timber; public class ViewGroupUtils { @NonNull @@ -47,4 +51,32 @@ public static List getAllChildrenRecursive(@NonNull ViewGroup viewGroup) { } return views; } + + public static void setRenderWorkaround(Activity activity) { + if (AnkiDroidApp.getSharedPrefs(activity).getBoolean("softwareRender", false)) { + Timber.i("ViewGroupUtils::setRenderWorkaround - software render requested, altering Views..."); + ViewGroupUtils.setContentViewLayerTypeSoftware(activity); + } else { + Timber.i("ViewGroupUtils::setRenderWorkaround - using default / hardware rendering"); + } + } + + /** + * Gets all the Views for the given Activity's ContentView, and sets their layerType + * to the given layerType + * + * @param activity Activity containing the View hierarchy to alter + */ + private static void setContentViewLayerTypeSoftware(@NonNull Activity activity) { + ViewGroup rootViewGroup = + (ViewGroup) ((ViewGroup) activity.findViewById(android.R.id.content)) + .getChildAt(0); + List allViews = getAllChildrenRecursive(rootViewGroup); + allViews.add(rootViewGroup); + for (View v : allViews) { + Timber.d("ViewGroupUtils::setContentViewLayerTypeSoftware for view %s", + v.getId()); + v.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + } } From 6e34c52db3f8c1a7787e21e57e04d40dc15575a3 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Sun, 4 Apr 2021 18:59:17 +0530 Subject: [PATCH 048/171] Autofocus name in 'Create deck' from Add Deck FAB (#8463) * Set focus on Create deck name field by default * Add comment related to opening of keyboard --- .../com/ichi2/anki/DeckPickerFloatingActionMenu.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java index d2a30a433ad2..1b22d0c841cd 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java @@ -19,6 +19,7 @@ import android.animation.Animator; import android.content.SharedPreferences; import android.view.View; +import android.view.WindowManager; import android.widget.EditText; import android.widget.LinearLayout; @@ -27,6 +28,8 @@ import com.ichi2.libanki.Decks; import com.ichi2.ui.FixedEditText; +import java.util.Objects; + import timber.log.Timber; public class DeckPickerFloatingActionMenu { @@ -79,7 +82,8 @@ public void onClick(View view) { closeFloatingActionMenu(); EditText mDialogEditText = new FixedEditText(mDeckPicker); mDialogEditText.setSingleLine(true); - new MaterialDialog.Builder(mDeckPicker) + mDialogEditText.requestFocus(); + MaterialDialog materialDialog = new MaterialDialog.Builder(mDeckPicker) .title(R.string.new_deck) .positiveText(R.string.dialog_ok) .customView(mDialogEditText, true) @@ -97,6 +101,9 @@ public void onClick(View view) { }) .negativeText(R.string.dialog_cancel) .show(); + + // Open keyboard when dialog shows + Objects.requireNonNull(materialDialog.getWindow()).setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } }); From 3229887282ffb876521c7a6a0710465bc8a3512a Mon Sep 17 00:00:00 2001 From: AnkiDroid Translations Date: Sun, 4 Apr 2021 14:21:22 +0000 Subject: [PATCH 049/171] Updated strings from Crowdin --- .../src/main/res/values-af/02-strings.xml | 1 + .../src/main/res/values-af/10-preferences.xml | 2 + .../src/main/res/values-am/02-strings.xml | 1 + .../src/main/res/values-am/10-preferences.xml | 2 + AnkiDroid/src/main/res/values-ar/01-core.xml | 2 +- .../src/main/res/values-ar/02-strings.xml | 1 + .../src/main/res/values-ar/10-preferences.xml | 2 + .../src/main/res/values-az/02-strings.xml | 1 + .../src/main/res/values-az/10-preferences.xml | 2 + .../src/main/res/values-be/02-strings.xml | 1 + .../src/main/res/values-be/10-preferences.xml | 2 + .../src/main/res/values-bg/02-strings.xml | 1 + .../src/main/res/values-bg/10-preferences.xml | 2 + .../src/main/res/values-bn/02-strings.xml | 1 + .../src/main/res/values-bn/10-preferences.xml | 2 + .../src/main/res/values-ca/02-strings.xml | 1 + .../src/main/res/values-ca/10-preferences.xml | 2 + .../src/main/res/values-ckb/02-strings.xml | 1 + .../main/res/values-ckb/10-preferences.xml | 2 + .../src/main/res/values-cs/02-strings.xml | 1 + .../src/main/res/values-cs/10-preferences.xml | 2 + .../src/main/res/values-da/02-strings.xml | 1 + .../src/main/res/values-da/10-preferences.xml | 2 + .../src/main/res/values-de/02-strings.xml | 1 + .../src/main/res/values-de/10-preferences.xml | 2 + .../src/main/res/values-el/02-strings.xml | 1 + .../src/main/res/values-el/10-preferences.xml | 2 + .../src/main/res/values-eo/02-strings.xml | 1 + .../src/main/res/values-eo/10-preferences.xml | 2 + .../src/main/res/values-es-rAR/02-strings.xml | 1 + .../main/res/values-es-rAR/10-preferences.xml | 2 + .../src/main/res/values-es-rES/02-strings.xml | 1 + .../main/res/values-es-rES/10-preferences.xml | 2 + .../src/main/res/values-et/02-strings.xml | 1 + .../src/main/res/values-et/10-preferences.xml | 2 + .../src/main/res/values-eu/02-strings.xml | 1 + .../src/main/res/values-eu/10-preferences.xml | 2 + .../src/main/res/values-fa/02-strings.xml | 1 + .../src/main/res/values-fa/10-preferences.xml | 2 + .../src/main/res/values-fi/02-strings.xml | 1 + .../src/main/res/values-fi/10-preferences.xml | 2 + .../src/main/res/values-fil/02-strings.xml | 1 + .../main/res/values-fil/10-preferences.xml | 2 + .../src/main/res/values-fr/02-strings.xml | 1 + .../src/main/res/values-fr/10-preferences.xml | 2 + .../src/main/res/values-fy/02-strings.xml | 1 + .../src/main/res/values-fy/10-preferences.xml | 2 + .../src/main/res/values-ga/02-strings.xml | 1 + .../src/main/res/values-ga/10-preferences.xml | 2 + .../src/main/res/values-gl/02-strings.xml | 1 + .../src/main/res/values-gl/10-preferences.xml | 2 + .../src/main/res/values-got/02-strings.xml | 1 + .../main/res/values-got/10-preferences.xml | 2 + .../src/main/res/values-gu/02-strings.xml | 1 + .../src/main/res/values-gu/10-preferences.xml | 2 + .../src/main/res/values-heb/02-strings.xml | 1 + .../main/res/values-heb/10-preferences.xml | 2 + AnkiDroid/src/main/res/values-hi/01-core.xml | 2 +- .../src/main/res/values-hi/02-strings.xml | 1 + .../src/main/res/values-hi/10-preferences.xml | 2 + .../src/main/res/values-hr/02-strings.xml | 1 + .../src/main/res/values-hr/10-preferences.xml | 2 + .../src/main/res/values-hu/02-strings.xml | 1 + .../src/main/res/values-hu/10-preferences.xml | 2 + .../src/main/res/values-hy/02-strings.xml | 1 + .../src/main/res/values-hy/10-preferences.xml | 2 + .../src/main/res/values-ind/02-strings.xml | 1 + .../main/res/values-ind/10-preferences.xml | 2 + .../src/main/res/values-is/02-strings.xml | 1 + .../src/main/res/values-is/10-preferences.xml | 2 + .../src/main/res/values-it/02-strings.xml | 1 + .../src/main/res/values-it/10-preferences.xml | 2 + .../src/main/res/values-ja/02-strings.xml | 1 + .../src/main/res/values-ja/10-preferences.xml | 2 + .../src/main/res/values-jv/02-strings.xml | 1 + .../src/main/res/values-jv/10-preferences.xml | 2 + .../src/main/res/values-ka/02-strings.xml | 1 + .../src/main/res/values-ka/10-preferences.xml | 2 + .../src/main/res/values-kk/02-strings.xml | 1 + .../src/main/res/values-kk/10-preferences.xml | 2 + .../src/main/res/values-km/02-strings.xml | 1 + .../src/main/res/values-km/10-preferences.xml | 2 + .../src/main/res/values-ko/02-strings.xml | 1 + .../src/main/res/values-ko/10-preferences.xml | 2 + .../src/main/res/values-ku/02-strings.xml | 1 + .../src/main/res/values-ku/10-preferences.xml | 2 + .../src/main/res/values-ky/02-strings.xml | 1 + .../src/main/res/values-ky/10-preferences.xml | 2 + .../src/main/res/values-lt/02-strings.xml | 1 + .../src/main/res/values-lt/10-preferences.xml | 2 + .../src/main/res/values-lv/02-strings.xml | 1 + .../src/main/res/values-lv/10-preferences.xml | 2 + .../src/main/res/values-mk/02-strings.xml | 1 + .../src/main/res/values-mk/10-preferences.xml | 2 + .../src/main/res/values-mn/02-strings.xml | 1 + .../src/main/res/values-mn/10-preferences.xml | 2 + .../src/main/res/values-mr/02-strings.xml | 1 + .../src/main/res/values-mr/10-preferences.xml | 2 + .../src/main/res/values-ms/02-strings.xml | 1 + .../src/main/res/values-ms/10-preferences.xml | 2 + .../src/main/res/values-my/02-strings.xml | 1 + .../src/main/res/values-my/10-preferences.xml | 2 + .../src/main/res/values-nl/02-strings.xml | 1 + .../src/main/res/values-nl/10-preferences.xml | 2 + .../src/main/res/values-nn/02-strings.xml | 1 + .../src/main/res/values-nn/10-preferences.xml | 2 + .../src/main/res/values-no/02-strings.xml | 1 + .../src/main/res/values-no/10-preferences.xml | 2 + AnkiDroid/src/main/res/values-or/01-core.xml | 4 +- .../src/main/res/values-or/02-strings.xml | 3 +- .../src/main/res/values-or/03-dialogs.xml | 4 +- .../src/main/res/values-or/04-network.xml | 2 +- .../src/main/res/values-or/10-preferences.xml | 2 + .../src/main/res/values-pa/02-strings.xml | 1 + .../src/main/res/values-pa/10-preferences.xml | 2 + .../src/main/res/values-pl/02-strings.xml | 1 + .../src/main/res/values-pl/10-preferences.xml | 2 + .../src/main/res/values-pt-rBR/02-strings.xml | 1 + .../main/res/values-pt-rBR/10-preferences.xml | 2 + .../src/main/res/values-pt-rPT/02-strings.xml | 1 + .../main/res/values-pt-rPT/10-preferences.xml | 2 + .../src/main/res/values-ro/02-strings.xml | 1 + .../src/main/res/values-ro/10-preferences.xml | 2 + .../src/main/res/values-ru/02-strings.xml | 1 + .../src/main/res/values-ru/10-preferences.xml | 2 + .../src/main/res/values-sat/02-strings.xml | 1 + .../main/res/values-sat/10-preferences.xml | 2 + .../src/main/res/values-sk/02-strings.xml | 1 + .../src/main/res/values-sk/10-preferences.xml | 2 + .../src/main/res/values-sl/02-strings.xml | 1 + .../src/main/res/values-sl/10-preferences.xml | 2 + .../src/main/res/values-sq/02-strings.xml | 1 + .../src/main/res/values-sq/10-preferences.xml | 2 + .../src/main/res/values-sr/02-strings.xml | 1 + .../src/main/res/values-sr/10-preferences.xml | 2 + .../src/main/res/values-ss/02-strings.xml | 1 + .../src/main/res/values-ss/10-preferences.xml | 2 + .../src/main/res/values-sv/02-strings.xml | 1 + .../src/main/res/values-sv/10-preferences.xml | 2 + .../src/main/res/values-sw/02-strings.xml | 1 + .../src/main/res/values-sw/10-preferences.xml | 2 + .../src/main/res/values-ta/02-strings.xml | 1 + .../src/main/res/values-ta/10-preferences.xml | 2 + .../src/main/res/values-te/02-strings.xml | 1 + .../src/main/res/values-te/10-preferences.xml | 2 + .../src/main/res/values-tg/02-strings.xml | 1 + .../src/main/res/values-tg/10-preferences.xml | 2 + .../src/main/res/values-tgl/02-strings.xml | 1 + .../main/res/values-tgl/10-preferences.xml | 2 + .../src/main/res/values-th/02-strings.xml | 1 + .../src/main/res/values-th/10-preferences.xml | 2 + .../src/main/res/values-ti/02-strings.xml | 1 + .../src/main/res/values-ti/10-preferences.xml | 2 + .../src/main/res/values-tn/02-strings.xml | 1 + .../src/main/res/values-tn/10-preferences.xml | 2 + AnkiDroid/src/main/res/values-tr/01-core.xml | 10 +-- .../src/main/res/values-tr/02-strings.xml | 85 ++++++++++--------- .../src/main/res/values-tr/03-dialogs.xml | 72 ++++++++-------- .../src/main/res/values-tr/04-network.xml | 2 +- .../src/main/res/values-tr/05-feedback.xml | 2 +- .../src/main/res/values-tr/07-cardbrowser.xml | 68 +++++++-------- .../src/main/res/values-tr/10-preferences.xml | 4 +- .../src/main/res/values-tr/11-arrays.xml | 58 ++++++------- .../main/res/values-tr/18-standard-models.xml | 4 +- .../src/main/res/values-ts/02-strings.xml | 1 + .../src/main/res/values-ts/10-preferences.xml | 2 + .../src/main/res/values-tt/02-strings.xml | 3 +- .../src/main/res/values-tt/10-preferences.xml | 4 +- .../main/res/values-tt/17-model-manager.xml | 2 +- .../src/main/res/values-uk/02-strings.xml | 1 + .../src/main/res/values-uk/10-preferences.xml | 2 + .../src/main/res/values-ur/02-strings.xml | 1 + .../src/main/res/values-ur/10-preferences.xml | 2 + .../src/main/res/values-uz/02-strings.xml | 1 + .../src/main/res/values-uz/10-preferences.xml | 2 + .../src/main/res/values-ve/02-strings.xml | 1 + .../src/main/res/values-ve/10-preferences.xml | 2 + .../src/main/res/values-vi/02-strings.xml | 1 + .../src/main/res/values-vi/10-preferences.xml | 2 + .../src/main/res/values-wo/02-strings.xml | 1 + .../src/main/res/values-wo/10-preferences.xml | 2 + .../src/main/res/values-xh/02-strings.xml | 1 + .../src/main/res/values-xh/10-preferences.xml | 2 + .../src/main/res/values-yue/02-strings.xml | 1 + .../main/res/values-yue/10-preferences.xml | 2 + .../src/main/res/values-zh-rCN/01-core.xml | 2 +- .../src/main/res/values-zh-rCN/02-strings.xml | 1 + .../main/res/values-zh-rCN/10-preferences.xml | 2 + .../src/main/res/values-zh-rTW/02-strings.xml | 1 + .../main/res/values-zh-rTW/10-preferences.xml | 2 + .../src/main/res/values-zu/02-strings.xml | 1 + .../src/main/res/values-zu/10-preferences.xml | 2 + .../marketdescription-tr.txt | 6 +- 193 files changed, 433 insertions(+), 166 deletions(-) diff --git a/AnkiDroid/src/main/res/values-af/02-strings.xml b/AnkiDroid/src/main/res/values-af/02-strings.xml index 499f8171f8e3..66353c0179b6 100644 --- a/AnkiDroid/src/main/res/values-af/02-strings.xml +++ b/AnkiDroid/src/main/res/values-af/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-af/10-preferences.xml b/AnkiDroid/src/main/res/values-af/10-preferences.xml index 8dedab398fda..7e8333a3fd4d 100644 --- a/AnkiDroid/src/main/res/values-af/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-af/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Kies taal + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-am/02-strings.xml b/AnkiDroid/src/main/res/values-am/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-am/02-strings.xml +++ b/AnkiDroid/src/main/res/values-am/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-am/10-preferences.xml b/AnkiDroid/src/main/res/values-am/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-am/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-am/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ar/01-core.xml b/AnkiDroid/src/main/res/values-ar/01-core.xml index 1ce950068b85..15349186403a 100644 --- a/AnkiDroid/src/main/res/values-ar/01-core.xml +++ b/AnkiDroid/src/main/res/values-ar/01-core.xml @@ -174,7 +174,7 @@ جلسة دراسة مخصصة أعد تسمية رزمة الدراسة المخصصة الموجودة أولًا هذه الرزمة فارغة - Deck Search + البحث عن رزمة إضافة بِطاقة اسم رزمة غير صالح هذه الرزمة فارغة. اضغط زر + لإضافة محتوى جديد. diff --git a/AnkiDroid/src/main/res/values-ar/02-strings.xml b/AnkiDroid/src/main/res/values-ar/02-strings.xml index c64db1e3219a..0653eda62a5d 100644 --- a/AnkiDroid/src/main/res/values-ar/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ar/02-strings.xml @@ -399,4 +399,5 @@ يلزم بريد إلكتروني تلزم كلمة مرور + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ar/10-preferences.xml b/AnkiDroid/src/main/res/values-ar/10-preferences.xml index 372ab5a89f97..9d81b2033754 100644 --- a/AnkiDroid/src/main/res/values-ar/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ar/10-preferences.xml @@ -151,6 +151,8 @@ XXX ثانية مهلة إظهار السؤال التالي حدد اللغة + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. وضع العرض الآمن تعطيل كل التأثيرات المتحركة واستخدام طريقة أأمن لرسم البطاقات. قد تتطلب أجهزة الحبر الإلكتروني هذا. تعطيل وضع تحرير الحقل الفردي diff --git a/AnkiDroid/src/main/res/values-az/02-strings.xml b/AnkiDroid/src/main/res/values-az/02-strings.xml index 3f2bd03e1f36..383601013123 100644 --- a/AnkiDroid/src/main/res/values-az/02-strings.xml +++ b/AnkiDroid/src/main/res/values-az/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-az/10-preferences.xml b/AnkiDroid/src/main/res/values-az/10-preferences.xml index 0e4d1c315a46..b06135a2a15c 100644 --- a/AnkiDroid/src/main/res/values-az/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-az/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Növbəti sualı göstərmə vaxtı Dili seç + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Etibarlı görüntü rejimi Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-be/02-strings.xml b/AnkiDroid/src/main/res/values-be/02-strings.xml index 9e3e10399d59..d9a3ad62789a 100644 --- a/AnkiDroid/src/main/res/values-be/02-strings.xml +++ b/AnkiDroid/src/main/res/values-be/02-strings.xml @@ -373,4 +373,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-be/10-preferences.xml b/AnkiDroid/src/main/res/values-be/10-preferences.xml index ae794e676d1b..014956a27fe6 100644 --- a/AnkiDroid/src/main/res/values-be/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-be/10-preferences.xml @@ -152,6 +152,8 @@ XXX сек. Час паказу наступнага пытання Выберыце мову + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Статычны рэжым экрана Адключыць усю анімацыю і выкарыстоўваць бяспечны метад для адлюстравання картак. Гэта можа быць актуальна для прылад з электроннымі чарніламі. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-bg/02-strings.xml b/AnkiDroid/src/main/res/values-bg/02-strings.xml index 922a3e3fdc77..1e3f6f3aa986 100644 --- a/AnkiDroid/src/main/res/values-bg/02-strings.xml +++ b/AnkiDroid/src/main/res/values-bg/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-bg/10-preferences.xml b/AnkiDroid/src/main/res/values-bg/10-preferences.xml index 1c6d2af21c7d..05c37370fcaf 100644 --- a/AnkiDroid/src/main/res/values-bg/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-bg/10-preferences.xml @@ -151,6 +151,8 @@ ХХХ с Време за показване на следващия въпрос Изберете език + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Безопасен режим на дисплея Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-bn/02-strings.xml b/AnkiDroid/src/main/res/values-bn/02-strings.xml index f6901fdd0d7c..422a09473ff8 100644 --- a/AnkiDroid/src/main/res/values-bn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-bn/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-bn/10-preferences.xml b/AnkiDroid/src/main/res/values-bn/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-bn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-bn/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ca/02-strings.xml b/AnkiDroid/src/main/res/values-ca/02-strings.xml index c4d647735e70..94867941a446 100644 --- a/AnkiDroid/src/main/res/values-ca/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ca/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ca/10-preferences.xml b/AnkiDroid/src/main/res/values-ca/10-preferences.xml index 3675199eb1e0..35e4987b107b 100644 --- a/AnkiDroid/src/main/res/values-ca/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ca/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Temps per mostrar la següent pregunta Seleccioneu l\'idioma + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Mode d\'exhibició segur Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ckb/02-strings.xml b/AnkiDroid/src/main/res/values-ckb/02-strings.xml index 7076319dae25..df5a6436313b 100644 --- a/AnkiDroid/src/main/res/values-ckb/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ckb/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ckb/10-preferences.xml b/AnkiDroid/src/main/res/values-ckb/10-preferences.xml index df3cf7a61db8..0c650f4631d5 100644 --- a/AnkiDroid/src/main/res/values-ckb/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ckb/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Zimanekî hilbijêre + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-cs/02-strings.xml b/AnkiDroid/src/main/res/values-cs/02-strings.xml index cb2576dabcc1..4a19d2b38964 100644 --- a/AnkiDroid/src/main/res/values-cs/02-strings.xml +++ b/AnkiDroid/src/main/res/values-cs/02-strings.xml @@ -373,4 +373,5 @@ Je požadována e-mailová adresa Je požadováno heslo + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-cs/10-preferences.xml b/AnkiDroid/src/main/res/values-cs/10-preferences.xml index 674c95373c3c..3ae12ddc36e0 100644 --- a/AnkiDroid/src/main/res/values-cs/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-cs/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Čas pro zobrazení další otázky Vyberte jazyk + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Režim bezpečného zobrazení Zakázat všechny animace a použít bezpečnější metodu pro vykreslení karty. E-ink zařízení toto mohou vyžadovat. Zakázat režim úprav v jednom poli diff --git a/AnkiDroid/src/main/res/values-da/02-strings.xml b/AnkiDroid/src/main/res/values-da/02-strings.xml index dc3edbd8d494..3013a4b20169 100644 --- a/AnkiDroid/src/main/res/values-da/02-strings.xml +++ b/AnkiDroid/src/main/res/values-da/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-da/10-preferences.xml b/AnkiDroid/src/main/res/values-da/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-da/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-da/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-de/02-strings.xml b/AnkiDroid/src/main/res/values-de/02-strings.xml index 23ccf9fcbaf0..7ae8b55ef20f 100644 --- a/AnkiDroid/src/main/res/values-de/02-strings.xml +++ b/AnkiDroid/src/main/res/values-de/02-strings.xml @@ -347,4 +347,5 @@ Die E-Mail-Adresse ist erforderlich Das Passwort ist erforderlich + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-de/10-preferences.xml b/AnkiDroid/src/main/res/values-de/10-preferences.xml index acf0d1a22faf..3ecf07dd097e 100644 --- a/AnkiDroid/src/main/res/values-de/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-de/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Zeit bis zur Anzeige der nächsten Frage Sprache wählen + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Sicherer Anzeigemodus Alle Animationen deaktivieren und Karten mit sichererer Methode darstellen. Eventuell erforderlich für E-Ink-Geräte. Einzelfeldbearbeitungsmodus deaktivieren diff --git a/AnkiDroid/src/main/res/values-el/02-strings.xml b/AnkiDroid/src/main/res/values-el/02-strings.xml index 4cfceb3203b6..3f2ae5df7a55 100644 --- a/AnkiDroid/src/main/res/values-el/02-strings.xml +++ b/AnkiDroid/src/main/res/values-el/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-el/10-preferences.xml b/AnkiDroid/src/main/res/values-el/10-preferences.xml index b98b6095d459..20810aad1b0e 100644 --- a/AnkiDroid/src/main/res/values-el/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-el/10-preferences.xml @@ -152,6 +152,8 @@ XXX δ Time to show next question Επιλογή Γλώσσας + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-eo/02-strings.xml b/AnkiDroid/src/main/res/values-eo/02-strings.xml index 92047385eff4..4afa6bd682f9 100644 --- a/AnkiDroid/src/main/res/values-eo/02-strings.xml +++ b/AnkiDroid/src/main/res/values-eo/02-strings.xml @@ -347,4 +347,5 @@ Retpoŝta adreso estas postulata Pasvorto estas postulata + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-eo/10-preferences.xml b/AnkiDroid/src/main/res/values-eo/10-preferences.xml index 41220b832a35..64b8388b22a2 100644 --- a/AnkiDroid/src/main/res/values-eo/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-eo/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Montri sekvan demandon post Elekti lingvon + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Sekura vidiga reĝimo Malaktivigi ĉiujn movbildojn kaj uzi pli sekuran manieron por bildigi kartojn. Elektronik-paperaj aparatoj povas bezoni tion ĉi. Malaktivigi unukampan redakt‑reĝimon 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 377d578ca01a..507103fc7c5a 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml @@ -347,4 +347,5 @@ Se requiere dirección de correo electrónico La contraseña es requerida + Press back again to exit 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 4f903b567267..1a18e3b99f4e 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Tiempo para mostrar la siguiente pregunta Seleccionar idioma + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Modo de visualización segura Desactiva todas las animaciones y utiliza un método más seguro para presentar las tarjetas. Los dspositivos de tinta electrónica (e-ink) podrían requerir esta opción. Desactivar modo de edición de un campo 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 3a39e0344bc9..a56b1780ded7 100644 --- a/AnkiDroid/src/main/res/values-es-rES/02-strings.xml +++ b/AnkiDroid/src/main/res/values-es-rES/02-strings.xml @@ -347,4 +347,5 @@ Se requiere dirección de correo electrónico La contraseña es requerida + Press back again to exit 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 0f6c7f2bb6f6..87a70ee4bbe2 100644 --- a/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Tiempo para mostrar la siguiente pregunta Selecciona idioma + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Modo de visualización segura Deshabilita todas las animaciones y utiliza un método más seguro para dibujar las tarjetas. Dispositivos E-ink podrían requerir esto. Desactivar modo de edición de un campo diff --git a/AnkiDroid/src/main/res/values-et/02-strings.xml b/AnkiDroid/src/main/res/values-et/02-strings.xml index dc7c74c81ef2..570101baea22 100644 --- a/AnkiDroid/src/main/res/values-et/02-strings.xml +++ b/AnkiDroid/src/main/res/values-et/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-et/10-preferences.xml b/AnkiDroid/src/main/res/values-et/10-preferences.xml index 2d9175afd55e..f75091b15682 100644 --- a/AnkiDroid/src/main/res/values-et/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-et/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-eu/02-strings.xml b/AnkiDroid/src/main/res/values-eu/02-strings.xml index 3080300af51a..f2aa7f880851 100644 --- a/AnkiDroid/src/main/res/values-eu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-eu/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-eu/10-preferences.xml b/AnkiDroid/src/main/res/values-eu/10-preferences.xml index 55ff6a3097bd..5211099bd38b 100644 --- a/AnkiDroid/src/main/res/values-eu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-eu/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Hautatu hizkuntza + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-fa/02-strings.xml b/AnkiDroid/src/main/res/values-fa/02-strings.xml index 0035c7364d1b..f314225f31e5 100644 --- a/AnkiDroid/src/main/res/values-fa/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fa/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-fa/10-preferences.xml b/AnkiDroid/src/main/res/values-fa/10-preferences.xml index 567afefb1654..5d8cee5b7f26 100644 --- a/AnkiDroid/src/main/res/values-fa/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fa/10-preferences.xml @@ -151,6 +151,8 @@ XXX ثانیه زمان نمایش پرسش بعدی زبان را انتخاب کنید + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. حالت نمایش محتاطانه غیرفعال کردن همۀ انیمیشن‌ها و استفاده از حالت امن‌تر برای نقاشی روی کارت‌ها. دستگاه‌های E-ink ممکن است نیازمند از گزینه باشند. غیرفعال کردن حالت ویرایش یک فیلد diff --git a/AnkiDroid/src/main/res/values-fi/02-strings.xml b/AnkiDroid/src/main/res/values-fi/02-strings.xml index 6f537a5fa6f9..324844fd01e7 100644 --- a/AnkiDroid/src/main/res/values-fi/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fi/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-fi/10-preferences.xml b/AnkiDroid/src/main/res/values-fi/10-preferences.xml index aac09c6adb17..9e43961a0a36 100644 --- a/AnkiDroid/src/main/res/values-fi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fi/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Aika ennen seuraavan kysymyksen näyttämistä Valitse kieli + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Turvallinen näyttötila Poista kaikki animaatiot käytöstä ja käytä turvallisempaa menetelmää korttien piirtämiseen. E-muste laitteet voivat vaatia tätä. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-fil/02-strings.xml b/AnkiDroid/src/main/res/values-fil/02-strings.xml index 1ee89702f83e..b855635005d0 100644 --- a/AnkiDroid/src/main/res/values-fil/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fil/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-fil/10-preferences.xml b/AnkiDroid/src/main/res/values-fil/10-preferences.xml index 61b3d0b365d6..a213c69ec7de 100644 --- a/AnkiDroid/src/main/res/values-fil/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fil/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Oras upang ipakita ang susunod na tanong Piliin ang wika + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Ligtas na display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-fr/02-strings.xml b/AnkiDroid/src/main/res/values-fr/02-strings.xml index 9a16cbe10e8c..86c818ab639c 100644 --- a/AnkiDroid/src/main/res/values-fr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fr/02-strings.xml @@ -347,4 +347,5 @@ L\'adresse courriel est requise Mot de passe requis + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-fr/10-preferences.xml b/AnkiDroid/src/main/res/values-fr/10-preferences.xml index 8277b76efec8..9d83f37b3b09 100644 --- a/AnkiDroid/src/main/res/values-fr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fr/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Temps avant de montrer la question suivante Sélectionnez la langue + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Mode d’affichage protégé Désactiver toutes les animations et utiliser le mode sécurisé pour afficher les cartes. Les appareils à encre électronique peuvent nécessiter ceci. Désactiver le mode d\'édition à champ unique diff --git a/AnkiDroid/src/main/res/values-fy/02-strings.xml b/AnkiDroid/src/main/res/values-fy/02-strings.xml index d74bb44aac3f..b6ae15acb2e7 100644 --- a/AnkiDroid/src/main/res/values-fy/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fy/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-fy/10-preferences.xml b/AnkiDroid/src/main/res/values-fy/10-preferences.xml index 221a0ceaa551..0977ec86d4ab 100644 --- a/AnkiDroid/src/main/res/values-fy/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fy/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ga/02-strings.xml b/AnkiDroid/src/main/res/values-ga/02-strings.xml index c78f32f69aed..c94cdaa05da8 100644 --- a/AnkiDroid/src/main/res/values-ga/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ga/02-strings.xml @@ -386,4 +386,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ga/10-preferences.xml b/AnkiDroid/src/main/res/values-ga/10-preferences.xml index bddf9191c83a..967ff2e81369 100644 --- a/AnkiDroid/src/main/res/values-ga/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ga/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-gl/02-strings.xml b/AnkiDroid/src/main/res/values-gl/02-strings.xml index 6741a364bc2d..93e4fbaaafa9 100644 --- a/AnkiDroid/src/main/res/values-gl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-gl/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-gl/10-preferences.xml b/AnkiDroid/src/main/res/values-gl/10-preferences.xml index 9aa79343801e..0b510ad38b4a 100644 --- a/AnkiDroid/src/main/res/values-gl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-gl/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Tempo para amosar a seguinte pregunta Seleccionar idioma + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-got/02-strings.xml b/AnkiDroid/src/main/res/values-got/02-strings.xml index 0b846f338c94..1fd6aad7ddaf 100644 --- a/AnkiDroid/src/main/res/values-got/02-strings.xml +++ b/AnkiDroid/src/main/res/values-got/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-got/10-preferences.xml b/AnkiDroid/src/main/res/values-got/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-got/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-got/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-gu/02-strings.xml b/AnkiDroid/src/main/res/values-gu/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-gu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-gu/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-gu/10-preferences.xml b/AnkiDroid/src/main/res/values-gu/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-gu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-gu/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-heb/02-strings.xml b/AnkiDroid/src/main/res/values-heb/02-strings.xml index bf7651e0c4ac..94cfc5cd5c0a 100644 --- a/AnkiDroid/src/main/res/values-heb/02-strings.xml +++ b/AnkiDroid/src/main/res/values-heb/02-strings.xml @@ -373,4 +373,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-heb/10-preferences.xml b/AnkiDroid/src/main/res/values-heb/10-preferences.xml index 126d93837098..80cca9ede33d 100644 --- a/AnkiDroid/src/main/res/values-heb/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-heb/10-preferences.xml @@ -152,6 +152,8 @@ XXX שנ׳ Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-hi/01-core.xml b/AnkiDroid/src/main/res/values-hi/01-core.xml index ac2927a980fe..63ceb98f7606 100644 --- a/AnkiDroid/src/main/res/values-hi/01-core.xml +++ b/AnkiDroid/src/main/res/values-hi/01-core.xml @@ -154,7 +154,7 @@ कस्टम अध्ययन सत्र कृपया पहले मौजूदा कस्टम अध्ययन डेक का नाम बदलें। इस डेक खाली है - Deck Search + डेक खोज कार्ड जोड़ें अमान्य डेक नाम यह डेक खाली है । नई सामग्री जोड़ने के लिए + बटन दबाएँ. diff --git a/AnkiDroid/src/main/res/values-hi/02-strings.xml b/AnkiDroid/src/main/res/values-hi/02-strings.xml index 4f77a4d68744..b244aed3aacc 100644 --- a/AnkiDroid/src/main/res/values-hi/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hi/02-strings.xml @@ -347,4 +347,5 @@ ई-मेल पते की आवश्यकता है पासवर्ड की आवश्यकता है + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-hi/10-preferences.xml b/AnkiDroid/src/main/res/values-hi/10-preferences.xml index 93387e9bbce8..e7c2d240719e 100644 --- a/AnkiDroid/src/main/res/values-hi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hi/10-preferences.xml @@ -151,6 +151,8 @@ XXX s अगला प्रश्न दिखाने का समय भाषा चुनें + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. सुरक्षित प्रदर्शन मोड Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-hr/02-strings.xml b/AnkiDroid/src/main/res/values-hr/02-strings.xml index 9a864a832d50..f3120f6ad517 100644 --- a/AnkiDroid/src/main/res/values-hr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hr/02-strings.xml @@ -360,4 +360,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-hr/10-preferences.xml b/AnkiDroid/src/main/res/values-hr/10-preferences.xml index 54a8ab2e8526..41e5032a1026 100644 --- a/AnkiDroid/src/main/res/values-hr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hr/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-hu/02-strings.xml b/AnkiDroid/src/main/res/values-hu/02-strings.xml index 1e29a2235c36..8f17d7d5983f 100644 --- a/AnkiDroid/src/main/res/values-hu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hu/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-hu/10-preferences.xml b/AnkiDroid/src/main/res/values-hu/10-preferences.xml index 67d6c9e51047..d6f8c95dbc59 100644 --- a/AnkiDroid/src/main/res/values-hu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hu/10-preferences.xml @@ -152,6 +152,8 @@ XXX másodperc A következő kérdés megjelenítése ennyi idő után Nyelv kiválasztása + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Biztonságos megjelenítési mód Kapcsolja ki az összes animációt, és biztonságosabb módszereket használjon a kártya rajzok számára. Az e-tintaeszközök igényelhetik ezt. Egy-Mezőnyi Szerkesztési Mód Kikapcsolása diff --git a/AnkiDroid/src/main/res/values-hy/02-strings.xml b/AnkiDroid/src/main/res/values-hy/02-strings.xml index 6ba797d4e35b..3c9a8d3763a7 100644 --- a/AnkiDroid/src/main/res/values-hy/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hy/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-hy/10-preferences.xml b/AnkiDroid/src/main/res/values-hy/10-preferences.xml index a052e76fe15b..e07d3ad1d3a4 100644 --- a/AnkiDroid/src/main/res/values-hy/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hy/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ind/02-strings.xml b/AnkiDroid/src/main/res/values-ind/02-strings.xml index e774612fe5c3..730a81f011c7 100644 --- a/AnkiDroid/src/main/res/values-ind/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ind/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ind/10-preferences.xml b/AnkiDroid/src/main/res/values-ind/10-preferences.xml index 6e93744c9617..8418ef295f40 100644 --- a/AnkiDroid/src/main/res/values-ind/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ind/10-preferences.xml @@ -152,6 +152,8 @@ XXX dtk Waktu menampilkan pertanyaan selanjutnya Pilih bahasa + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Mode tampilan aman Nonaktifkan semua animasi dan gunakan metode yang lebih aman untuk menggambar kartu. Perangkat E-ink mungkin memerlukan ini. Nonaktifkan mode edit kolom tunggal diff --git a/AnkiDroid/src/main/res/values-is/02-strings.xml b/AnkiDroid/src/main/res/values-is/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-is/02-strings.xml +++ b/AnkiDroid/src/main/res/values-is/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-is/10-preferences.xml b/AnkiDroid/src/main/res/values-is/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-is/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-is/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-it/02-strings.xml b/AnkiDroid/src/main/res/values-it/02-strings.xml index af544e900c12..b30b691a9eb7 100644 --- a/AnkiDroid/src/main/res/values-it/02-strings.xml +++ b/AnkiDroid/src/main/res/values-it/02-strings.xml @@ -347,4 +347,5 @@ L\'indirizzo di posta elettronica è obbligatorio. La password è obbligatoria + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-it/10-preferences.xml b/AnkiDroid/src/main/res/values-it/10-preferences.xml index 7bb07cc39aab..2ddcc4c67f98 100644 --- a/AnkiDroid/src/main/res/values-it/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-it/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Tempo prima di mostrare la prossima domanda Scegli la lingua + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Modalità visualizzazione sicura Disattiva tutte le animazioni e utilizza il metodo più sicuro per disegnare le carte. Dispositivi e-ink possono richiedere questo. Disabilita la modalità Modifica singolo campo diff --git a/AnkiDroid/src/main/res/values-ja/02-strings.xml b/AnkiDroid/src/main/res/values-ja/02-strings.xml index a1c866bdc4fc..8b8ace3c318c 100644 --- a/AnkiDroid/src/main/res/values-ja/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ja/02-strings.xml @@ -333,4 +333,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ja/10-preferences.xml b/AnkiDroid/src/main/res/values-ja/10-preferences.xml index 82126bdcd840..4539dc0ec31e 100644 --- a/AnkiDroid/src/main/res/values-ja/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ja/10-preferences.xml @@ -151,6 +151,8 @@ XXX 秒 次の質問までの時間 言語を選択 + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. 安全表示モード アニメーションを無効にし、より不具合が起こりくい方法でカードを表示します。電子ペーパー端末ではこの設定が必要となる可能性があります。 単一フィールド編集モードを無効にする diff --git a/AnkiDroid/src/main/res/values-jv/02-strings.xml b/AnkiDroid/src/main/res/values-jv/02-strings.xml index 78b22d566f37..267c3c8675ec 100644 --- a/AnkiDroid/src/main/res/values-jv/02-strings.xml +++ b/AnkiDroid/src/main/res/values-jv/02-strings.xml @@ -339,4 +339,5 @@ Dek itu sudah ada Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-jv/10-preferences.xml b/AnkiDroid/src/main/res/values-jv/10-preferences.xml index 1602926af8db..e34753ec51e1 100644 --- a/AnkiDroid/src/main/res/values-jv/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-jv/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ka/02-strings.xml b/AnkiDroid/src/main/res/values-ka/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-ka/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ka/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ka/10-preferences.xml b/AnkiDroid/src/main/res/values-ka/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-ka/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ka/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-kk/02-strings.xml b/AnkiDroid/src/main/res/values-kk/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-kk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-kk/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-kk/10-preferences.xml b/AnkiDroid/src/main/res/values-kk/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-kk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-kk/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-km/02-strings.xml b/AnkiDroid/src/main/res/values-km/02-strings.xml index 22a603f14f07..cefa222a2e20 100644 --- a/AnkiDroid/src/main/res/values-km/02-strings.xml +++ b/AnkiDroid/src/main/res/values-km/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-km/10-preferences.xml b/AnkiDroid/src/main/res/values-km/10-preferences.xml index 1602926af8db..e34753ec51e1 100644 --- a/AnkiDroid/src/main/res/values-km/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-km/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ko/02-strings.xml b/AnkiDroid/src/main/res/values-ko/02-strings.xml index df59bd971ae4..097ac4eb667b 100644 --- a/AnkiDroid/src/main/res/values-ko/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ko/02-strings.xml @@ -337,4 +337,5 @@ Context | Request Context Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ko/10-preferences.xml b/AnkiDroid/src/main/res/values-ko/10-preferences.xml index 33605e225a74..9cfb214f30c8 100644 --- a/AnkiDroid/src/main/res/values-ko/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ko/10-preferences.xml @@ -151,6 +151,8 @@ XXX 초 다음 문제를 표시할 시간 언어 선택 + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. 안전한 디스플레이 모드 모든 애니메이션을 비활성화하고 더 안전한 그리기 방법을 사용합니다. 전자 잉크 기기에는 이 기능이 필요할 수 있습니다. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ku/02-strings.xml b/AnkiDroid/src/main/res/values-ku/02-strings.xml index d5fd8f164373..2670cabb1504 100644 --- a/AnkiDroid/src/main/res/values-ku/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ku/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ku/10-preferences.xml b/AnkiDroid/src/main/res/values-ku/10-preferences.xml index df3cf7a61db8..0c650f4631d5 100644 --- a/AnkiDroid/src/main/res/values-ku/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ku/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Zimanekî hilbijêre + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ky/02-strings.xml b/AnkiDroid/src/main/res/values-ky/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-ky/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ky/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ky/10-preferences.xml b/AnkiDroid/src/main/res/values-ky/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-ky/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ky/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-lt/02-strings.xml b/AnkiDroid/src/main/res/values-lt/02-strings.xml index bb1f47c72821..b46d2819c388 100644 --- a/AnkiDroid/src/main/res/values-lt/02-strings.xml +++ b/AnkiDroid/src/main/res/values-lt/02-strings.xml @@ -374,4 +374,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-lt/10-preferences.xml b/AnkiDroid/src/main/res/values-lt/10-preferences.xml index 61905f339751..b3eaa87b67a6 100644 --- a/AnkiDroid/src/main/res/values-lt/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-lt/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Laikas, po kurio bus rodomas sekantis klausimas Pasirinkite kalbą + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Ribotoji ekrano veiksena Išjungti visą animaciją ir naudoti saugesnį kortelių piešimo būdą. To gali reikėti įrenginiams, kurių veikimas paremtas „E-ink“ technologija. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-lv/02-strings.xml b/AnkiDroid/src/main/res/values-lv/02-strings.xml index b95ace94e58e..f849bae430cc 100644 --- a/AnkiDroid/src/main/res/values-lv/02-strings.xml +++ b/AnkiDroid/src/main/res/values-lv/02-strings.xml @@ -360,4 +360,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-lv/10-preferences.xml b/AnkiDroid/src/main/res/values-lv/10-preferences.xml index ae6449a47a18..b789126f9b0e 100644 --- a/AnkiDroid/src/main/res/values-lv/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-lv/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-mk/02-strings.xml b/AnkiDroid/src/main/res/values-mk/02-strings.xml index 260b03b85e14..be3ac715a6c3 100644 --- a/AnkiDroid/src/main/res/values-mk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-mk/02-strings.xml @@ -348,4 +348,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-mk/10-preferences.xml b/AnkiDroid/src/main/res/values-mk/10-preferences.xml index b0e253e47b8f..88b547a3e41b 100644 --- a/AnkiDroid/src/main/res/values-mk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-mk/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-mn/02-strings.xml b/AnkiDroid/src/main/res/values-mn/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-mn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-mn/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-mn/10-preferences.xml b/AnkiDroid/src/main/res/values-mn/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-mn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-mn/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-mr/02-strings.xml b/AnkiDroid/src/main/res/values-mr/02-strings.xml index cdb9d9f396bf..9ec99ebc0c45 100644 --- a/AnkiDroid/src/main/res/values-mr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-mr/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-mr/10-preferences.xml b/AnkiDroid/src/main/res/values-mr/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-mr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-mr/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ms/02-strings.xml b/AnkiDroid/src/main/res/values-ms/02-strings.xml index 5875ff0dae04..0332e092b51b 100644 --- a/AnkiDroid/src/main/res/values-ms/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ms/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ms/10-preferences.xml b/AnkiDroid/src/main/res/values-ms/10-preferences.xml index 54f4783519f5..7b996a20f21f 100644 --- a/AnkiDroid/src/main/res/values-ms/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ms/10-preferences.xml @@ -152,6 +152,8 @@ minit yang lalu. XXX s Masa untuk menunjukkan pertanyaan berikutnya Pilih Bahasa + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-my/02-strings.xml b/AnkiDroid/src/main/res/values-my/02-strings.xml index 8d3ffb2e6836..48819525f3d2 100644 --- a/AnkiDroid/src/main/res/values-my/02-strings.xml +++ b/AnkiDroid/src/main/res/values-my/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-my/10-preferences.xml b/AnkiDroid/src/main/res/values-my/10-preferences.xml index 1602926af8db..e34753ec51e1 100644 --- a/AnkiDroid/src/main/res/values-my/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-my/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-nl/02-strings.xml b/AnkiDroid/src/main/res/values-nl/02-strings.xml index 0f76fd4c4eff..b74410af6dd1 100644 --- a/AnkiDroid/src/main/res/values-nl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-nl/02-strings.xml @@ -348,4 +348,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-nl/10-preferences.xml b/AnkiDroid/src/main/res/values-nl/10-preferences.xml index 0eb50cb53e26..fc16ffda5b38 100644 --- a/AnkiDroid/src/main/res/values-nl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-nl/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Tijd om de volgende vraag te tonen Taal selecteren + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Veilige weergavemodus Alle animaties uitschakelen en veiligere methode gebruiken om kaarten te trekken. Dit kan nodig zijn voor sommige E-inkt-apparaten. Bewerkingsmodus voor een enkel veld uitschakelen diff --git a/AnkiDroid/src/main/res/values-nn/02-strings.xml b/AnkiDroid/src/main/res/values-nn/02-strings.xml index 2d54b2b4972c..9a112f8b55fc 100644 --- a/AnkiDroid/src/main/res/values-nn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-nn/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-nn/10-preferences.xml b/AnkiDroid/src/main/res/values-nn/10-preferences.xml index 298047ac9135..1b8c4ca57d66 100644 --- a/AnkiDroid/src/main/res/values-nn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-nn/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Velg språk + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-no/02-strings.xml b/AnkiDroid/src/main/res/values-no/02-strings.xml index 2d54b2b4972c..9a112f8b55fc 100644 --- a/AnkiDroid/src/main/res/values-no/02-strings.xml +++ b/AnkiDroid/src/main/res/values-no/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-no/10-preferences.xml b/AnkiDroid/src/main/res/values-no/10-preferences.xml index 298047ac9135..1b8c4ca57d66 100644 --- a/AnkiDroid/src/main/res/values-no/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-no/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Velg språk + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-or/01-core.xml b/AnkiDroid/src/main/res/values-or/01-core.xml index 9831d7ecd0d1..67487ec69ea8 100644 --- a/AnkiDroid/src/main/res/values-or/01-core.xml +++ b/AnkiDroid/src/main/res/values-or/01-core.xml @@ -113,7 +113,7 @@ କମଳା ପତାକା ସବୁଜ ପତାକା ନୀଳ ପତାକା - ଟ୍ୟାଗ୍ ସଂପାଦନ କରନ୍ତୁ + ଟ୍ୟାଗ୍ ସମ୍ପାଦନା ଆପଣ ପ୍ରକୃତରେ ଏହି ନୋଟ୍ ଏବଂ ଏହାର ସମସ୍ତ କାର୍ଡ ବିଲୋପ କରିବାକୁ ଚାହୁଁଛନ୍ତି କି?\n%s ପାଠ୍ୟ ଚୟନ କରନ୍ତୁ %1$s ରେ ଦେଖନ୍ତୁ @@ -154,7 +154,7 @@ ଇଚ୍ଛାରୂପିତ ଅଧ୍ୟୟନ ଅଧିବେଶନ ଆଗ ସାମ୍ପ୍ରତିକ ଇଚ୍ଛାରୂପିତ ଅଧ୍ୟୟନାଧିବେଶନକୁ ପୁନଃନାମିତ କରନ୍ତୁ ତାସଖଣ୍ଡଟି ଖାଲି ଅଛି - Deck Search + ଡେକ୍ ସନ୍ଧାନ କାର୍ଡ ଯୋଡ଼ନ୍ତୁ ଅବୈଧ ତାସଖଣ୍ଡ ନାମ ଏହି ଡେକ୍ ଖାଲି ଅଛି। ନୂତନ ବିଷୟବସ୍ତୁ ଯୋଗ କରିବା ପାଇଁ + ବଟନ୍ ଦବାନ୍ତୁ। diff --git a/AnkiDroid/src/main/res/values-or/02-strings.xml b/AnkiDroid/src/main/res/values-or/02-strings.xml index d2ccf93bef98..4a2b97d70a6b 100644 --- a/AnkiDroid/src/main/res/values-or/02-strings.xml +++ b/AnkiDroid/src/main/res/values-or/02-strings.xml @@ -340,11 +340,12 @@ AnkiDroid କୁ ସର୍ଟକଟ୍ ଯୋଡିବାକୁ ଅନୁମତି ଦେବା ପାଇଁ ଆପଣଙ୍କୁ iManager ବ୍ୟବହାର କରିବାକୁ ପଡିପାରେ ଆପଣଙ୍କର ହୋମ ସ୍କ୍ରିନ AnkiDroid କୁ ସର୍ଟକଟ୍ ଯୋଡିବାକୁ ଅନୁମତି ଦିଏ ନାହିଁ ସର୍ଟକଟ୍ ଯୋଡିବାରେ ତ୍ରୁଟି : %s - ବାକ୍ୟଗୁଡ଼ିଙ୍କ ବଡ଼ କରନ୍ତୁ + ବାକ୍ୟଗୁଡ଼ିକ କ୍ୟାପିଟାଲାଇଜ୍ କରନ୍ତୁ + - ଇମେଲ୍ ଠିକଣା ଆବଶ୍ୟକ ପାସୱାର୍ଡ ଆବଶ୍ୟକ + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-or/03-dialogs.xml b/AnkiDroid/src/main/res/values-or/03-dialogs.xml index ed1e0ba87e53..79181d6a9653 100644 --- a/AnkiDroid/src/main/res/values-or/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-or/03-dialogs.xml @@ -133,7 +133,7 @@ ଦେଖନ୍ତୁ ନିର୍ଦ୍ଦିଷ୍ଟ ପଥ ଏକ ବୈଧ ନିର୍ଦେଶିକା ନୁହେଁ - ମିଡିଆ ଯାଞ୍ଚ କରନ୍ତୁ? + ମିଡିଆ ଯାଞ୍ଚ କରିବେ କି? ବଡ଼ ମିଡିଆ ସଂଗ୍ରହ ସହିତ ଏହା ଏକ ଦୀର୍ଘ ସମୟ ନେଇପାରେ ମିଡିଆ ଯାଞ୍ଚ କରୁଛି… ମିଡିଆ ଯାଞ୍ଚ କରାଯାଇଛି @@ -156,7 +156,7 @@ ବାକି ଖାଲି ପତ୍ର ଖୋଜାହଉଛି - ଆପଣ ବାତିଲ କରିବାକୁ ଚାହାନ୍ତି? + ଆପଣ ବାତିଲ କରିବାକୁ ଚାହୁଁଛନ୍ତି କି? କୌଣସି ଖାଲି କାର୍ଡ ନାହିଁ ବିଲୋପ କରିବାକୁ ପତ୍ର : %d ବିଲୋପିତ ପତ୍ର: %d diff --git a/AnkiDroid/src/main/res/values-or/04-network.xml b/AnkiDroid/src/main/res/values-or/04-network.xml index b2d5a615a7e6..b55b13f621a6 100644 --- a/AnkiDroid/src/main/res/values-or/04-network.xml +++ b/AnkiDroid/src/main/res/values-or/04-network.xml @@ -52,7 +52,7 @@ ଇମେଲ୍ ଠିକଣା ପାସୱାର୍ଡ ଲଗ୍ ଇନ୍ - AnkiWeb ଖାତା ନାହିଁ କି? ଏହା ମାଗଣା! + AnkiWeb ଖାତା ନାହିଁ କି? ଏହା ମାଗଣା ଅଟେ! ସୂଚନା: AnkiWeb AnkiDroid ସହିତ ଜଡ଼ିତ ନୁହେଁ ସାଇନ୍ ଅପ୍ ଭାବେ ଲଗ୍ ଇନ୍ କରିଛନ୍ତି diff --git a/AnkiDroid/src/main/res/values-or/10-preferences.xml b/AnkiDroid/src/main/res/values-or/10-preferences.xml index d424fa55291a..331df1538f40 100644 --- a/AnkiDroid/src/main/res/values-or/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-or/10-preferences.xml @@ -151,6 +151,8 @@ XXX s ପରବର୍ତ୍ତୀ ପ୍ରଶ୍ନ ଦେଖାଇବାର ସମୟ ଆସିଗଲା ଭାଷା ବାଛନ୍ତୁ + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. ସୁରକ୍ଷିତ ପ୍ରଦର୍ଶନ ମୋଡ୍ ସମସ୍ତ ଆନିମେସନ୍ ଅକ୍ଷମ କରନ୍ତୁ ଏବଂ କାର୍ଡ ଅଙ୍କନ ପାଇଁ ସୁରକ୍ଷିତ ପଦ୍ଧତି ବ୍ୟବହାର କରନ୍ତୁ | ଇ-ଇଙ୍କ ଉପକରଣଗୁଡ଼ିକ ଏହା ଆବଶ୍ୟକ କରିପାରନ୍ତି | ଗୋଟିଏ-କ୍ଷେତ୍ର ସଂପାଦନା ମୋଡ୍ କୁ ଅକ୍ଷମ କରନ୍ତୁ diff --git a/AnkiDroid/src/main/res/values-pa/02-strings.xml b/AnkiDroid/src/main/res/values-pa/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-pa/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pa/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-pa/10-preferences.xml b/AnkiDroid/src/main/res/values-pa/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-pa/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pa/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-pl/02-strings.xml b/AnkiDroid/src/main/res/values-pl/02-strings.xml index d4f09db74be2..ba25d7379d7f 100644 --- a/AnkiDroid/src/main/res/values-pl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pl/02-strings.xml @@ -373,4 +373,5 @@ Adres e-mail jest wymagany Hasło jest wymagane + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-pl/10-preferences.xml b/AnkiDroid/src/main/res/values-pl/10-preferences.xml index eb33b353e1f6..eca1624a4c39 100644 --- a/AnkiDroid/src/main/res/values-pl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pl/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Czas do pokazania kolejnego pytania Wybór języka + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Tryb bezpiecznego wyświetlania Wyłącz wszystkie animacje i użyj bezpieczniejszej metody rysowania kart. Urządzenia E-ink mogą tego wymagać. Wyłącz tryb edycji pojedynczego pola 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 15dd56b9d0ae..da4a0414408a 100644 --- a/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit 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 ca3335fade2f..b3ca097f3a77 100644 --- a/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Tempo para mostrar próxima questão Selecionar Idioma + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Modo de exibição segura Desativar todas animações e utilizar método seguro para desenhar cards. Dispositivos E-ink podem exigir isso. Desabilitar Modo de Edição de Campo Único 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 50bd11934520..118e5c138003 100644 --- a/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit 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 eafd434c28cf..7b87eba7e994 100644 --- a/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Tempo para mostrar a pergunta seguinte Selecionar Idioma + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Modo de visualização segura Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ro/02-strings.xml b/AnkiDroid/src/main/res/values-ro/02-strings.xml index 941652a4402b..9fb49c231e54 100644 --- a/AnkiDroid/src/main/res/values-ro/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ro/02-strings.xml @@ -360,4 +360,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ro/10-preferences.xml b/AnkiDroid/src/main/res/values-ro/10-preferences.xml index 8edf3877096d..47e2114cbcc7 100644 --- a/AnkiDroid/src/main/res/values-ro/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ro/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ru/02-strings.xml b/AnkiDroid/src/main/res/values-ru/02-strings.xml index c2c23616d27d..e5b258a50b42 100644 --- a/AnkiDroid/src/main/res/values-ru/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ru/02-strings.xml @@ -372,4 +372,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ru/10-preferences.xml b/AnkiDroid/src/main/res/values-ru/10-preferences.xml index f75a82879150..74c850be1d70 100644 --- a/AnkiDroid/src/main/res/values-ru/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ru/10-preferences.xml @@ -151,6 +151,8 @@ XXX с Показать следующий вопрос через Выберите язык + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Статический режим экрана Отключает всю анимацию и использует безопасную отрисовку. Может понадобиться для экранов на электронных чернилах Отключить режим одного поля diff --git a/AnkiDroid/src/main/res/values-sat/02-strings.xml b/AnkiDroid/src/main/res/values-sat/02-strings.xml index 26c0df27b029..5856eabe1d5d 100644 --- a/AnkiDroid/src/main/res/values-sat/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sat/02-strings.xml @@ -343,4 +343,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-sat/10-preferences.xml b/AnkiDroid/src/main/res/values-sat/10-preferences.xml index fddb76cc8232..566f650963dd 100644 --- a/AnkiDroid/src/main/res/values-sat/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sat/10-preferences.xml @@ -151,6 +151,8 @@ XXX ᱴᱤᱯᱤᱡ ᱤᱱᱟᱹ ᱛᱟᱭᱚᱢ ᱯᱚᱨᱥᱚᱱ ᱫᱮᱠᱷᱟᱣᱼᱟ ᱟᱲᱟᱝ ᱪᱚᱭᱚᱱ + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. ᱡᱟᱹᱯᱛᱤ ᱚᱵᱚᱥᱛᱷᱟ ᱩᱫᱩᱜᱽ ᱢᱚᱰ ᱡᱷᱚᱛᱚ ᱮᱱᱤᱢᱮᱥᱚᱱ ᱵᱚᱱᱫᱽ ᱠᱟᱛᱮ ᱟᱨ ᱴᱷᱤᱠ ᱛᱚᱨᱤᱠᱟ ᱛᱮ ᱠᱟᱰ ᱤᱫᱤ ᱢᱮ ᱾ ᱤᱼᱠᱟᱲᱤ ᱰᱤᱵᱷᱟᱤᱥ ᱫᱚᱨᱠᱟᱨ ᱯᱟᱲᱟᱣ ᱜᱮᱭᱟ ᱾ ᱢᱤᱫᱴᱟᱹᱝ ᱡᱟᱭᱜᱟ ᱥᱟᱯᱰᱟᱣ ᱢᱚᱰ ᱵᱸᱫᱚ diff --git a/AnkiDroid/src/main/res/values-sk/02-strings.xml b/AnkiDroid/src/main/res/values-sk/02-strings.xml index dee6a25e9160..c2a55dbbf2fd 100644 --- a/AnkiDroid/src/main/res/values-sk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sk/02-strings.xml @@ -373,4 +373,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-sk/10-preferences.xml b/AnkiDroid/src/main/res/values-sk/10-preferences.xml index fac6c6b5d6f7..edb0f3e0cc0d 100644 --- a/AnkiDroid/src/main/res/values-sk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sk/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Čas do zobrazenia ďalšej otázky Zvoliť jazyk + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Režim bezpečného zobrazenia Vypnuť všetky animácie a použiť bezpečnejšiu metódu na ťahanie kartičiek. Toto môže byť vyžadované E-ink zariadeniami. Vypnúť mód editácie jedného poľa diff --git a/AnkiDroid/src/main/res/values-sl/02-strings.xml b/AnkiDroid/src/main/res/values-sl/02-strings.xml index f9573a41531d..0fabf4048761 100644 --- a/AnkiDroid/src/main/res/values-sl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sl/02-strings.xml @@ -373,4 +373,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-sl/10-preferences.xml b/AnkiDroid/src/main/res/values-sl/10-preferences.xml index 9e7eaa1ab5ec..b99cbdbde8a0 100644 --- a/AnkiDroid/src/main/res/values-sl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sl/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Časa do naslednjega vprašanja Izberite jezik + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Način varnega prikaza Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-sq/02-strings.xml b/AnkiDroid/src/main/res/values-sq/02-strings.xml index f30d5926636e..7ce498b78f9b 100644 --- a/AnkiDroid/src/main/res/values-sq/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sq/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-sq/10-preferences.xml b/AnkiDroid/src/main/res/values-sq/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-sq/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sq/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-sr/02-strings.xml b/AnkiDroid/src/main/res/values-sr/02-strings.xml index 305a2e925958..060d3a091098 100644 --- a/AnkiDroid/src/main/res/values-sr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sr/02-strings.xml @@ -360,4 +360,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-sr/10-preferences.xml b/AnkiDroid/src/main/res/values-sr/10-preferences.xml index e63c7ada4452..cb1892fe12f0 100644 --- a/AnkiDroid/src/main/res/values-sr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sr/10-preferences.xml @@ -152,6 +152,8 @@ XXX с Време за приказивање следећег питања Изаберите језик + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Безбедан режим приказивања Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ss/02-strings.xml b/AnkiDroid/src/main/res/values-ss/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-ss/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ss/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ss/10-preferences.xml b/AnkiDroid/src/main/res/values-ss/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-ss/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ss/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-sv/02-strings.xml b/AnkiDroid/src/main/res/values-sv/02-strings.xml index d94c39e80341..4728812582bd 100644 --- a/AnkiDroid/src/main/res/values-sv/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sv/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-sv/10-preferences.xml b/AnkiDroid/src/main/res/values-sv/10-preferences.xml index bc188f3cb2f7..440beff49e8a 100644 --- a/AnkiDroid/src/main/res/values-sv/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sv/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Tid för att visa nästa fråga Välj språk + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Säkert visningsläge Stäng av alla animationer och använd en säkrare metod för att rita kort. E-bläckenheter kan kräva detta. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-sw/02-strings.xml b/AnkiDroid/src/main/res/values-sw/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-sw/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sw/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-sw/10-preferences.xml b/AnkiDroid/src/main/res/values-sw/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-sw/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sw/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ta/02-strings.xml b/AnkiDroid/src/main/res/values-ta/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-ta/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ta/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ta/10-preferences.xml b/AnkiDroid/src/main/res/values-ta/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-ta/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ta/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-te/02-strings.xml b/AnkiDroid/src/main/res/values-te/02-strings.xml index 5c31520788f1..15406803384e 100644 --- a/AnkiDroid/src/main/res/values-te/02-strings.xml +++ b/AnkiDroid/src/main/res/values-te/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-te/10-preferences.xml b/AnkiDroid/src/main/res/values-te/10-preferences.xml index 438e08252412..7854ebb3b9a4 100644 --- a/AnkiDroid/src/main/res/values-te/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-te/10-preferences.xml @@ -152,6 +152,8 @@ XXX s తదుపరి ప్రశ్న చూపించడానికి సమయం భాషను ఎంచుకోండి + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. సురక్షిత ప్రదర్శన మోడ్ Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-tg/02-strings.xml b/AnkiDroid/src/main/res/values-tg/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-tg/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tg/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-tg/10-preferences.xml b/AnkiDroid/src/main/res/values-tg/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-tg/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tg/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-tgl/02-strings.xml b/AnkiDroid/src/main/res/values-tgl/02-strings.xml index eed31d82c41a..ba4f463bb5fc 100644 --- a/AnkiDroid/src/main/res/values-tgl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tgl/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-tgl/10-preferences.xml b/AnkiDroid/src/main/res/values-tgl/10-preferences.xml index 0c487e4a8ed8..d83b843d6d9c 100644 --- a/AnkiDroid/src/main/res/values-tgl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tgl/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Oras upang ipakita ang susunod na tanong Piliin ang wika + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Ligtas na display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-th/02-strings.xml b/AnkiDroid/src/main/res/values-th/02-strings.xml index 8d3ffb2e6836..48819525f3d2 100644 --- a/AnkiDroid/src/main/res/values-th/02-strings.xml +++ b/AnkiDroid/src/main/res/values-th/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-th/10-preferences.xml b/AnkiDroid/src/main/res/values-th/10-preferences.xml index 1602926af8db..e34753ec51e1 100644 --- a/AnkiDroid/src/main/res/values-th/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-th/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ti/02-strings.xml b/AnkiDroid/src/main/res/values-ti/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-ti/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ti/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ti/10-preferences.xml b/AnkiDroid/src/main/res/values-ti/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-ti/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ti/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-tn/02-strings.xml b/AnkiDroid/src/main/res/values-tn/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-tn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tn/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-tn/10-preferences.xml b/AnkiDroid/src/main/res/values-tn/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-tn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tn/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-tr/01-core.xml b/AnkiDroid/src/main/res/values-tr/01-core.xml index f3a0d990c279..5c356b95c637 100644 --- a/AnkiDroid/src/main/res/values-tr/01-core.xml +++ b/AnkiDroid/src/main/res/values-tr/01-core.xml @@ -41,11 +41,11 @@ evet Desteler - Kart Tarayıcısı + Kart tarayıcısı İstatistikler Ayarlar Yardım - Geribildirim Gönder + Geribildirim gönder Bugün çıkacak: Toplam yeni kart: Toplam kart: @@ -90,8 +90,8 @@ İçeri aktar Geri Al Son çalışmayı - Deste değiştirmeyi - Bir paket kartın hepsini taşı + Desteleri değiştir + Hepsini desteye taşı Görünür Yap Destenin adını değiştir Kısayol oluştur @@ -154,7 +154,7 @@ Kişiselleştirilmiş çalışma oturumu Lütfen mevcut özelleştirilmiş çalışma destesini yeniden adlandırın. Bu deste boş - Deck Search + Deste Ara Kart ekle Geçersiz deste adı Bu deste boş. Yeni içerik eklemek için + düğmesine basın. diff --git a/AnkiDroid/src/main/res/values-tr/02-strings.xml b/AnkiDroid/src/main/res/values-tr/02-strings.xml index 1d9199100dba..cd8927050859 100644 --- a/AnkiDroid/src/main/res/values-tr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tr/02-strings.xml @@ -107,10 +107,10 @@ Notu Düzenle Kapatıp şu anki girdi kaybedilsin mi? - No cards created. Please fill in more fields - The first field is empty - This note type must contain cloze deletions - Set field language + Hiçbir kart yaratılmadı. Lütfen daha fazla alan doldurun + İlk alan boş + Bu not türü çıkartmalı (boşluk doldurmalı) silme işlemlerini içermelidir + Alanın dilini belirle The current note type did not produce any cards.\nPlease choose another note type, or click ‘Cards’ and add a field substitution Cloze deletions will only work on a Cloze note type Not kaydediliyor @@ -160,8 +160,8 @@ \"%s\" koleksiyona eklensin mi? Bu uzun sürebilir. This isn’t a valid Anki package file Failed to unzip apkg: %s - Failed to copy apkg to %s - apkg failed validation + apkg\'yi %s\'e kopyalama başarısız + apkg onaylama başarısız There is not enough space to unzip the package. Needed %1$d, available %2$d Error importing file, likely a cache clear during processing.\nPlease try again Data import succeeded but post-import cleanup failed. Run check database later. Root cause: %s @@ -276,74 +276,75 @@ Failed to save whiteboard image. %s Whiteboard image saved to %s - Whiteboard pen color + Beyaz tahta kalem rengi Empty Cards.]]> - Empty first field: %s + Boş ilk alan: %s First field matched: %s Added duplicate with first field: %s Appeared twice in file: %s One or more notes were not imported, because they didn\'t generate any cards. This can happen when you have empty fields or when you have not mapped the content in the text file to the correct fields ‘%1$s’ had %2$d fields, expected %3$d - Aborted: %s + İptal edildi: %s Detected automated test. If you are a human, contact AnkiDroid support - %d note added - %d notes added + %d notu eklendi + %d notları eklendi - %d note updated - %d notes updated + %d notu güncellendi + %d notları güncellendi - %d note unchanged - %d notes unchanged + %d notu değiştirilmedi + %d notları değiştirilmedi This card uses unsupported AnkiDroid features. Contact developer %1$s, or view the wiki. %2$s Card provided invalid data. %s Invalid AnkiDroid JS API version. Contact developer %s, or view wiki AnkiDroid JS API update available. Contact developer %s, or view wiki - View - (Error Code: %d) + Görüntüle + (Hata kodu: %d) Copied debug information to clipboard Error copying debug information to clipboard Card Content Error: Failed to load ‘%s’ Card Content Error: Media update required - Loading http resources is no longer supported + Yüklenen HTTP kaynakları artık desteklenmiyor - Failed to load deck ‘%s’ - Search decks - Fatal Error + \'%s\' destesinin yüklenmesi başarısız + Deste ara + Kritik hata AnkiDroid relies on the System WebView which is unavailable. This can happen if the system is installing updates. Please try again in a few minutes.\n\n%s - Font Size - Show Toolbar - Format as Bold - Format as Italic - Format as Underline - Insert Horizontal Line - Insert Heading - Change Font Size - Insert MathJax Equation + Yazı tipi boyutu + Araç Çubuğunu Göster + Kalın olarak biçimlendir + İtalic olarak biçimlendir + Altı çizili olarak biçimlendir + Yatay Çizgi Ekle + Başlık ekle + Yazı Tipi Boyutunu Değiştir + MathJax denklemi ekle Insert Cloze Deletion - HTML Before Selection - HTML After Selection - Create Toolbar Item + Seçimden önce HTML + Seçimden sonra HTML + Araç çubuğu öğesi yarat Enter HTML to be inserted before and after the selected text\n\nLong press a toolbar item to remove it - Remove Toolbar Item? - The image is too large to paste, please insert the image manually - Android backup in progress. Please try again - You may need to use iManager to allow AnkiDroid to add shortcuts - Your home screen does not allow AnkiDroid to add shortcuts - Error adding shortcut: %s - Capitalize sentences + Araç çubuğu öğesini sil? + Görsel yapıştırmak için çok büyük, lütfen görseli elle ekleyin + Android yedeklemesi gerçekleştiriliyor. Lütfen tekrar deneyin + AnkiDroid\'in kısayol eklemesine izin vermek için iManager kullanmanız gerekebilir + Ana ekranın, kısayol ekleyebilmesi için AnkiDroid\'e izin vermiyor + Kısayolu eklerken hata: %s + Cümlelerin baş harflerini büyült + - - Email address is required - Password is required + E-posta adresi gereklidir + Parola gereklidir + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-tr/03-dialogs.xml b/AnkiDroid/src/main/res/values-tr/03-dialogs.xml index 3ea55cb7631a..321214db9618 100644 --- a/AnkiDroid/src/main/res/values-tr/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-tr/03-dialogs.xml @@ -76,8 +76,8 @@ - Reschedule card - Reschedule cards + Kartı yeniden planla + Kartları yeniden planla Current interval: %d day @@ -106,8 +106,8 @@ X kartları rastgele desteden seç: Son x günlerdeki eklenen yeni kartları önizle: Could not rebuild custom study deck - Arşiv içeri aktarılıyor.\nLütfen bekleyin… - Medya dosyaları %1$d%% içeri aktarılıyor.\nLütfen bekleyin… + Koleksiyon içeri aktarılıyor... + Medya dosyaları %1$d%% içeri aktarılıyor... • Notlar aktarılıyor: %1$d%%\n• Kartlar aktarılıyor: %2$d%%\n• Sonişleme: %3$d%% Mevcut %2$d notun %1$d tanesi güncellendi. Bazı güncellemeler not türü değiştiği için yoksayıldı @@ -116,9 +116,9 @@ SD kart neredeyse dolu Yedek geri yüklensin mi? Koleksiyonunuz eski bir kopyayla değiştirilecek. Kaydedilmemiş ilerlemeler kaybolacak. - İptâl + İptal Tamam - No + Hayır Devam Et Oluştur Sil @@ -144,11 +144,11 @@ Kullanılmayan veya eksik dosya bulunmuyor. Medya veritabanı yeniden inşa edildi. Kullanılmayanları sil - Deleting media… - Deletion result + Medya siliniyor... + Silme sonuçları - %d file deleted - %d files deleted + %d dosya silindi + %d dosyaları silindi Tüm kartlar @@ -156,10 +156,10 @@ Çıkacak Boş kartlar aranıyor… - Do you want to cancel? + İptal etmek istiyor musunuz? Boş kart yok Silinecek kartlar %d - Silinmiş Kartlar %d + Silinen kartlar: %d Could not obtain microphone permission. Could not obtain camera permission. Functionality has been disabled. @@ -167,17 +167,17 @@ Audio recording permission denied - Welcome to AnkiDroid! + AnkiDroid\'e Hoşgeldiniz! AnkiDroid requires storage permission, which we use exclusively to store your AnkiDroid collection, flashcard media and backups. Our code is open-source, written by volunteers, and trusted by millions.\n\nIf you have any questions, please access our in-app manual or visit our support forums.\n\nThank you for trying AnkiDroid!\n—AnkiDroid Development Team Unknown error occurred displaying Advanced Editor - AnkiDroid Upgraded + AnkiDroid Güncellendi AnkiDroid has been upgraded. This upgrade includes fixes for possible database problems, and we advise you to run check database now. - Continue Anyway + Yine de devam et Check Database uses a large amount of temporary storage.\n\nIt is strongly recommended that you have at least %s free space on your device before continuing. \n\nYou currently have %s free. %d cards with incorrect home decks were recovered. Please see the manual for more information. - Could not play video + Video oynatılamadı Could not change deck @@ -185,16 +185,16 @@ Failed to process deck options: %s - Database Locked + Veritabanı kilitli The AnkiDroid database is in use by another application. Please close the other application then reopen AnkiDroid. Incompatible Database Version The database is a more advanced version than this version of AnkiDroid can work with. Upgrade AnkiDroid or downgrade the database to open it\n\nSupported version: %1$d\nDatabase version: %2$d\n\nThe following restore options will overwrite your current collection, possibly with a compatible database version: Background image applied Background image removed - No image selected + Görsel seçilmedi Error selecting image. Please see manual. %s - Error deleting image + Görsel silinirken hata Failed to apply background image %s Deck Picker background too large Maximum image size %d MB allowed @@ -207,29 +207,29 @@ Failed to schedule reminders Too many reminders scheduled. Some will not appear - Keyboard Language Selection + Klavye dili seçimi 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. - Using AnkiDroid - AnkiDroid Manual - Anki Manual - AnkiDroid FAQ - Get Help - Mailing List + AnkiDroid kullanımı + AnkiDroid Kılavuzu + Anki Kılavuzu + AnkiDroid SSS + Yardım al + E-Posta Listesi Reddit - Report a Bug - Support AnkiDroid - Donate - Develop - Rate - Other - Community - Anki Forums + Hata Bildir + AnkiDroid\'i destekle + Bağış + Geliştirme + Dğerlendirme + Diğer + Topluluk + Anki Forumları Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Sorun giderme raporu gönder + Rapor zaten gönderildi An automatic sync may be triggered in %d second diff --git a/AnkiDroid/src/main/res/values-tr/04-network.xml b/AnkiDroid/src/main/res/values-tr/04-network.xml index eff5b708e9d2..be719a0f58c2 100644 --- a/AnkiDroid/src/main/res/values-tr/04-network.xml +++ b/AnkiDroid/src/main/res/values-tr/04-network.xml @@ -128,5 +128,5 @@ Senkronizasyon (tam) Senkronizasyon (giriş yap) - Could not connect to AnkiWeb. Is your internet working?\n\n%s + AnkiWeb\'e bağlanılamadı. İnternetinizin çalıştığına emin misiniz?\n\n%s diff --git a/AnkiDroid/src/main/res/values-tr/05-feedback.xml b/AnkiDroid/src/main/res/values-tr/05-feedback.xml index a7d7f8cf976a..6d5cac13cc71 100644 --- a/AnkiDroid/src/main/res/values-tr/05-feedback.xml +++ b/AnkiDroid/src/main/res/values-tr/05-feedback.xml @@ -49,5 +49,5 @@ Hata ayıklama bilgisini kopyala Bildir Hata raporlarını otomatik olarak gönder - No suitable app found + Uygun uygulama bulunamadı diff --git a/AnkiDroid/src/main/res/values-tr/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-tr/07-cardbrowser.xml index ad13898f081b..2b4f0e9b9623 100644 --- a/AnkiDroid/src/main/res/values-tr/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values-tr/07-cardbrowser.xml @@ -48,14 +48,14 @@ Kartları askıya al Desteyi değiştir Kartları askıdan al - Notları işâretle - Notlardan işâreti kaldır + Notları işaretle + Notlardan işareti kaldır Notu sil %s\'e taşındı - İşâretlileri göster + İşaretlileri göster Askıdakileri göster Etikete göre filtrele - Filter by flag + Bayrağa göre filtrele Aramalarım Aramayı kaydet Kayıtlı arama seç @@ -72,48 +72,48 @@ Sıralamadan (daha hızlı) Sıralama alanına göre Oluşturulma zamanına göre - By note modification time - By card modification time + Not düzenleme tarihine göre + Kart düzenleme tarihine göre Zamana göre Aralığa göre Kolaylığa göre - İncelemelere göre + Tekrara göre Süresi dolana göre Hepsini seç Hiçbirini seçme (yeni) - (filtered) - (learning) - No cards found in deck ‘%s’ - Search all decks - Unknown + (filtrelendi) + (öğreniliyor) + %s\'de hiçbir kart bulunamadı + Tüm desteleri ara + Bilinmeyen - %d card deleted - %d cards deleted + %d kartı silindi + %d kartları silindi - Added - First Review - Latest Review + Eklendi + İlk tekrar + Son tekrar Interval - Ease - Reviews + Kolaylık + Tekrarlar Lapses - Average Time - Total Time - Card Type - Note Type - Deck Name - Card Id - Note Id + Ortalama süre + Toplam Süre + Kart tipi + Not tipi + Deste adı + Kart numarası + Not numarası Time - Date - Rating - Type - Learn - Review - Relearn - Filtered - N/A + Tarih + Derecelendirme + Tür + Öğren + Gözden geçir + Yeniden öğren + Filtrelenmiş + Yok diff --git a/AnkiDroid/src/main/res/values-tr/10-preferences.xml b/AnkiDroid/src/main/res/values-tr/10-preferences.xml index 06e4484f9af4..fa1768647ebb 100644 --- a/AnkiDroid/src/main/res/values-tr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tr/10-preferences.xml @@ -78,7 +78,7 @@ Display filenames in card browser Display media filenames in the card browser question/answer fields AnkiDroid dizini - Tamekran modu + Tam ekran modu Devre Dışı Sistem çubuklarını gizle Sistem çubuklarını ve cevap butonlarını gizle @@ -151,6 +151,8 @@ XXX s Sonraki soruyu gösterme zamanı Dil Seç + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Güvenli görüntü modu Bütün animasyonları devredışı bırak ve kartları çizmek için daha güvenli yöntem kullan. Bu e-mürekkepli cihazlar için gerekebilir. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-tr/11-arrays.xml b/AnkiDroid/src/main/res/values-tr/11-arrays.xml index 00fe2e176747..11e4a8ca43a1 100644 --- a/AnkiDroid/src/main/res/values-tr/11-arrays.xml +++ b/AnkiDroid/src/main/res/values-tr/11-arrays.xml @@ -48,7 +48,7 @@ Yeni kartlar ekleniş sırasına göre - Eski çıkanlar ilk önce + Önce en eski görüntülenir Rastgele Artan aralıkta Azalan aralıkta @@ -64,8 +64,8 @@ Yeni kartları ve çalışmaları karıştır - Yeni kartlar çalışmalardan sonra - Yeni kartlar çalışmalardan önce + Yeni kartlar tekrardan sonra + Yeni kartlar tekrardan önce Bilgi kartlarında font belirtilmediği zaman @@ -80,38 +80,38 @@ Koyu - xx-small - x-small - small - medium - large - x-large - xx-large + çok çok küçük + çok küçük + küçük + orta + büyük + çok büyük + çok çok büyük Eylem yok - 1. cevap düğmesi - 2. cevap düğmesi - 3. cevap düğmesi - 4. cevap düğmesi + Cevap butonu 1 + Cevap butonu 2 + Cevap butonu 3 + Cevap butonu 4 Önerilen cevabı (yeşil) ver Önerilenden daha iyi cevap ver - Tag note - Lookup expression - Play media - Abort learning - Toggle Red Flag - Toggle Orange Flag - Toggle Green Flag - Toggle Blue Flag - Remove Flag - Page Up - Page Down - Abort Learning and Sync - Toggle Whiteboard - Record Voice - Replay Voice + Notu etiketlendir + İfadeyi kontrol et + Medyayı oynat + Öğrenmeyi durdur + Kırmızı bayrağı etkinleştir + Turuncu bayrağı etkinleştir + Yeşil bayrağı etkinleştir + Mavi bayrağı etkinleştir + Bayrağı sil + Yukarı + Aşağı + Öğrenmeyi ve senkronizasyonu iptal et + Yazı tahtasını etkinleştir + Ses kaydet + Sesi tekrar oynat diff --git a/AnkiDroid/src/main/res/values-tr/18-standard-models.xml b/AnkiDroid/src/main/res/values-tr/18-standard-models.xml index 7ec337d7ea89..b63997995e57 100644 --- a/AnkiDroid/src/main/res/values-tr/18-standard-models.xml +++ b/AnkiDroid/src/main/res/values-tr/18-standard-models.xml @@ -24,9 +24,9 @@ Arka Metin Ekstra - Add Reverse + Tersini Ekle - Kartlar: %d + Kart: %d Temel Cloze diff --git a/AnkiDroid/src/main/res/values-ts/02-strings.xml b/AnkiDroid/src/main/res/values-ts/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-ts/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ts/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ts/10-preferences.xml b/AnkiDroid/src/main/res/values-ts/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-ts/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ts/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-tt/02-strings.xml b/AnkiDroid/src/main/res/values-tt/02-strings.xml index 7454146a5d21..f0cc7556de17 100644 --- a/AnkiDroid/src/main/res/values-tt/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tt/02-strings.xml @@ -219,7 +219,7 @@ %s сәгатъ - %s days + %s көн %s ай @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-tt/10-preferences.xml b/AnkiDroid/src/main/res/values-tt/10-preferences.xml index 23e2350249c3..356203ed9119 100644 --- a/AnkiDroid/src/main/res/values-tt/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tt/10-preferences.xml @@ -152,6 +152,8 @@ XXX сек Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode @@ -258,7 +260,7 @@ Group management Add and change options groups Current group - Rename + Исемен үзгәртү Restore defaults Restore current options group to default values? Кушу diff --git a/AnkiDroid/src/main/res/values-tt/17-model-manager.xml b/AnkiDroid/src/main/res/values-tt/17-model-manager.xml index 9938d3045551..91d82ff18508 100644 --- a/AnkiDroid/src/main/res/values-tt/17-model-manager.xml +++ b/AnkiDroid/src/main/res/values-tt/17-model-manager.xml @@ -37,7 +37,7 @@ Delete note type - Rename + Исемен үзгәртү Edit cards Add note type diff --git a/AnkiDroid/src/main/res/values-uk/02-strings.xml b/AnkiDroid/src/main/res/values-uk/02-strings.xml index a847d178169a..41ceefc58ebd 100644 --- a/AnkiDroid/src/main/res/values-uk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-uk/02-strings.xml @@ -373,4 +373,5 @@ Необхідно вказати адресу електронної пошти Поле «Пароль» обов\'язкове до заповнення + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-uk/10-preferences.xml b/AnkiDroid/src/main/res/values-uk/10-preferences.xml index 4c14006a15d9..931c5f2ecb0c 100644 --- a/AnkiDroid/src/main/res/values-uk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-uk/10-preferences.xml @@ -152,6 +152,8 @@ XXX сек. Час показу наступного питання Виберіть мову + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Статичний режим екрану Вимкнути всю анімацію та використовувати безпечні методи для відображення карток. Це може бути потрібно для E-ink пристроїв Вимкнути режим редагування одиничного поля diff --git a/AnkiDroid/src/main/res/values-ur/02-strings.xml b/AnkiDroid/src/main/res/values-ur/02-strings.xml index 304aa4c203b7..e4492af1c117 100644 --- a/AnkiDroid/src/main/res/values-ur/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ur/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ur/10-preferences.xml b/AnkiDroid/src/main/res/values-ur/10-preferences.xml index b127815af46c..780655281985 100644 --- a/AnkiDroid/src/main/res/values-ur/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ur/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-uz/02-strings.xml b/AnkiDroid/src/main/res/values-uz/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-uz/02-strings.xml +++ b/AnkiDroid/src/main/res/values-uz/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-uz/10-preferences.xml b/AnkiDroid/src/main/res/values-uz/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-uz/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-uz/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-ve/02-strings.xml b/AnkiDroid/src/main/res/values-ve/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-ve/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ve/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-ve/10-preferences.xml b/AnkiDroid/src/main/res/values-ve/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-ve/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ve/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-vi/02-strings.xml b/AnkiDroid/src/main/res/values-vi/02-strings.xml index e6fbccb1f828..a96223054011 100644 --- a/AnkiDroid/src/main/res/values-vi/02-strings.xml +++ b/AnkiDroid/src/main/res/values-vi/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-vi/10-preferences.xml b/AnkiDroid/src/main/res/values-vi/10-preferences.xml index c5a977f349df..a1f92bc6b08a 100644 --- a/AnkiDroid/src/main/res/values-vi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-vi/10-preferences.xml @@ -151,6 +151,8 @@ XXX s Thời gian để hiển thị câu hỏi tiếp theo Chọn ngôn ngữ + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Chế độ an toàn màn hình Tắt tất cả hoạt ảnh và sử dụng phương pháp an toàn hơn để vẽ thẻ. Các thiết bị E-ink có thể yêu cầu điều này. Tắt chế độ chỉnh sửa trường đơn diff --git a/AnkiDroid/src/main/res/values-wo/02-strings.xml b/AnkiDroid/src/main/res/values-wo/02-strings.xml index 8d3ffb2e6836..48819525f3d2 100644 --- a/AnkiDroid/src/main/res/values-wo/02-strings.xml +++ b/AnkiDroid/src/main/res/values-wo/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-wo/10-preferences.xml b/AnkiDroid/src/main/res/values-wo/10-preferences.xml index 1602926af8db..e34753ec51e1 100644 --- a/AnkiDroid/src/main/res/values-wo/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-wo/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-xh/02-strings.xml b/AnkiDroid/src/main/res/values-xh/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-xh/02-strings.xml +++ b/AnkiDroid/src/main/res/values-xh/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-xh/10-preferences.xml b/AnkiDroid/src/main/res/values-xh/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-xh/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-xh/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-yue/02-strings.xml b/AnkiDroid/src/main/res/values-yue/02-strings.xml index 1b430dd7c62d..9aa4f18e52cb 100644 --- a/AnkiDroid/src/main/res/values-yue/02-strings.xml +++ b/AnkiDroid/src/main/res/values-yue/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-yue/10-preferences.xml b/AnkiDroid/src/main/res/values-yue/10-preferences.xml index 1602926af8db..e34753ec51e1 100644 --- a/AnkiDroid/src/main/res/values-yue/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-yue/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml b/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml index 8477e0d1cb93..a73433c3b02d 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml @@ -149,7 +149,7 @@ 自定义学习的进程 请先重命名现有的自定义学习牌组。 此列表为空 - Deck Search + 牌组搜索 添加卡片 无效的牌组名称 这个牌组是空的,请点击 + 按钮来添加内容。 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 73a98022aa84..9ea259a7be49 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml @@ -334,4 +334,5 @@ 电子邮件地址是必填项 密码是必填项 + Press back again to exit 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 cd3a7dfeb7f3..438e880f03b5 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml @@ -151,6 +151,8 @@ XXX 秒 显示下一个问题时间 选择语言 + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. 安全显示模式 禁用所有动画并使用更安全的方法绘制卡牌。使用墨水屏(E-ink)的设备可能需要此功能。 禁用单字段编辑模式 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 3bf95802fba9..c3b4b04d3514 100644 --- a/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml +++ b/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml @@ -334,4 +334,5 @@ Email address is required Password is required + Press back again to exit 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 de7f5971c033..3cca1d22cc29 100644 --- a/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml @@ -151,6 +151,8 @@ XXX 秒 顯示下一題的時間 選擇語言 + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. 安全顯示模式 關閉所有動畫且使用較安全的方法來繪製卡片。電子墨水裝置可能會需要這個方法。 禁用單域編輯模式 diff --git a/AnkiDroid/src/main/res/values-zu/02-strings.xml b/AnkiDroid/src/main/res/values-zu/02-strings.xml index c5575b57726e..1f3e0f61a620 100644 --- a/AnkiDroid/src/main/res/values-zu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-zu/02-strings.xml @@ -347,4 +347,5 @@ Email address is required Password is required + Press back again to exit diff --git a/AnkiDroid/src/main/res/values-zu/10-preferences.xml b/AnkiDroid/src/main/res/values-zu/10-preferences.xml index 58b2d5b3e9ee..f39852e77b2b 100644 --- a/AnkiDroid/src/main/res/values-zu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zu/10-preferences.xml @@ -152,6 +152,8 @@ XXX s Time to show next question Select language + Disable card hardware render + Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. Safe display mode Disable all animations and use safer method for drawing cards. E-ink devices may require this. Disable Single-Field Edit Mode diff --git a/docs/marketing/localized_description/marketdescription-tr.txt b/docs/marketing/localized_description/marketdescription-tr.txt index 426ad477bf10..9adecea13a19 100644 --- a/docs/marketing/localized_description/marketdescription-tr.txt +++ b/docs/marketing/localized_description/marketdescription-tr.txt @@ -6,7 +6,7 @@ Her türlü şeyi istediğiniz yerde, istediğiniz zaman çalışın. Otobüs yo Kendi bilgi kartı destelerinizi oluşturun veya birçok dil ve konu için hazırlanmış ücretsiz desteleri indirin (6000'den fazla mevcut). -Masaüstü uygulaması Anki veya doğrudan AnkiDroid yoluyla malzeme ekleyin. Uygulama bir sözlükten otomatik olarak malzeme eklemeyi bile destekler! +Masaüstü uygulaması Anki veya doğrudan AnkiDroid yoluyla malzeme ekleyin. Uygulama bir sözlükten otomatik olarak materyal eklemeyi bile destekler! ★ Anahtar özellikler: • desteklenen bilgi kartı içerikleri: yazı, resim, ses, LaTeX @@ -24,10 +24,10 @@ Masaüstü uygulaması Anki veya doğrudan AnkiDroid yoluyla malzeme ekleyin. Uy • kart düzenleyici/ekleyici • kart tarayıcı • tablet düzeni -• var olan toplama dosyalarını kullanma (Anki Masaüstü yoluyla) +• var olan koleksiyon dosyalarını içeri aktarma (Anki Desktop aracılığıyla) • sözlük entegrasyonu (ColorDict, GoldenDict, Leo.org, Aedict, çeşitli web sözlükleri) • sözlük gibi diğer uygulamalardan istek tabanlı kart ekleme -• hâricî yazı tipi desteği +• özel yazı tipi desteği • tam yedekleme sistemi • dokunma ve kaydırmalarla gezinme • kişiselleştirilebilir From 63a34e0ce2e7267c5bcbb23790fdf8d5c2478425 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Sun, 4 Apr 2021 14:36:44 +0100 Subject: [PATCH 050/171] Upgrade ankidroid backend to 0.1.4 * rsdroid-testing: diagnose file not found error * fix OOM via memory limit on DB row results * Add Java-based exceptions for rust errors Fixes #8178 (hopefully) --- AnkiDroid/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 591dab567a61..2834dccd8399 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -248,7 +248,7 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.6' // == Rust conversion (from Anki-Android-Backend on GitHub) == - String backendVersion = "0.1.2" // We want both testing and implementation on the same version + String backendVersion = "0.1.4" // We want both testing and implementation on the same version // build with ./gradlew rsdroid:assembleRelease // In my experience, using `files()` currently requires a reindex operation, which is slow. // implementation files("C:\\GitHub\\Rust-Test\\rsdroid\\build\\outputs\\aar\\rsdroid-release.aar") From d21f972cb493a95f4f5f4e840dc4ccfd42de85f5 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Sun, 4 Apr 2021 15:15:38 +0000 Subject: [PATCH 051/171] Bumped version to 2.15alpha39 @branch-specific --- AnkiDroid/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 2834dccd8399..6091d0cbe298 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -50,8 +50,8 @@ 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=21500138 - versionName="2.15alpha38" + versionCode=21500139 + versionName="2.15alpha39" minSdkVersion 21 //noinspection OldTargetApi - also performed in api/build.fradle targetSdkVersion 29 // change .travis.yml platform download at same time From 4152619d86ced1e9d640b199bc847fdb942064ab Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Sun, 4 Apr 2021 12:18:14 -0500 Subject: [PATCH 052/171] Use JDK-native immutable list implementation for locale list This avoids use of undeclared transitive dependency on library classes available from the google-analytics-java library in it's java7 flavor Related #7056 --- .../java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java index e5032626ebca..de6edc4d43db 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Locale; @@ -49,7 +50,6 @@ import androidx.appcompat.widget.Toolbar; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import java8.util.Lists; /** Locale selection dialog. Note: this must be dismissed onDestroy if not called from an activity implementing LocaleSelectionDialogHandler */ public class LocaleSelectionDialog extends AnalyticsDialogFragment { @@ -198,7 +198,7 @@ public void setLocale(@NonNull Locale locale) { } public LocaleListAdapter(@NonNull Locale[] locales) { - mSelectableLocales = Lists.of(locales); + mSelectableLocales = Collections.unmodifiableList(new ArrayList<>(Arrays.asList(locales))); mCurrentlyVisibleLocales = new ArrayList<>(Arrays.asList(locales)); } From a9d297406066646846e870d403fee363fa7944e5 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 01:19:27 +0100 Subject: [PATCH 053/171] test: add BackendEmulatingOpenConflict Test helper for the future --- .../libanki/backend/DroidBackendFactory.java | 14 +++++ .../BackendEmulatingOpenConflict.java | 60 +++++++++++++++++++ .../BackendEmulatingOpenConflictTest.java | 55 +++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflict.java create mode 100644 AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflictTest.java diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackendFactory.java b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackendFactory.java index 1b8720406bc5..be9bdb325040 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackendFactory.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/backend/DroidBackendFactory.java @@ -23,12 +23,17 @@ import net.ankiweb.rsdroid.RustBackendFailedException; import net.ankiweb.rsdroid.RustCleanup; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import timber.log.Timber; /** Responsible for selection of either the Rust or Java-based backend */ public class DroidBackendFactory { + private static DroidBackend sBackendForTesting; + + /** Intentionally private - use {@link DroidBackendFactory#getInstance(boolean)}} */ private DroidBackendFactory() { @@ -38,8 +43,12 @@ private DroidBackendFactory() { * Obtains an instance of a {@link DroidBackend}. * Each call to this method will generate a separate instance which can handle a new Anki collection */ + @NonNull @RustCleanup("Change back to a constant SYNC_VER") public static DroidBackend getInstance(boolean useBackend) { + if (sBackendForTesting != null) { + return sBackendForTesting; + } BackendFactory backendFactory = null; if (useBackend) { @@ -64,4 +73,9 @@ private static DroidBackend getInstance(@Nullable BackendFactory backendFactory) return new RustDroidBackend(backendFactory); } } + + @VisibleForTesting + public static void setOverride(DroidBackend backend) { + sBackendForTesting = backend; + } } diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflict.java b/AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflict.java new file mode 100644 index 000000000000..8e658f67a7e7 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflict.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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.testutils; + +import com.ichi2.libanki.DB; +import com.ichi2.libanki.backend.DroidBackendFactory; +import com.ichi2.libanki.backend.RustDroidBackend; + +import net.ankiweb.rsdroid.BackendException; +import net.ankiweb.rsdroid.BackendFactory; +import net.ankiweb.rsdroid.RustBackendFailedException; + +import BackendProto.Backend; + +import static org.mockito.Mockito.mock; + +/** Test helper: + * causes getCol to emulate an exception caused by having another AnkiDroid instance open on the same collection + */ +public class BackendEmulatingOpenConflict extends RustDroidBackend { + + public BackendEmulatingOpenConflict(BackendFactory mBackend) { + super(mBackend); + } + + + public static void enable() { + try { + DroidBackendFactory.setOverride(new BackendEmulatingOpenConflict(BackendFactory.createInstance())); + } catch (RustBackendFailedException e) { + throw new RuntimeException(e); + } + } + + + public static void disable() { + DroidBackendFactory.setOverride(null); + } + + + @Override + public DB openCollectionDatabase(String path) { + Backend.BackendError error = mock(Backend.BackendError.class); + throw new BackendException.BackendDbException.BackendDbLockedException(error); + } +} diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflictTest.java b/AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflictTest.java new file mode 100644 index 000000000000..6bbef3cb7381 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/BackendEmulatingOpenConflictTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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.testutils; + +import com.ichi2.anki.CollectionHelper; +import com.ichi2.anki.RobolectricTest; + +import net.ankiweb.rsdroid.BackendException.BackendDbException.BackendDbLockedException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static org.junit.Assert.assertThrows; + +@RunWith(AndroidJUnit4.class) +public class BackendEmulatingOpenConflictTest extends RobolectricTest { + + @Before + @Override + public void setUp() { + super.setUp(); + BackendEmulatingOpenConflict.enable(); + } + + @After + @Override + public void tearDown() { + super.tearDown(); + BackendEmulatingOpenConflict.disable(); + } + + @Test + public void assumeMocksAreValid() { + assertThrows(BackendDbLockedException.class, () -> CollectionHelper.getInstance().getCol(super.getTargetContext())); + } + +} From c956a91b2bea18145be0ca8847fc967d86f4fa61 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 01:23:25 +0100 Subject: [PATCH 054/171] NF: DeckPicker - Extract initial error detection Allows for adding new cases of error Extracts a responsibility from DeckPicker --- .../main/java/com/ichi2/anki/DeckPicker.java | 49 +++++++-------- .../java/com/ichi2/anki/InitialActivity.java | 61 +++++++++++++++++++ 2 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index bc45dd3fe6fb..6d73e3793b87 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -22,7 +22,6 @@ package com.ichi2.anki; import android.Manifest; -import android.animation.Animator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -44,7 +43,6 @@ import android.provider.Settings; import com.afollestad.materialdialogs.GravityEnum; -import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.snackbar.Snackbar; import androidx.annotation.NonNull; @@ -86,6 +84,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anki.CollectionHelper.CollectionIntegrityStorageCheck; +import com.ichi2.anki.InitialActivity.StartupFailure; import com.ichi2.anki.StudyOptionsFragment.StudyOptionsListener; import com.ichi2.anki.analytics.UsageAnalytics; import com.ichi2.anki.dialogs.AsyncDialogFragment; @@ -531,34 +530,36 @@ protected void onCreate(Bundle savedInstanceState) throws SQLException { } else { // Show error dialogs if (Permissions.hasStorageAccessPermission(this)) { - if (!AnkiDroidApp.isSdCardMounted()) { - Timber.i("SD card not mounted"); - onSdCardNotMounted(); - } else if (!CollectionHelper.isCurrentAnkiDroidDirAccessible(this)) { - Timber.i("AnkiDroid directory inaccessible"); - Intent i = Preferences.getPreferenceSubscreenIntent(this, "com.ichi2.anki.prefs.advanced"); - startActivityForResultWithoutAnimation(i, REQUEST_PATH_UPDATE); - Toast.makeText(this, R.string.directory_inaccessible, Toast.LENGTH_LONG).show(); - } else if (isFutureAnkiDroidVersion()) { - Timber.i("Displaying database versioning"); - showDatabaseErrorDialog(DatabaseErrorDialog.INCOMPATIBLE_DB_VERSION); - } else { - Timber.i("Displaying database error"); - showDatabaseErrorDialog(DatabaseErrorDialog.DIALOG_LOAD_FAILED); - } + StartupFailure failure = InitialActivity.getStartupFailureType(this); + handleStartupFailure(failure); } + // firstCollectionOpen should have been called to show a dialog - so no need to do anything here. } mShortAnimDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); } - - private boolean isFutureAnkiDroidVersion() { - try { - return CollectionHelper.isFutureAnkiDroidVersion(this); - } catch (Exception e) { - Timber.w(e, "Could not determine if future AnkiDroid version - assuming not"); - return false; + @VisibleForTesting + void handleStartupFailure(StartupFailure failure) { + switch (failure) { + case SD_CARD_NOT_MOUNTED: + Timber.i("SD card not mounted"); + onSdCardNotMounted(); + break; + case DIRECTORY_NOT_ACCESSIBLE: + Timber.i("AnkiDroid directory inaccessible"); + Intent i = Preferences.getPreferenceSubscreenIntent(this, "com.ichi2.anki.prefs.advanced"); + startActivityForResultWithoutAnimation(i, REQUEST_PATH_UPDATE); + Toast.makeText(this, R.string.directory_inaccessible, Toast.LENGTH_LONG).show(); + break; + case FUTURE_ANKIDROID_VERSION: + Timber.i("Displaying database versioning"); + showDatabaseErrorDialog(DatabaseErrorDialog.INCOMPATIBLE_DB_VERSION); + break; + case DB_ERROR: + default: + Timber.i("Displaying database error"); + showDatabaseErrorDialog(DatabaseErrorDialog.DIALOG_LOAD_FAILED); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java new file mode 100644 index 000000000000..58ee66eed433 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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; + +import android.content.Context; + +import androidx.annotation.CheckResult; +import androidx.annotation.NonNull; +import timber.log.Timber; + +/** Utilities for launching the first activity (currently the DeckPicker) */ +public class InitialActivity { + + private InitialActivity() { + + } + + @NonNull + @CheckResult + public static StartupFailure getStartupFailureType(Context context) { + if (!AnkiDroidApp.isSdCardMounted()) { + return StartupFailure.SD_CARD_NOT_MOUNTED; + } else if (!CollectionHelper.isCurrentAnkiDroidDirAccessible(context)) { + return StartupFailure.DIRECTORY_NOT_ACCESSIBLE; + } else if (isFutureAnkiDroidVersion(context)) { + return StartupFailure.FUTURE_ANKIDROID_VERSION; + } else { + return StartupFailure.DB_ERROR; + } + } + + private static boolean isFutureAnkiDroidVersion(Context context) { + try { + return CollectionHelper.isFutureAnkiDroidVersion(context); + } catch (Exception e) { + Timber.w(e, "Could not determine if future AnkiDroid version - assuming not"); + return false; + } + } + + public enum StartupFailure { + SD_CARD_NOT_MOUNTED, + DIRECTORY_NOT_ACCESSIBLE, + FUTURE_ANKIDROID_VERSION, + DB_ERROR, + } +} From 392e6b48dcbbb20d534e9816523cc72ff7a22cd4 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 01:48:32 +0100 Subject: [PATCH 055/171] Initial Activity Startup: handle locked database --- .../java/com/ichi2/anki/InitialActivity.java | 10 +++ .../com/ichi2/anki/InitialActivityTest.java | 70 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java index 58ee66eed433..90bfbccc27cb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/InitialActivity.java @@ -18,6 +18,8 @@ import android.content.Context; +import net.ankiweb.rsdroid.BackendException; + import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import timber.log.Timber; @@ -39,6 +41,13 @@ public static StartupFailure getStartupFailureType(Context context) { } else if (isFutureAnkiDroidVersion(context)) { return StartupFailure.FUTURE_ANKIDROID_VERSION; } else { + try { + CollectionHelper.getInstance().getCol(context); + } catch (BackendException.BackendDbException.BackendDbLockedException e) { + return StartupFailure.DATABASE_LOCKED; + } catch (Exception ignored) { + + } return StartupFailure.DB_ERROR; } } @@ -57,5 +66,6 @@ public enum StartupFailure { DIRECTORY_NOT_ACCESSIBLE, FUTURE_ANKIDROID_VERSION, DB_ERROR, + DATABASE_LOCKED, } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java new file mode 100644 index 000000000000..7056d33b0c37 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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; + +import android.app.Application; + +import com.ichi2.testutils.BackendEmulatingOpenConflict; +import com.ichi2.testutils.EmptyApplication; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; +import org.robolectric.shadows.ShadowEnvironment; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@RunWith(AndroidJUnit4.class) +@Config(application = EmptyApplication.class) // no point in Application init if we don't use it +public class InitialActivityTest extends RobolectricTest { + @Before + @Override + public void setUp() { + super.setUp(); + BackendEmulatingOpenConflict.enable(); + } + + @After + @Override + public void tearDown() { + super.tearDown(); + BackendEmulatingOpenConflict.disable(); + } + + @Test + public void testInitialActivityResult() { + setupForDatabaseConflict(); + + InitialActivity.StartupFailure f = InitialActivity.getStartupFailureType(getTargetContext()); + + assertThat("A conflict should be returned", f, is(InitialActivity.StartupFailure.DATABASE_LOCKED)); + } + + public static void setupForDatabaseConflict() { + ShadowApplication app = Shadows.shadowOf((Application) ApplicationProvider.getApplicationContext()); + app.grantPermissions(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE); + ShadowEnvironment.setExternalStorageState("mounted"); + } +} From bd639ad715821df2c54f962c983ba4a45b39eefa Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 02:37:19 +0100 Subject: [PATCH 056/171] Fix "database locked" showing corrupt db error On startup, we didn't differentiate between DB locked and DB corrupt even though we had a "DB Locked" screen Due to the Rust DB code, we now lock the database, which makes this error more likely to occur Shows the "DB Locked" screen Note: This is only fixed when we have storage permission on startup Fixes 8075 --- .../main/java/com/ichi2/anki/DeckPicker.java | 4 ++ .../java/com/ichi2/anki/DeckPickerTest.java | 44 +++++++++++++++++-- .../com/ichi2/anki/InitialActivityTest.java | 16 +++++-- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 6d73e3793b87..426b7b19055b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -556,6 +556,10 @@ void handleStartupFailure(StartupFailure failure) { Timber.i("Displaying database versioning"); showDatabaseErrorDialog(DatabaseErrorDialog.INCOMPATIBLE_DB_VERSION); break; + case DATABASE_LOCKED: + Timber.i("Displaying database locked error"); + showDatabaseErrorDialog(DatabaseErrorDialog.DIALOG_DB_LOCKED); + break; case DB_ERROR: default: Timber.i("Displaying database error"); diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java index 282df09163fe..f57430bfeb02 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java @@ -1,24 +1,26 @@ package com.ichi2.anki; import android.content.Context; - -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; - import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; +import com.ichi2.anki.dialogs.DatabaseErrorDialog; import com.ichi2.libanki.Collection; import com.ichi2.libanki.DeckConfig; import com.ichi2.libanki.sched.AbstractSched; +import com.ichi2.testutils.BackendEmulatingOpenConflict; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.Robolectric; import java.util.HashMap; import java.util.Map; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.runners.AndroidJUnit4; + import static com.ichi2.anki.DeckPicker.UPGRADE_VERSION_KEY; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -193,4 +195,38 @@ public void confirmDeckDeletionDeletesEmptyDeck() { assertThat("deck was deleted", getCol().getDecks().count(), is(1)); } + + @Test + public void databaseLockedTest() { + // don't call .onCreate + DeckPickerEx deckPicker = Robolectric.buildActivity(DeckPickerEx.class, new Intent()).get(); + + deckPicker.handleStartupFailure(InitialActivity.StartupFailure.DATABASE_LOCKED); + + assertThat(deckPicker.mDatabaseErrorDialog, is(DatabaseErrorDialog.DIALOG_DB_LOCKED)); + } + + @Test + public void databaseLockedWithPermissionIntegrationTest() { + try { + BackendEmulatingOpenConflict.enable(); + InitialActivityTest.setupForDatabaseConflict(); + + DeckPickerEx d = super.startActivityNormallyOpenCollectionWithIntent(DeckPickerEx.class, new Intent()); + + assertThat("A specific dialog for a conflict should be shown", d.mDatabaseErrorDialog, is(DatabaseErrorDialog.DIALOG_DB_LOCKED)); + } finally { + BackendEmulatingOpenConflict.disable(); + InitialActivityTest.setupForDefault(); + } + } + + private static class DeckPickerEx extends DeckPicker { + private int mDatabaseErrorDialog; + + @Override + public void showDatabaseErrorDialog(int id) { + this.mDatabaseErrorDialog = id; + } + } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java index 7056d33b0c37..00f334fc868b 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/InitialActivityTest.java @@ -55,11 +55,15 @@ public void tearDown() { @Test public void testInitialActivityResult() { - setupForDatabaseConflict(); + try { + setupForDatabaseConflict(); - InitialActivity.StartupFailure f = InitialActivity.getStartupFailureType(getTargetContext()); + InitialActivity.StartupFailure f = InitialActivity.getStartupFailureType(getTargetContext()); - assertThat("A conflict should be returned", f, is(InitialActivity.StartupFailure.DATABASE_LOCKED)); + assertThat("A conflict should be returned", f, is(InitialActivity.StartupFailure.DATABASE_LOCKED)); + } finally { + setupForDefault(); + } } public static void setupForDatabaseConflict() { @@ -67,4 +71,10 @@ public static void setupForDatabaseConflict() { app.grantPermissions(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE); ShadowEnvironment.setExternalStorageState("mounted"); } + + public static void setupForDefault() { + ShadowApplication app = Shadows.shadowOf((Application) ApplicationProvider.getApplicationContext()); + app.denyPermissions(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE); + ShadowEnvironment.setExternalStorageState("removed"); + } } From 732eb1a86096a9220a86a88f2bcec25bcaa76a63 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 02:55:21 +0100 Subject: [PATCH 057/171] Fix crash: permissions granted when db locked On startup, we handled "DB Locked" from onCreate but not from onRequestPermissionsResult This fix ensures the two workflows to use the same checks Fixes 8075 --- .../main/java/com/ichi2/anki/DeckPicker.java | 31 +++++++++++++------ .../java/com/ichi2/anki/DeckPickerTest.java | 26 ++++++++++++++++ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 426b7b19055b..2bb41a592804 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -173,7 +173,8 @@ public class DeckPicker extends NavigationDrawerActivity implements /** * Available options performed by other activities (request codes for onActivityResult()) */ - private static final int REQUEST_STORAGE_PERMISSION = 0; + @VisibleForTesting + static final int REQUEST_STORAGE_PERMISSION = 0; private static final int REQUEST_PATH_UPDATE = 1; public static final int REPORT_FEEDBACK = 4; private static final int LOG_IN_FOR_SYNC = 6; @@ -524,21 +525,31 @@ protected void onCreate(Bundle savedInstanceState) throws SQLException { mReviewSummaryTextView = findViewById(R.id.today_stats_text_view); Timber.i("colOpen: %b", colOpen); + // if permission is denied, firstCollectionOpen() requests it and onRequestPermissionsResult continues execution + if (Permissions.hasStorageAccessPermission(this)) { + handleStartup(colOpen); + } + + + mShortAnimDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); + } + + /** The first call in showing dialogs for startup - error or success */ + private void handleStartup(boolean colOpen) { + // TODO: colOpen is not colIsOpen() if called from onCreate - we should fix this mismatch of terms + // or use the same variable if the semantics should have been equivalent if (colOpen) { // Show any necessary dialogs (e.g. changelog, special messages, etc) - showStartupScreensAndDialogs(preferences, 0); + SharedPreferences sharedPrefs = AnkiDroidApp.getSharedPrefs(this); + showStartupScreensAndDialogs(sharedPrefs, 0); } else { // Show error dialogs - if (Permissions.hasStorageAccessPermission(this)) { - StartupFailure failure = InitialActivity.getStartupFailureType(this); - handleStartupFailure(failure); - } - // firstCollectionOpen should have been called to show a dialog - so no need to do anything here. + StartupFailure failure = InitialActivity.getStartupFailureType(this); + handleStartupFailure(failure); } - - mShortAnimDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); } + @VisibleForTesting void handleStartupFailure(StartupFailure failure) { switch (failure) { @@ -926,7 +937,7 @@ public void onRequestPermissionsResult (int requestCode, @NonNull String[] permi if (requestCode == REQUEST_STORAGE_PERMISSION && permissions.length == 1) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { invalidateOptionsMenu(); - showStartupScreensAndDialogs(AnkiDroidApp.getSharedPrefs(this), 0); + handleStartup(colIsOpen()); } else { // User denied access to file storage so show error toast and display "App Info" Toast.makeText(this, R.string.startup_no_storage_permission, Toast.LENGTH_LONG).show(); diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java index f57430bfeb02..c0acbca305e7 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; +import android.content.pm.PackageManager; import com.ichi2.anki.dialogs.DatabaseErrorDialog; import com.ichi2.libanki.Collection; @@ -221,6 +222,27 @@ public void databaseLockedWithPermissionIntegrationTest() { } } + @Test + public void databaseLockedNoPermissionIntegrationTest() { + // no permissions -> grant permissions -> db locked + try { + InitialActivityTest.setupForDefault(); + BackendEmulatingOpenConflict.enable(); + + DeckPickerEx d = super.startActivityNormallyOpenCollectionWithIntent(DeckPickerEx.class, new Intent()); + + // grant permissions + InitialActivityTest.setupForDatabaseConflict(); + + d.onStoragePermissionGranted(); + + assertThat("A specific dialog for a conflict should be shown", d.mDatabaseErrorDialog, is(DatabaseErrorDialog.DIALOG_DB_LOCKED)); + } finally { + BackendEmulatingOpenConflict.disable(); + InitialActivityTest.setupForDefault(); + } + } + private static class DeckPickerEx extends DeckPicker { private int mDatabaseErrorDialog; @@ -228,5 +250,9 @@ private static class DeckPickerEx extends DeckPicker { public void showDatabaseErrorDialog(int id) { this.mDatabaseErrorDialog = id; } + + public void onStoragePermissionGranted() { + onRequestPermissionsResult(DeckPicker.REQUEST_STORAGE_PERMISSION, new String[] { "" }, new int[] { PackageManager.PERMISSION_GRANTED }); + } } } From d549083eea7b911a1b5df6d3a0dceff44df82d1c Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 01:49:05 +0100 Subject: [PATCH 058/171] NF: fix typo --- .../main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java index 7794d4633427..ea94d5c170ac 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java @@ -41,7 +41,7 @@ public class DatabaseErrorDialog extends AsyncDialogFragment { public static final int DIALOG_FULL_SYNC_FROM_SERVER = 8; /** If the database is locked, all we can do is reset the app */ public static final int DIALOG_DB_LOCKED = 9; - /** If the datbase is at a version higher than what we can currently handle */ + /** If the database is at a version higher than what we can currently handle */ public static final int INCOMPATIBLE_DB_VERSION = 10; // public flag which lets us distinguish between inaccessible and corrupt database From 2eab77f6e77bb609ebdbaddf9f1b72a410e18447 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 03:31:09 +0100 Subject: [PATCH 059/171] Fix "database locked" causing error reports Since it's a handled case, error reports would be noise This adds a hack: sSentExceptionReportHack to be used for unit testing Writing a custom ACRA listener would be a better design for this Fixes 8075 --- AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java | 5 +++++ AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java | 5 +++++ AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java | 3 +++ 3 files changed, 13 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index 8749860cc56b..e91f4a459614 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -193,6 +193,9 @@ public class AnkiDroidApp extends Application { @Nullable private Throwable mWebViewError; + /** HACK: Whether an exception report has been thrown - TODO: Rewrite an ACRA Listener to do this */ + @VisibleForTesting + public static boolean sSentExceptionReportHack; @NonNull public static InputStream getResourceAsStream(@NonNull String name) { @@ -409,6 +412,8 @@ public static void sendExceptionReport(Throwable e, String origin, @Nullable Str public static void sendExceptionReport(Throwable e, String origin, @Nullable String additionalInfo, boolean onlyIfSilent) { UsageAnalytics.sendAnalyticsException(e, false); + sSentExceptionReportHack = true; + if (onlyIfSilent) { String reportMode = getSharedPrefs(getInstance().getApplicationContext()).getString(AnkiDroidApp.FEEDBACK_REPORT_KEY, FEEDBACK_REPORT_ASK); if (!FEEDBACK_REPORT_ALWAYS.equals(reportMode)) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java index b650d0d6a738..b3e58558b0ec 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java @@ -33,6 +33,8 @@ import com.ichi2.preferences.PreferenceExtensions; import com.ichi2.utils.FileUtil; +import net.ankiweb.rsdroid.BackendException; + import java.io.File; import java.io.IOException; @@ -151,6 +153,9 @@ public synchronized Time getTimeSafe(Context context) { public synchronized Collection getColSafe(Context context) { try { return getCol(context); + } catch (BackendException.BackendDbException.BackendDbLockedException e) { + Timber.w(e); + return null; } catch (Exception e) { Timber.w(e); AnkiDroidApp.sendExceptionReport(e, "CollectionHelper.getColSafe"); diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java index c0acbca305e7..bb2095bc2116 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.java @@ -209,6 +209,7 @@ public void databaseLockedTest() { @Test public void databaseLockedWithPermissionIntegrationTest() { + AnkiDroidApp.sSentExceptionReportHack = false; try { BackendEmulatingOpenConflict.enable(); InitialActivityTest.setupForDatabaseConflict(); @@ -216,6 +217,8 @@ public void databaseLockedWithPermissionIntegrationTest() { DeckPickerEx d = super.startActivityNormallyOpenCollectionWithIntent(DeckPickerEx.class, new Intent()); assertThat("A specific dialog for a conflict should be shown", d.mDatabaseErrorDialog, is(DatabaseErrorDialog.DIALOG_DB_LOCKED)); + + assertThat("No exception reports should be thrown", AnkiDroidApp.sSentExceptionReportHack, is(false)); } finally { BackendEmulatingOpenConflict.disable(); InitialActivityTest.setupForDefault(); From 43a41a07103a29b8d94d7e8f89117030d48c9deb Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 02:28:58 +0100 Subject: [PATCH 060/171] NF: Add BackupManagerTestUtilities This would have been useful for testing the DeckPicker, but I picked a different strategy. It's still a useful abstraction, even though the calling test is currently ignored. --- .../anki/BackupManagerIntegrationTest.java | 14 +++--- .../testutils/BackupManagerTestUtilities.java | 44 +++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 AnkiDroid/src/test/java/com/ichi2/testutils/BackupManagerTestUtilities.java diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/BackupManagerIntegrationTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/BackupManagerIntegrationTest.java index 8b420f4a9aa9..0732ce71b321 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/BackupManagerIntegrationTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/BackupManagerIntegrationTest.java @@ -18,11 +18,11 @@ import com.ichi2.async.CollectionTask; import com.ichi2.testutils.AnkiAssert; +import com.ichi2.testutils.BackupManagerTestUtilities; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.shadows.ShadowStatFs; import java.io.File; @@ -51,12 +51,16 @@ public void restoreBackupLeavesCollectionWritable() throws InterruptedException private String createBackup() { - int blockCount = 100000; - ShadowStatFs.registerStats(new File(getCol().getPath()).getParentFile().getPath(), blockCount, blockCount, blockCount); + try { + BackupManagerTestUtilities.setupSpaceForBackup(getTargetContext()); - assertThat("Backup should work", BackupManager.performBackupInBackground(getCol().getPath(), getCol().getTime()), is(true)); + assertThat("Backup should work", BackupManager.performBackupInBackground(getCol().getPath(), getCol().getTime()), is(true)); + + return spinUntilBackupExists(1000); + } finally { + BackupManagerTestUtilities.reset(); + } - return spinUntilBackupExists(1000); } diff --git a/AnkiDroid/src/test/java/com/ichi2/testutils/BackupManagerTestUtilities.java b/AnkiDroid/src/test/java/com/ichi2/testutils/BackupManagerTestUtilities.java new file mode 100644 index 000000000000..77ecac02467b --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/testutils/BackupManagerTestUtilities.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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.testutils; + +import android.content.Context; + +import com.ichi2.anki.BackupManager; +import com.ichi2.anki.CollectionHelper; + +import org.robolectric.shadows.ShadowStatFs; + +import java.io.File; + +import static org.junit.Assert.assertTrue; + +public class BackupManagerTestUtilities { + public static void setupSpaceForBackup(Context context) { + String currentAnkiDroidDirectory = CollectionHelper.getCurrentAnkiDroidDirectory(context); + + String path = new File(currentAnkiDroidDirectory).getParentFile().getPath(); + ShadowStatFs.registerStats(path, 100, 20, 10000); + + assertTrue(BackupManager.enoughDiscSpace(currentAnkiDroidDirectory)); + } + + + public static void reset() { + ShadowStatFs.reset(); + } +} From aa0c51bcae88ffbfa76224c27beed0c6d923dcd9 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 5 Apr 2021 23:46:28 +0100 Subject: [PATCH 061/171] Code Style: Fix Constant naming Should be ALL_CAPS_WITH_UNDERSCORES --- .../ichi2/anki/tests/ContentProviderTest.java | 8 +- .../ichi2/anki/AbstractFlashcardViewer.java | 2 +- .../main/java/com/ichi2/anki/DeckOptions.java | 2 +- .../java/com/ichi2/anki/ModelBrowser.java | 11 +-- .../main/java/com/ichi2/anki/NoteEditor.java | 4 +- .../ichi2/anki/cardviewer/TypedAnswer.java | 2 +- .../com/ichi2/anki/dialogs/DialogHandler.java | 4 +- .../activity/MultimediaEditFieldActivity.java | 6 +- .../java/com/ichi2/async/CollectionTask.java | 39 ++++---- .../java/com/ichi2/libanki/Collection.java | 2 +- .../main/java/com/ichi2/libanki/Decks.java | 19 ++-- .../main/java/com/ichi2/libanki/LaTeX.java | 15 ++- .../main/java/com/ichi2/libanki/Media.java | 8 +- .../main/java/com/ichi2/libanki/Models.java | 8 +- .../main/java/com/ichi2/libanki/Sound.java | 6 +- .../java/com/ichi2/libanki/StdModels.java | 28 +++--- .../main/java/com/ichi2/libanki/Storage.java | 12 +-- .../ichi2/libanki/importer/Anki2Importer.java | 2 +- .../ichi2/libanki/template/ParsedNode.java | 13 +-- .../libanki/template/TemplateFilters.java | 10 +- .../com/ichi2/libanki/template/Tokenizer.java | 32 +++---- .../java/com/ichi2/utils/BooleanGetter.java | 4 +- .../java/com/ichi2/utils/DeckComparator.java | 7 +- .../com/ichi2/utils/DeckNameComparator.java | 2 +- .../com/ichi2/utils/NamedJSONComparator.java | 2 +- .../com/ichi2/libanki/CollectionTest.java | 4 +- .../ichi2/libanki/template/TokenizerTest.java | 96 +++++++++---------- 27 files changed, 161 insertions(+), 187 deletions(-) diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.java b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.java index 0f5b20ad054e..f24bd9865ff3 100644 --- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.java +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.java @@ -35,7 +35,6 @@ import com.ichi2.anki.CollectionHelper; import com.ichi2.anki.FlashCardsContract; import com.ichi2.anki.exception.ConfirmModSchemaException; -import com.ichi2.async.CollectionTask; import com.ichi2.async.TaskManager; import com.ichi2.libanki.Card; import com.ichi2.libanki.Collection; @@ -43,7 +42,6 @@ import com.ichi2.libanki.Deck; import com.ichi2.libanki.Decks; import com.ichi2.libanki.Model; -import com.ichi2.libanki.Models; import com.ichi2.libanki.Note; import com.ichi2.libanki.sched.AbstractSched; import com.ichi2.libanki.StdModels; @@ -156,7 +154,7 @@ public void setUp() throws Exception { // Do not teardown if setup was aborted // Add a new basic model that we use for testing purposes (existing models could potentially be corrupted) - Model model = StdModels.basicModel.add(col, BASIC_MODEL_NAME); + Model model = StdModels.BASIC_MODEL.add(col, BASIC_MODEL_NAME); mModelId = model.getLong("id"); List fields = model.getFieldsNames(); // Use the names of the fields as test values for the notes which will be added @@ -287,7 +285,7 @@ public void testInsertTemplate() throws Exception { final ContentResolver cr = getContentResolver(); Collection col = getCol(); // Add a new basic model that we use for testing purposes (existing models could potentially be corrupted) - Model model = StdModels.basicModel.add(col, BASIC_MODEL_NAME); + Model model = StdModels.BASIC_MODEL.add(col, BASIC_MODEL_NAME); long modelId = model.getLong("id"); // Add the note Uri modelUri = ContentUris.withAppendedId(FlashCardsContract.Model.CONTENT_URI, modelId); @@ -324,7 +322,7 @@ public void testInsertField() throws Exception { // Get required objects for test final ContentResolver cr = getContentResolver(); Collection col = getCol(); - Model model = StdModels.basicModel.add(col, BASIC_MODEL_NAME); + Model model = StdModels.BASIC_MODEL.add(col, BASIC_MODEL_NAME); long modelId = model.getLong("id"); JSONArray initialFieldsArr = model.getJSONArray("flds"); int initialFieldCount = initialFieldsArr.length(); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 3dc10c3efd85..f0b3cbde1454 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -3185,7 +3185,7 @@ private String removeFrontSideAudio(String answerContent) { String newAnswerContent = answerContent; if (answerFormat.contains("{{FrontSide}}")) { // possible audio removal necessary String frontSideFormat = mCurrentCard._getQA(false).get("q"); - Matcher audioReferences = Sound.sSoundPattern.matcher(frontSideFormat); + Matcher audioReferences = Sound.SOUND_PATTERN.matcher(frontSideFormat); // remove the first instance of audio contained in "{{FrontSide}}" while (audioReferences.find()) { newAnswerContent = newAnswerContent.replaceFirst(Pattern.quote(audioReferences.group()), ""); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java index 0cb11685d042..324ff9ad2a88 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java @@ -808,7 +808,7 @@ protected void updateSummaries() { protected void buildLists() { android.preference.ListPreference deckConfPref = (android.preference.ListPreference) findPreference("deckConf"); ArrayList confs = mCol.getDecks().allConf(); - Collections.sort(confs, NamedJSONComparator.instance); + Collections.sort(confs, NamedJSONComparator.INSTANCE); String[] confValues = new String[confs.size()]; String[] confLabels = new String[confs.size()]; for (int i = 0; i < confs.size(); i++) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java index caa75aa60927..e1cde9c6ca29 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java @@ -48,7 +48,6 @@ import com.ichi2.libanki.Model; import com.ichi2.libanki.StdModels; import com.ichi2.ui.FixedEditText; -import com.ichi2.utils.Triple; import com.ichi2.widget.WidgetStatus; import java.util.ArrayList; @@ -309,13 +308,13 @@ private void addNewNoteTypeDialog() { //Populates arrayadapters listing the mModels (includes prefixes/suffixes) int existingModelSize = (mModels == null) ? 0 : mModels.size(); - int stdModelSize = StdModels.stdModels.length; + int stdModelSize = StdModels.STD_MODELS.length; ArrayList newModelLabels = new ArrayList<>(existingModelSize + stdModelSize); ArrayList existingModelsNames = new ArrayList<>(existingModelSize); //Used to fetch model names mNewModelNames = new ArrayList<>(stdModelSize); - for (StdModels StdModels: StdModels.stdModels) { + for (StdModels StdModels: StdModels.STD_MODELS) { String defaultName = StdModels.getDefaultName(); newModelLabels.add(String.format(add, defaultName)); mNewModelNames.add(defaultName); @@ -383,14 +382,14 @@ private void addNewNoteTypeDialog() { private void addNewNoteType(String modelName, int position) { Model model; if (modelName.length() > 0) { - int nbStdModels = StdModels.stdModels.length; + int nbStdModels = StdModels.STD_MODELS.length; if (position < nbStdModels) { - model = StdModels.stdModels[position].add(col); + model = StdModels.STD_MODELS[position].add(col); } else { //New model //Model that is being cloned Model oldModel = mModels.get(position - nbStdModels).deepClone(); - Model newModel = StdModels.basicModel.add(col); + Model newModel = StdModels.BASIC_MODEL.add(col); oldModel.put("id", newModel.getLong("id")); model = oldModel; } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java index 5b012ebcb4f2..2eba5468e10d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java @@ -569,7 +569,7 @@ protected void onCollectionLoaded(Collection col) { // Note type Selector mNoteTypeSpinner = findViewById(R.id.note_type_spinner); ArrayList models = getCol().getModels().all(); - Collections.sort(models, NamedJSONComparator.instance); + Collections.sort(models, NamedJSONComparator.INSTANCE); final ArrayList modelNames = new ArrayList<>(models.size()); mAllModelIds = new ArrayList<>(models.size()); for (JSONObject m : models) { @@ -606,7 +606,7 @@ public View getDropDownView(int position, View convertView, ViewGroup parent) { mNoteDeckSpinner = findViewById(R.id.note_deck_spinner); ArrayList decks = getCol().getDecks().all(); - Collections.sort(decks, DeckComparator.instance); + Collections.sort(decks, DeckComparator.INSTANCE); final ArrayList deckNames = new ArrayList<>(decks.size()); mAllDeckIds = new ArrayList<>(decks.size()); for (Deck d : decks) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypedAnswer.java b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypedAnswer.java index d04b056b7d4f..20e2070ae0e7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypedAnswer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/cardviewer/TypedAnswer.java @@ -42,7 +42,7 @@ public static String cleanCorrectAnswer(String answer) { String answerText = matcher.replaceAll(""); matcher = sBrPattern.matcher(answerText); answerText = matcher.replaceAll("\n"); - matcher = Sound.sSoundPattern.matcher(answerText); + matcher = Sound.SOUND_PATTERN.matcher(answerText); answerText = matcher.replaceAll(""); return Utils.nfcNormalized(answerText); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.java index c31a67bb71d6..2cc0ca52175a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DialogHandler.java @@ -46,7 +46,7 @@ public class DialogHandler extends Handler { public static final int MSG_SHOW_FORCE_FULL_SYNC_DIALOG = 7; public static final int MSG_DO_SYNC = 8; - public static final String[] sMessageNameList = { + public static final String[] MESSAGE_NAME_LIST = { "CollectionLoadErrorDialog", "ImportReplaceDialog", "ImportAddDialog", @@ -70,7 +70,7 @@ public DialogHandler(AnkiActivity activity) { @Override public void handleMessage(Message msg) { Bundle msgData = msg.getData(); - String messageName = sMessageNameList[msg.what]; + String messageName = MESSAGE_NAME_LIST[msg.what]; UsageAnalytics.sendAnalyticsScreenView(messageName); Timber.i("Handling Message: %s", messageName); if (msg.what == MSG_SHOW_COLLECTION_LOADING_ERROR_DIALOG) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java index 05f5623fa13a..81f888c51d48 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java @@ -68,7 +68,7 @@ public class MultimediaEditFieldActivity extends AnkiActivity private static final int REQUEST_AUDIO_PERMISSION = 0; private static final int REQUEST_CAMERA_PERMISSION = 1; - public static final int sImageLimit = 1024 * 1024; // 1MB in bytes + public static final int IMAGE_LIMIT = 1024 * 1024; // 1MB in bytes private IField mField; private IMultimediaEditableNote mNote; @@ -263,8 +263,8 @@ protected void done() { bChangeToText = true; } else { long length = f.length(); - if (length > sImageLimit) { - showLargeFileCropDialog((float) (1.0 * length / sImageLimit)); + if (length > IMAGE_LIMIT) { + showLargeFileCropDialog((float) (1.0 * length / IMAGE_LIMIT)); return; } } diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index 32a2d81ec904..0aec4b615d2e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -64,7 +64,6 @@ import com.ichi2.utils.PairWithBoolean; import com.ichi2.utils.PairWithCard; import com.ichi2.utils.SyncStatus; -import com.ichi2.utils.ThreadUtil; import com.ichi2.utils.Triple; import java.io.File; @@ -94,8 +93,8 @@ import static com.ichi2.async.TaskManager.setLatestInstance; import static com.ichi2.libanki.Card.deepCopyCardArray; import static com.ichi2.libanki.Undoable.*; -import static com.ichi2.utils.BooleanGetter.False; -import static com.ichi2.utils.BooleanGetter.True; +import static com.ichi2.utils.BooleanGetter.FALSE; +import static com.ichi2.utils.BooleanGetter.TRUE; /** * Loading in the background, so that AnkiDroid does not look like frozen. @@ -310,9 +309,9 @@ protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener all() { */ public ArrayList allSorted() { ArrayList decks = all(); - Collections.sort(decks, DeckComparator.instance); + Collections.sort(decks, DeckComparator.INSTANCE); return decks; } @VisibleForTesting public List allSortedNames() { List names = allNames(); - Collections.sort(names, DeckNameComparator.instance); + Collections.sort(names, DeckNameComparator.INSTANCE); return names; } @@ -822,7 +821,7 @@ public void updateConf(DeckConfig g) { public long confId(String name) { - return confId(name, defaultConf); + return confId(name, DEFAULT_CONF); } @@ -883,7 +882,7 @@ public List didsForConf(DeckConfig conf) { public void restoreToDefault(DeckConfig conf) { int oldOrder = conf.getJSONObject("new").getInt("order"); - DeckConfig _new = new DeckConfig(defaultConf); + DeckConfig _new = new DeckConfig(DEFAULT_CONF); _new.put("id", conf.getLong("id")); _new.put("name", conf.getString("name")); mDconf.put(conf.getLong("id"), _new); @@ -1135,7 +1134,7 @@ public Node childMap() { // Go through all decks, sorted by name ArrayList decks = all(); - Collections.sort(decks, DeckComparator.instance); + Collections.sort(decks, DeckComparator.INSTANCE); for (Deck deck : decks) { Node node = new Node(); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/LaTeX.java b/AnkiDroid/src/main/java/com/ichi2/libanki/LaTeX.java index e19f16a9f836..79344a40f603 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/LaTeX.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/LaTeX.java @@ -18,10 +18,7 @@ package com.ichi2.libanki; -import android.text.Html; - import com.ichi2.utils.HtmlUtils; -import com.ichi2.utils.JSONObject; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,11 +44,11 @@ public class LaTeX { /** * Patterns used to identify LaTeX tags */ - public static final Pattern sStandardPattern = Pattern.compile("\\[latex](.+?)\\[/latex]", + public static final Pattern STANDARD_PATTERN = Pattern.compile("\\[latex](.+?)\\[/latex]", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); - public static final Pattern sExpressionPattern = Pattern.compile("\\[\\$](.+?)\\[/\\$]", + public static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\[\\$](.+?)\\[/\\$]", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); - public static final Pattern sMathPattern = Pattern.compile("\\[\\$\\$](.+?)\\[/\\$\\$]", + public static final Pattern MATH_PATTERN = Pattern.compile("\\[\\$\\$](.+?)\\[/\\$\\$]", Pattern.DOTALL | Pattern.CASE_INSENSITIVE); @@ -67,20 +64,20 @@ public static String mungeQA(String html, Collection col, Model model) { @VisibleForTesting public static String mungeQA(String html, Media m, Model model) { StringBuffer sb = new StringBuffer(); - Matcher matcher = sStandardPattern.matcher(html); + Matcher matcher = STANDARD_PATTERN.matcher(html); while (matcher.find()) { matcher.appendReplacement(sb, _imgLink(matcher.group(1), model, m)); } matcher.appendTail(sb); - matcher = sExpressionPattern.matcher(sb.toString()); + matcher = EXPRESSION_PATTERN.matcher(sb.toString()); sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, _imgLink("$" + matcher.group(1) + "$", model, m)); } matcher.appendTail(sb); - matcher = sMathPattern.matcher(sb.toString()); + matcher = MATH_PATTERN.matcher(sb.toString()); sb = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(sb, diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Media.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Media.java index dbe06f3fd3a4..e96609613916 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Media.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Media.java @@ -114,7 +114,7 @@ public class Media { */ private static final Pattern fImgRegExpU = Pattern.compile("(?i)(]* src=(?!['\"])([^ >]+)[^>]*?>)"); - public static final List mRegexps = Arrays.asList(fSoundRegexps, fImgRegExpQ, fImgRegExpU); + public static final List REGEXPS = Arrays.asList(fSoundRegexps, fImgRegExpQ, fImgRegExpU); private final Collection mCol; private final String mDir; @@ -324,7 +324,7 @@ public List filesInStr(Long mid, String string, boolean includeRemote) { s = LaTeX.mungeQA(s, mCol, model); // extract filenames Matcher m; - for (Pattern p : mRegexps) { + for (Pattern p : REGEXPS) { // NOTE: python uses the named group 'fname'. Java doesn't have named groups, so we have to determine // the index based on which pattern we are using int fnameIdx = p.equals(fSoundRegexps) ? 2 : p.equals(fImgRegExpU) ? 2 : 3; @@ -350,7 +350,7 @@ private List _expandClozes(String string) { ords.add(m.group(1)); } ArrayList strings = new ArrayList<>(ords.size() + 1); - String clozeReg = TemplateFilters.clozeReg; + String clozeReg = TemplateFilters.CLOZE_REG; for (String ord : ords) { StringBuffer buf = new StringBuffer(); @@ -378,7 +378,7 @@ private List _expandClozes(String string) { * @return The media-free string. */ public String strip(String txt) { - for (Pattern p : mRegexps) { + for (Pattern p : REGEXPS) { txt = p.matcher(txt).replaceAll(""); } return txt; diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java index 86ffaa1ce48d..9a39c1f51646 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java @@ -49,10 +49,8 @@ import androidx.annotation.NonNull; -import static com.ichi2.libanki.Models.AllowEmpty.FALSE; import static com.ichi2.libanki.Models.AllowEmpty.ONLY_CLOZE; import static com.ichi2.libanki.Models.AllowEmpty.TRUE; -import static com.ichi2.libanki.Utils.trimArray; @SuppressWarnings({"PMD.ExcessiveClassLength", "PMD.AvoidThrowingRawExceptionTypes","PMD.AvoidReassigningParameters", "PMD.NPathComplexity","PMD.MethodNamingConventions", @@ -72,7 +70,7 @@ public class Models { @SuppressWarnings("RegExpRedundantEscape") private static final Pattern fClozeOrdPattern = Pattern.compile("(?si)\\{\\{c(\\d+)::.*?\\}\\}"); - public static final String defaultModel = + public static final String DEFAULT_MODEL = "{'sortf': 0, " + "'did': 1, " + "'latexPre': \"" @@ -233,7 +231,7 @@ public void flush() { public boolean ensureNotEmpty() { if (mModels.isEmpty()) { // TODO: Maybe we want to restore all models if we don't have any - StdModels.basicModel.add(mCol); + StdModels.BASIC_MODEL.add(mCol); return true; } else { return false; @@ -314,7 +312,7 @@ public Model byName(String name) { // not in python. Thus the method has to be renamed. public Model newModel(String name) { // caller should call save() after modifying - Model m = new Model(defaultModel); + Model m = new Model(DEFAULT_MODEL); m.put("name", name); m.put("mod", mCol.getTime().intTime()); m.put("flds", new JSONArray()); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java index 2fb841ca97d3..513950dc8715 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java @@ -60,7 +60,7 @@ public class Sound { /** * Pattern used to identify the markers for sound files */ - public static final Pattern sSoundPattern = Pattern.compile("\\[sound:([^\\[\\]]*)]"); + public static final Pattern SOUND_PATTERN = Pattern.compile("\\[sound:([^\\[\\]]*)]"); /** * Pattern used to parse URI (according to http://tools.ietf.org/html/rfc3986#page-50) @@ -140,7 +140,7 @@ public void resetSounds() { * @param qa -- the base categorization of the sounds in the content, SoundSide.SOUNDS_QUESTION or SoundSide.SOUNDS_ANSWER */ public void addSounds(String soundDir, String content, SoundSide qa) { - Matcher matcher = sSoundPattern.matcher(content); + Matcher matcher = SOUND_PATTERN.matcher(content); // While there is matches of the pattern for sound markers while (matcher.find()) { // Create appropriate list if needed; list must not be empty so long as code does no check @@ -203,7 +203,7 @@ public static String expandSounds(String soundDir, String content) { Timber.d("expandSounds"); - Matcher matcher = sSoundPattern.matcher(content); + Matcher matcher = SOUND_PATTERN.matcher(content); // While there is matches of the pattern for sound markers while (matcher.find()) { // Get the sound file name diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java b/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java index 70fd4af411a6..1ac28aad70d4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java @@ -55,7 +55,7 @@ public String getDefaultName() { /// create the standard models - public static final StdModels basicModel = new StdModels( + public static final StdModels BASIC_MODEL = new StdModels( (mm, name) -> { Model m = mm.newModel(name); String frontName = AnkiDroidApp.getAppResources().getString(R.string.front_field_name); @@ -73,9 +73,9 @@ public String getDefaultName() { }, R.string.basic_model_name); - public static final StdModels basicTypingModel = new StdModels + public static final StdModels BASIC_TYPING_MODEL = new StdModels ((mm, name) -> { - Model m = basicModel._new(mm, name); + Model m = BASIC_MODEL._new(mm, name); JSONObject t = m.getJSONArray("tmpls").getJSONObject(0); String frontName = m.getJSONArray("flds").getJSONObject(0).getString("name"); String backName = m.getJSONArray("flds").getJSONObject(1).getString("name"); @@ -85,9 +85,9 @@ public String getDefaultName() { }, R.string.basic_typing_model_name); - public static final StdModels forwardReverseModel = new StdModels + public static final StdModels FORWARD_REVERSE_MODEL = new StdModels ((mm, name) -> { - Model m = basicModel._new(mm, name); + Model m = BASIC_MODEL._new(mm, name); String frontName = m.getJSONArray("flds").getJSONObject(0).getString("name"); String backName = m.getJSONArray("flds").getJSONObject(1).getString("name"); String cardTwoName = AnkiDroidApp.getAppResources().getString(R.string.card_n_name, 2); @@ -99,9 +99,9 @@ public String getDefaultName() { }, R.string.forward_reverse_model_name); - public static final StdModels forwardOptionalReverseModel = new StdModels + public static final StdModels FORWARD_OPTIONAL_REVERSE_MODEL = new StdModels ((mm, name) -> { - Model m = forwardReverseModel._new(mm, name); + Model m = FORWARD_REVERSE_MODEL._new(mm, name); String av = AnkiDroidApp.getAppResources().getString(R.string.field_to_ask_front_name); JSONObject fm = mm.newField(av); mm.addFieldInNewModel(m, fm); @@ -111,7 +111,7 @@ public String getDefaultName() { }, R.string.forward_optional_reverse_model_name); - public static final StdModels clozeModel = new StdModels + public static final StdModels CLOZE_MODEL = new StdModels ((mm, name) -> { Model m = mm.newModel(name); m.put("type", Consts.MODEL_CLOZE); @@ -132,13 +132,13 @@ public String getDefaultName() { }, R.string.cloze_model_name); - public static final StdModels[] stdModels = + public static final StdModels[] STD_MODELS = { - basicModel, - basicTypingModel, - forwardReverseModel, - forwardOptionalReverseModel, - clozeModel, + BASIC_MODEL, + BASIC_TYPING_MODEL, + FORWARD_REVERSE_MODEL, + FORWARD_OPTIONAL_REVERSE_MODEL, + CLOZE_MODEL, }; } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java index 18e87a77a695..b514b11cc682 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Storage.java @@ -92,8 +92,8 @@ public static Collection Collection(Context context, String path, boolean server throw new RuntimeException("This file requires a newer version of Anki."); } else if (create) { // add in reverse order so basic is default - for (int i = StdModels.stdModels.length-1; i>=0; i--) { - StdModels.stdModels[i].add(col); + for (int i = StdModels.STD_MODELS.length-1; i>=0; i--) { + StdModels.STD_MODELS[i].add(col); } col.save(); } @@ -179,7 +179,7 @@ private static void _upgrade(Collection col, int ver) { if (ver < 6) { col.modSchemaNoCheck(); for (Model m : col.getModels().all()) { - m.put("css", new JSONObject(Models.defaultModel).getString("css")); + m.put("css", new JSONObject(Models.DEFAULT_MODEL).getString("css")); JSONArray ar = m.getJSONArray("tmpls"); for (JSONObject t: ar.jsonObjectIterable()) { if (!t.has("css")) { @@ -355,19 +355,19 @@ private static void _addSchema(DB db, boolean setColConf, @NonNull Time time) { private static void _setColVars(DB db, @NonNull Time time) { - JSONObject g = new JSONObject(Decks.defaultDeck); + JSONObject g = new JSONObject(Decks.DEFAULT_DECK); g.put("id", 1); g.put("name", "Default"); g.put("conf", 1); g.put("mod", time.intTime()); - JSONObject gc = new JSONObject(Decks.defaultConf); + JSONObject gc = new JSONObject(Decks.DEFAULT_CONF); gc.put("id", 1); JSONObject ag = new JSONObject(); ag.put("1", g); JSONObject agc = new JSONObject(); agc.put("1", gc); ContentValues values = new ContentValues(); - values.put("conf", Collection.defaultConf); + values.put("conf", Collection.DEFAULT_CONF); values.put("decks", Utils.jsonToString(ag)); values.put("dconf", Utils.jsonToString(agc)); db.update("col", values); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/Anki2Importer.java b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/Anki2Importer.java index d409b5eaa709..d43b1407143d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/Anki2Importer.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/Anki2Importer.java @@ -762,7 +762,7 @@ private void _writeDstMedia(String fname, BufferedInputStream data) { // running splitFields() on every note is fairly expensive and actually not necessary private String _mungeMedia(long mid, String fields) { - for (Pattern p : Media.mRegexps) { + for (Pattern p : Media.REGEXPS) { Matcher m = p.matcher(fields); StringBuffer sb = new StringBuffer(); int fnameIdx = Media.indexOfFname(p); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/template/ParsedNode.java b/AnkiDroid/src/main/java/com/ichi2/libanki/template/ParsedNode.java index 5070273ad5ce..e828c0c03baf 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/template/ParsedNode.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/template/ParsedNode.java @@ -1,7 +1,6 @@ package com.ichi2.libanki.template; import android.content.Context; -import android.text.TextUtils; import android.util.Pair; import com.ichi2.anki.R; @@ -9,7 +8,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -20,7 +18,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import okhttp3.internal.Util; import timber.log.Timber; /** @@ -98,11 +95,11 @@ public boolean template_is_empty(String... nonempty_fields) { while (tokens.hasNext()) { Tokenizer.Token token = tokens.next(); switch (token.getKind()) { - case Text: { + case TEXT: { nodes.add(new Text(token.getText())); break; } - case Replacement: { + case REPLACEMENT: { String[] it = token.getText().split(":", -1); String key = it[it.length - 1]; List filters = new ArrayList<>(it.length - 1); @@ -112,17 +109,17 @@ public boolean template_is_empty(String... nonempty_fields) { nodes.add(new Replacement(key, filters, token.getText())); break; } - case OpenConditional: { + case OPEN_CONDITIONAL: { String tag = token.getText(); nodes.add(new Conditional(tag, parse_inner(tokens, tag))); break; } - case OpenNegated: { + case OPEN_NEGATED: { String tag = token.getText(); nodes.add(new NegatedConditional(tag, parse_inner(tokens, tag))); break; } - case CloseConditional: { + case CLOSE_CONDITIONAL: { String tag = token.getText(); if (open_tag == null) { throw new TemplateError.ConditionalNotOpen(tag); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/template/TemplateFilters.java b/AnkiDroid/src/main/java/com/ichi2/libanki/template/TemplateFilters.java index f0280d6d8dc6..f5fc5188b35a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/template/TemplateFilters.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/template/TemplateFilters.java @@ -23,7 +23,7 @@ public class TemplateFilters { public static final String CLOZE_DELETION_REPLACEMENT = "[...]"; private static final Pattern fHookFieldMod = Pattern.compile("^(.*?)(?:\\((.*)\\))?$"); - public static final String clozeReg = "(?si)\\{\\{(c)%s::(.*?)(::(.*?))?\\}\\}"; + public static final String CLOZE_REG = "(?si)\\{\\{(c)%s::(.*?)(::(.*?))?\\}\\}"; /** @@ -122,12 +122,12 @@ private static String runHint(String txt, String tag) { private static @NonNull String clozeText(@NonNull String txt, @NonNull String ord, char type) { - if (!Pattern.compile(String.format(Locale.US, clozeReg, ord)).matcher(txt).find()) { + if (!Pattern.compile(String.format(Locale.US, CLOZE_REG, ord)).matcher(txt).find()) { return ""; } txt = removeFormattingFromMathjax(txt, ord); - Matcher m = Pattern.compile(String.format(Locale.US, clozeReg, ord)).matcher(txt); + Matcher m = Pattern.compile(String.format(Locale.US, CLOZE_REG, ord)).matcher(txt); StringBuffer repl = new StringBuffer(); while (m.find()) { @@ -151,7 +151,7 @@ private static String runHint(String txt, String tag) { } txt = m.appendTail(repl).toString(); // and display other clozes normally - return txt.replaceAll(String.format(Locale.US, clozeReg, "\\d+"), "$2"); + return txt.replaceAll(String.format(Locale.US, CLOZE_REG, "\\d+"), "$2"); } /** @@ -165,7 +165,7 @@ private static String runHint(String txt, String tag) { * Cloze in a ". */ public static @NonNull String removeFormattingFromMathjax(@NonNull String txt, @NonNull String ord) { - String creg = clozeReg.replace("(?si)", ""); + String creg = CLOZE_REG.replace("(?si)", ""); // Scan the string left to right. // After a MathJax opening - \( or \[ - flip in_mathjax to True. // After a MathJax closing - \) or \] - flip in_mathjax to False. diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/template/Tokenizer.java b/AnkiDroid/src/main/java/com/ichi2/libanki/template/Tokenizer.java index 61cf7ada6d08..7f7f31424255 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/template/Tokenizer.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/template/Tokenizer.java @@ -7,11 +7,11 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.CloseConditional; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.OpenConditional; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.OpenNegated; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.Replacement; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.Text; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.CLOSE_CONDITIONAL; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.OPEN_CONDITIONAL; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.OPEN_NEGATED; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.REPLACEMENT; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.TEXT; /** * This class encodes template.rs's file creating template. @@ -45,23 +45,23 @@ enum TokenKind { /** * Some text, assumed not to contains {{*}} */ - Text, + TEXT, /** * {{Field name}} */ - Replacement, + REPLACEMENT, /** * {{#Field name}} */ - OpenConditional, + OPEN_CONDITIONAL, /** * {{^Field name}} */ - OpenNegated, + OPEN_NEGATED, /** * {{/Field name}} */ - CloseConditional + CLOSE_CONDITIONAL } @@ -165,7 +165,7 @@ public boolean equals(@Nullable Object obj) { if (text_size == 0) { return null; } - return new IResult(new Token(Text, template.substring(0, text_size)), template.substring(text_size)); + return new IResult(new Token(TEXT, template.substring(0, text_size)), template.substring(text_size)); } @@ -180,17 +180,17 @@ public boolean equals(@Nullable Object obj) { } String start = handle.substring(start_pos).trim(); if (start.length() < 2) { - return new Token(Replacement, start); + return new Token(REPLACEMENT, start); } switch (start.charAt(0)) { case '#': - return new Token(OpenConditional, start.substring(1)); + return new Token(OPEN_CONDITIONAL, start.substring(1)); case '/': - return new Token(CloseConditional, start.substring(1)); + return new Token(CLOSE_CONDITIONAL, start.substring(1)); case '^': - return new Token(OpenNegated, start.substring(1)); + return new Token(OPEN_NEGATED, start.substring(1)); default: - return new Token(Replacement, start); + return new Token(REPLACEMENT, start); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/BooleanGetter.java b/AnkiDroid/src/main/java/com/ichi2/utils/BooleanGetter.java index e5522448a04d..9df7c2c5bf54 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/BooleanGetter.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/BooleanGetter.java @@ -6,8 +6,8 @@ public interface BooleanGetter { boolean getBoolean(); - BooleanGetter True = () -> true; - BooleanGetter False = () -> false; + BooleanGetter TRUE = () -> true; + BooleanGetter FALSE = () -> false; static BooleanGetter fromBoolean(boolean b) { return (() -> b); } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/DeckComparator.java b/AnkiDroid/src/main/java/com/ichi2/utils/DeckComparator.java index 0c6cb3700d1e..ed8b7263ddde 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/DeckComparator.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/DeckComparator.java @@ -1,15 +1,12 @@ package com.ichi2.utils; -import com.ichi2.utils.DeckNameComparator; -import com.ichi2.utils.JSONObject; - import java.util.Comparator; public class DeckComparator implements Comparator { - public static final DeckComparator instance = new DeckComparator(); + public static final DeckComparator INSTANCE = new DeckComparator(); @Override public int compare(JSONObject lhs, JSONObject rhs) { - return DeckNameComparator.instance.compare(lhs.getString("name"), rhs.getString("name")); + return DeckNameComparator.INSTANCE.compare(lhs.getString("name"), rhs.getString("name")); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/DeckNameComparator.java b/AnkiDroid/src/main/java/com/ichi2/utils/DeckNameComparator.java index 7f8609fb8427..fd4e0a9fefc6 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/DeckNameComparator.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/DeckNameComparator.java @@ -5,7 +5,7 @@ import java.util.Comparator; public class DeckNameComparator implements Comparator { - public static final DeckNameComparator instance = new DeckNameComparator(); + public static final DeckNameComparator INSTANCE = new DeckNameComparator(); @Override public int compare(String lhs, String rhs) { diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/NamedJSONComparator.java b/AnkiDroid/src/main/java/com/ichi2/utils/NamedJSONComparator.java index a9fcf3884e67..3d09a36f7f65 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/NamedJSONComparator.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/NamedJSONComparator.java @@ -5,7 +5,7 @@ import java.util.Comparator; public class NamedJSONComparator implements Comparator { - public static final NamedJSONComparator instance = new NamedJSONComparator(); + public static final NamedJSONComparator INSTANCE = new NamedJSONComparator(); @Override public int compare(JSONObject lhs, JSONObject rhs) { diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.java index b2840f55beac..e3253a356d1e 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/CollectionTest.java @@ -169,10 +169,10 @@ public void test_addDelTags() { @Test public void test_timestamps() { Collection col = getCol(); - int stdModelSize = StdModels.stdModels.length; + int stdModelSize = StdModels.STD_MODELS.length; assertEquals(col.getModels().all().size(), stdModelSize); for (int i = 0; i < 100; i++) { - StdModels.basicModel.add(col); + StdModels.BASIC_MODEL.add(col); } assertEquals(col.getModels().all().size(), 100 + stdModelSize); } diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/template/TokenizerTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/template/TokenizerTest.java index f6b0fec24a6e..038d57cf129a 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/template/TokenizerTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/template/TokenizerTest.java @@ -1,27 +1,18 @@ package com.ichi2.libanki.template; -import android.util.Pair; - import com.ichi2.anki.RobolectricTest; -import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.NoSuchElementException; - import androidx.test.ext.junit.runners.AndroidJUnit4; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.CloseConditional; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.OpenConditional; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.OpenNegated; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.Replacement; -import static com.ichi2.libanki.template.Tokenizer.TokenKind.Text; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.CLOSE_CONDITIONAL; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.OPEN_CONDITIONAL; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.OPEN_NEGATED; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.REPLACEMENT; +import static com.ichi2.libanki.template.Tokenizer.TokenKind.TEXT; import static com.ichi2.libanki.template.Tokenizer.IResult; import static com.ichi2.libanki.template.Tokenizer.classify_handle; import static com.ichi2.libanki.template.Tokenizer.handlebar_token; @@ -29,7 +20,6 @@ import static com.ichi2.libanki.template.Tokenizer.next_token; import static com.ichi2.libanki.template.Tokenizer.text_token; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; @@ -47,39 +37,39 @@ public void test_text_token() { assertThat(text_token(""), is(nullValue())); assertThat(text_token("foo{{bar}}plop"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Tokenizer.TokenKind.Text, "foo"), + new Tokenizer.Token(Tokenizer.TokenKind.TEXT, "foo"), "{{bar}}plop"))); assertThat(text_token("foo{bar}plop"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Tokenizer.TokenKind.Text, "foo{bar}plop"), + new Tokenizer.Token(Tokenizer.TokenKind.TEXT, "foo{bar}plop"), ""))); } @Test public void test_classify_handle() { assertThat(classify_handle("#foo"), - Matchers.is(new Tokenizer.Token(OpenConditional, + Matchers.is(new Tokenizer.Token(OPEN_CONDITIONAL, "foo"))); assertThat(classify_handle("/foo"), - Matchers.is(new Tokenizer.Token(CloseConditional, + Matchers.is(new Tokenizer.Token(CLOSE_CONDITIONAL, "foo"))); assertThat(classify_handle("^foo"), - Matchers.is(new Tokenizer.Token(OpenNegated, + Matchers.is(new Tokenizer.Token(OPEN_NEGATED, "foo"))); assertThat(classify_handle("!foo"), - Matchers.is(new Tokenizer.Token(Replacement, + Matchers.is(new Tokenizer.Token(REPLACEMENT, "!foo"))); assertThat(classify_handle("{#foo}"), - Matchers.is(new Tokenizer.Token(OpenConditional, + Matchers.is(new Tokenizer.Token(OPEN_CONDITIONAL, "foo}"))); assertThat(classify_handle("{ #foo}"), - Matchers.is(new Tokenizer.Token(OpenConditional, + Matchers.is(new Tokenizer.Token(OPEN_CONDITIONAL, "foo}"))); assertThat(classify_handle(" #"), - Matchers.is(new Tokenizer.Token(Replacement, + Matchers.is(new Tokenizer.Token(REPLACEMENT, "#"))); assertThat(classify_handle(" foo "), - Matchers.is(new Tokenizer.Token(Replacement, + Matchers.is(new Tokenizer.Token(REPLACEMENT, "foo"))); } @@ -87,60 +77,60 @@ public void test_classify_handle() { public void test_handlebar_token() { assertThat(handlebar_token("{{#foo}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(OpenConditional, + new Tokenizer.Token(OPEN_CONDITIONAL, "foo"), " bar"))); assertThat(handlebar_token("{{/foo}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(CloseConditional, + new Tokenizer.Token(CLOSE_CONDITIONAL, "foo"), " bar"))); assertThat(handlebar_token("{{^foo}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(OpenNegated, + new Tokenizer.Token(OPEN_NEGATED, "foo"), " bar"))); assertThat(handlebar_token("{{!foo}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Replacement, + new Tokenizer.Token(REPLACEMENT, "!foo"), " bar"))); assertThat(handlebar_token("{{{#foo}}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(OpenConditional, + new Tokenizer.Token(OPEN_CONDITIONAL, "foo"), "} bar"))); assertThat(handlebar_token("{{{ #foo}}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(OpenConditional, + new Tokenizer.Token(OPEN_CONDITIONAL, "foo"), "} bar"))); assertThat(handlebar_token("{{ #}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Replacement, + new Tokenizer.Token(REPLACEMENT, "#"), " bar"))); assertThat(handlebar_token("{{ foo }} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Replacement, + new Tokenizer.Token(REPLACEMENT, "foo"), " bar"))); assertThat(handlebar_token("{{filter:field}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Replacement, + new Tokenizer.Token(REPLACEMENT, "filter:field"), " bar"))); // The empty field name without filter is not valid in Anki, // However, it's not the lexer job to deal with it, and so it should be lexed correctly. assertThat(handlebar_token("{{}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Replacement, + new Tokenizer.Token(REPLACEMENT, ""), " bar"))); // Empty field name with filter is valid and has special meaning assertThat(handlebar_token("{{filter:}} bar"), Matchers.is(new Tokenizer.IResult( - new Tokenizer.Token(Replacement, + new Tokenizer.Token(REPLACEMENT, "filter:"), " bar"))); assertThat(handlebar_token(""), @@ -157,53 +147,53 @@ public void test_handlebar_token() { public void test_next_token() { assertThat(next_token("{{#foo}} bar"), Matchers.is(new IResult( - new Tokenizer.Token(OpenConditional, + new Tokenizer.Token(OPEN_CONDITIONAL, "foo"), " bar"))); assertThat(next_token("{{/foo}} bar"), Matchers.is(new IResult( - new Token(CloseConditional, + new Token(CLOSE_CONDITIONAL, "foo"), " bar"))); assertThat(next_token("{{^foo}} bar"), Matchers.is(new IResult( - new Token(OpenNegated, + new Token(OPEN_NEGATED, "foo"), " bar"))); assertThat(next_token("{{!foo}} bar"), Matchers.is(new IResult( - new Token(Replacement, + new Token(REPLACEMENT, "!foo"), " bar"))); assertThat(next_token("{{{#foo}}} bar"), Matchers.is(new IResult( - new Token(OpenConditional, + new Token(OPEN_CONDITIONAL, "foo"), "} bar"))); assertThat(next_token("{{{ #foo}}} bar"), Matchers.is(new IResult( - new Token(OpenConditional, + new Token(OPEN_CONDITIONAL, "foo"), "} bar"))); assertThat(next_token("{{ #}} bar"), Matchers.is(new IResult( - new Token(Replacement, + new Token(REPLACEMENT, "#"), " bar"))); assertThat(next_token("{{ foo }} bar"), Matchers.is(new IResult( - new Token(Replacement, + new Token(REPLACEMENT, "foo"), " bar"))); assertThat(next_token(""), is(nullValue())); assertThat(next_token("foo{{bar}}plop"), Matchers.is(new IResult( - new Token(Text, "foo"), + new Token(TEXT, "foo"), "{{bar}}plop"))); assertThat(next_token("foo{bar}plop"), Matchers.is(new IResult( - new Token(Text, "foo{bar}plop"), + new Token(TEXT, "foo{bar}plop"), ""))); } @@ -211,13 +201,13 @@ public void test_next_token() { public void test_tokens() { Tokenizer tokenizer = new Tokenizer("Foo {{Test}} {{{ #Bar}} {{/Plop }}iee {{!ien nnr"); - assertThat(tokenizer.next(), is(new Token(Text, "Foo "))); - assertThat(tokenizer.next(), is(new Token(Replacement, "Test"))); - assertThat(tokenizer.next(), is(new Token(Text, " "))); - assertThat(tokenizer.next(), is(new Token(OpenConditional, "Bar"))); - assertThat(tokenizer.next(), is(new Token(Text, " "))); - assertThat(tokenizer.next(), is(new Token(CloseConditional, "Plop"))); - assertThat(tokenizer.next(), is(new Token(Text, "iee "))); + assertThat(tokenizer.next(), is(new Token(TEXT, "Foo "))); + assertThat(tokenizer.next(), is(new Token(REPLACEMENT, "Test"))); + assertThat(tokenizer.next(), is(new Token(TEXT, " "))); + assertThat(tokenizer.next(), is(new Token(OPEN_CONDITIONAL, "Bar"))); + assertThat(tokenizer.next(), is(new Token(TEXT, " "))); + assertThat(tokenizer.next(), is(new Token(CLOSE_CONDITIONAL, "Plop"))); + assertThat(tokenizer.next(), is(new Token(TEXT, "iee "))); try { tokenizer.next(); fail(); From dfa10a3f9a4252a0b7aa326faf012a236d8f47e4 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Tue, 6 Apr 2021 00:12:25 +0100 Subject: [PATCH 062/171] fix: Not allowed to increase new cards newCount() is the currently displayed "new count", not the total fixes #8481 --- .../src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java index f9473553122e..ed694479f6d0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java @@ -358,7 +358,7 @@ private int[] getListIds(int dialogId) { dialogOptions.add(CUSTOM_STUDY_RANDOM); dialogOptions.add(CUSTOM_STUDY_PREVIEW); dialogOptions.add(CUSTOM_STUDY_TAGS); - if (col.getSched().newCount() == 0) { + if (col.getSched().totalNewForCurrentDeck() == 0) { // If no new cards we wont show CUSTOM_STUDY_NEW dialogOptions.remove(Integer.valueOf(CUSTOM_STUDY_NEW)); } From 6af4a00c72f91156f31aab765503df8441e87abb Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Tue, 6 Apr 2021 15:20:02 +0100 Subject: [PATCH 063/171] =?UTF-8?q?Add=20preference:=20Focus=20=E2=80=98ty?= =?UTF-8?q?pe=20in=20answer=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some users only use the 'type in answer' function occasionally (for example: checking spelling) When we added the 'always focus type in answer' functionality, this was not a good preference for them, as the keyboard uses a lot of space. Now, we add in a preference: "Focus ‘type in answer’". We default this to false, as to not change user expectations This preference does not make sense if the 'Type answer into the card' feature is set, so disable it if we use an box. Fixes 8424 --- .../java/com/ichi2/anki/AbstractFlashcardViewer.java | 6 +++++- AnkiDroid/src/main/res/values/10-preferences.xml | 3 +++ AnkiDroid/src/main/res/xml/preferences_advanced.xml | 12 ++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index f0b3cbde1454..7888378c27de 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -385,6 +385,9 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i /** Handle providing help for "Image Not Found" */ private static final MissingImageHandler mMissingImageHandler = new MissingImageHandler(); + /** Preference: Whether the user wants to focus "type in answer" */ + private boolean mFocusTypeAnswer; + // ---------------------------------------------------------------------------- // LISTENERS // ---------------------------------------------------------------------------- @@ -1757,7 +1760,7 @@ private void actualHideEaseButtons() { private void focusAnswerCompletionField() { // This does not handle mUseInputTag (the WebView contains an input field with a typable answer). // In this case, the user can use touch to focus the field if necessary. - if (typeAnswer()) { + if (typeAnswer() && mFocusTypeAnswer) { mAnswerField.focusWithKeyboard(); } else { mFlipCardLayout.requestFocus(); @@ -1814,6 +1817,7 @@ protected SharedPreferences restorePreferences() { mScrollingButtons = preferences.getBoolean("scrolling_buttons", false); mDoubleScrolling = preferences.getBoolean("double_scrolling", false); mPrefShowTopbar = preferences.getBoolean("showTopbar", true); + mFocusTypeAnswer = preferences.getBoolean("autoFocusTypeInAnswer", false); mGesturesEnabled = AnkiDroidApp.initiateGestures(preferences); mLinkOverridesTouchGesture = preferences.getBoolean("linkOverridesTouchGesture", false); diff --git a/AnkiDroid/src/main/res/values/10-preferences.xml b/AnkiDroid/src/main/res/values/10-preferences.xml index 91304e8b40b8..fa069f6c6357 100644 --- a/AnkiDroid/src/main/res/values/10-preferences.xml +++ b/AnkiDroid/src/main/res/values/10-preferences.xml @@ -280,6 +280,9 @@ Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/xml/preferences_advanced.xml b/AnkiDroid/src/main/res/xml/preferences_advanced.xml index 8cbf7bc26c60..3d6d1d779828 100644 --- a/AnkiDroid/src/main/res/xml/preferences_advanced.xml +++ b/AnkiDroid/src/main/res/xml/preferences_advanced.xml @@ -65,6 +65,7 @@ @@ -86,6 +87,17 @@ android:key="noCodeFormatting" android:summary="@string/no_code_formatting_summ" android:title="@string/no_code_formatting" /> + + Date: Wed, 7 Apr 2021 04:42:43 +0530 Subject: [PATCH 064/171] Added test for AssetHelper util class (#8497) --- .../java/com/ichi2/utils/AssetHelperTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/AssetHelperTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/AssetHelperTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/AssetHelperTest.java new file mode 100644 index 000000000000..e25a15292e79 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/AssetHelperTest.java @@ -0,0 +1,32 @@ +/* + 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.utils; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class AssetHelperTest { + + @Test + public void guessMimeTypeTest() { + assertThat(AssetHelper.guessMimeType("test.txt"), is("text/plain")); + assertThat(AssetHelper.guessMimeType("test.png"), is("image/png")); + assertThat(AssetHelper.guessMimeType("test.zip"), is("application/zip")); + } +} \ No newline at end of file From 7b6fcec9d7d49b2f15fc57b589d5e85e4c6ceaf5 Mon Sep 17 00:00:00 2001 From: Piyush Goel <46752548+Arnold2381@users.noreply.github.com> Date: Wed, 7 Apr 2021 04:54:11 +0530 Subject: [PATCH 065/171] Code Style 'm' instance variable prefix AdvancedStatistics (#8492) --- .../libanki/stats/AdvancedStatistics.java | 576 +++++++++--------- 1 file changed, 288 insertions(+), 288 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/stats/AdvancedStatistics.java b/AnkiDroid/src/main/java/com/ichi2/libanki/stats/AdvancedStatistics.java index 86bdfe81298a..b000a5739f49 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/stats/AdvancedStatistics.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/stats/AdvancedStatistics.java @@ -139,9 +139,9 @@ public class AdvancedStatistics { private static final int REVIEW_OUTCOME_GOOD_PLUS_1 = 3; private static final int REVIEW_OUTCOME_EASY_PLUS_1 = 4; - private final ArrayUtils ArrayUtils = new ArrayUtils(); - private final DeckFactory Decks = new DeckFactory(); - private Settings Settings; + private final ArrayUtils mArrayUtils = new ArrayUtils(); + private final DeckFactory mDecks = new DeckFactory(); + private Settings mSettings; /** * Determine forecast statistics based on a computation or simulation of future reviews. @@ -167,7 +167,7 @@ public StatsMetaInfo calculateDueAsMetaInfo(StatsMetaInfo metaInfo, Stats.AxisTy return metaInfo; } //To indicate that we calculated the statistics so that Stats.java knows that it shouldn't display the standard Forecast chart. - Settings = new Settings(context); + mSettings = new Settings(context); metaInfo.setStatsCalculated(true); Collection mCol = CollectionHelper.getInstance().getCol(context); @@ -309,16 +309,16 @@ private PlottableSimulationResult calculateDueAsPlottableSimulationResult(Stats. EaseClassifier classifier = new EaseClassifier(mCol.getTime(), mCol.getDb()); ReviewSimulator reviewSimulator = new ReviewSimulator(mCol.getDb(), classifier, end, chunk); - TodayStats todayStats = new TodayStats(mCol, Settings.getDayStartCutoff(mCol.getCrt())); + TodayStats todayStats = new TodayStats(mCol, mSettings.getDayStartCutoff(mCol.getCrt())); long t0 = mCol.getTime().intTimeMS(); - SimulationResult simulationResult = reviewSimulator.simNreviews(Settings.getToday((int)mCol.getCrt()), mCol.getDecks(), dids, todayStats); + SimulationResult simulationResult = reviewSimulator.simNreviews(mSettings.getToday((int)mCol.getCrt()), mCol.getDecks(), dids, todayStats); long t1 = mCol.getTime().intTimeMS(); Timber.d("Simulation of all decks took: %d ms", t1 - t0); - int[][] nReviews = ArrayUtils.transposeMatrix(simulationResult.getNReviews()); - int[][] nInState = ArrayUtils.transposeMatrix(simulationResult.getNInState()); + int[][] nReviews = mArrayUtils.transposeMatrix(simulationResult.getNReviews()); + int[][] nInState = mArrayUtils.transposeMatrix(simulationResult.getNInState()); //Append row with zeros and transpose to make it the same dimension as nReviews //int[][] nInState = simulationResult.getNInState(); @@ -380,78 +380,78 @@ private PlottableSimulationResult calculateDueAsPlottableSimulationResult(Stats. // nInState = ArrayUtils.append(nInState, nInState[nInState.length-1], dues.size() - nInState.length); //} - return new PlottableSimulationResult(dues, ArrayUtils.transposeMatrix(nInStateCum)); + return new PlottableSimulationResult(dues, mArrayUtils.transposeMatrix(nInStateCum)); } private static class Card { - private int ivl; - private double factor; - private int lastReview; - private int due; - private int correct; - private long id; + private int mIvl; + private double mFactor; + private int mLastReview; + private int mDue; + private int mCorrect; + private long mId; @Override public @NonNull String toString() { - return "Card [ivl=" + ivl + ", factor=" + factor + ", due=" + due + ", correct=" + correct + ", id=" - + id + "]"; + return "Card [ivl=" + mIvl + ", factor=" + mFactor + ", due=" + mDue + ", correct=" + mCorrect + ", id=" + + mId + "]"; } public Card(long id, int ivl, int factor, int due, int correct, int lastReview) { super(); - this.id = id; - this.ivl = ivl; - this.factor = factor / 1000.0; - this.due = due; - this.correct = correct; - this.lastReview = lastReview; + this.mId = id; + this.mIvl = ivl; + this.mFactor = factor / 1000.0; + this.mDue = due; + this.mCorrect = correct; + this.mLastReview = lastReview; } public void setAll(long id, int ivl, int factor, int due, int correct, int lastReview) { - this.id = id; - this.ivl = ivl; - this.factor = factor / 1000.0; - this.due = due; - this.correct = correct; - this.lastReview = lastReview; + this.mId = id; + this.mIvl = ivl; + this.mFactor = factor / 1000.0; + this.mDue = due; + this.mCorrect = correct; + this.mLastReview = lastReview; } public void setAll(Card card) { - this.id = card.id; - this.ivl = card.ivl; - this.factor = card.factor; - this.due = card.due; - this.correct = card.correct; - this.lastReview = card.lastReview; + this.mId = card.mId; + this.mIvl = card.mIvl; + this.mFactor = card.mFactor; + this.mDue = card.mDue; + this.mCorrect = card.mCorrect; + this.mLastReview = card.mLastReview; } public long getId() { - return id; + return mId; } public int getIvl() { - return ivl; + return mIvl; } public void setIvl(int ivl) { - this.ivl = ivl; + this.mIvl = ivl; } public double getFactor() { - return factor; + return mFactor; } public void setFactor(double factor) { - this.factor = factor; + this.mFactor = factor; } public int getDue() { - return due; + return mDue; } public void setDue(int due) { - this.due = due; + this.mDue = due; } /** @@ -459,9 +459,9 @@ public void setDue(int due) { * @return CARD_TYPE_NEW if interval = 0, CARD_TYPE_YOUNG if interval 1-20, CARD_TYPE_MATURE if interval >= 20 */ public int getType() { - if(ivl == 0) { + if(mIvl == 0) { return CARD_TYPE_NEW; - } else if (ivl >= 21) { + } else if (mIvl >= 21) { return CARD_TYPE_MATURE; } else { return CARD_TYPE_YOUNG; @@ -469,19 +469,19 @@ public int getType() { } public int getCorrect() { - return correct; + return mCorrect; } public void setCorrect(int correct) { - this.correct = correct; + this.mCorrect = correct; } public int getLastReview() { - return lastReview; + return mLastReview; } public void setLastReview(int lastReview) { - this.lastReview = lastReview; + this.mLastReview = lastReview; } } @@ -493,9 +493,9 @@ public Deck createDeck(long did, Decks decks) { DeckConfig conf = decks.confForDid(did); - int newPerDay = Settings.getMaxNewPerDay(); - int revPerDay = Settings.getMaxReviewsPerDay(); - int initialFactor = Settings.getInitialFactor(); + int newPerDay = mSettings.getMaxNewPerDay(); + int revPerDay = mSettings.getMaxReviewsPerDay(); + int initialFactor = mSettings.getInitialFactor(); if (conf.isStd()) { revPerDay = conf.getJSONObject("rev").getInt("perDay"); @@ -518,48 +518,48 @@ public Deck createDeck(long did, Decks decks) { */ private static class Deck { - private final long did; + private final long mDid; - private final int newPerDay; - private final int revPerDay; - private final int initialFactor; + private final int mNewPerDay; + private final int mRevPerDay; + private final int mInitialFactor; public Deck(long did, int newPerDay, int revPerDay, int initialFactor) { - this.did = did; - this.newPerDay = newPerDay; - this.revPerDay = revPerDay; - this.initialFactor = initialFactor; + this.mDid = did; + this.mNewPerDay = newPerDay; + this.mRevPerDay = revPerDay; + this.mInitialFactor = initialFactor; } public long getDid() { - return did; + return mDid; } public int getNewPerDay() { - return newPerDay; + return mNewPerDay; } public int getRevPerDay() { - return revPerDay; + return mRevPerDay; } public int getInitialFactor() { - return initialFactor; + return mInitialFactor; } } private static class CardIterator { - private final Cursor cur; + private final Cursor mCur; - private final int today; - private final Deck deck; + private final int mToday; + private final Deck mDeck; public CardIterator(DB db, int today, Deck deck) { - this.today = today; - this.deck = deck; + this.mToday = today; + this.mDeck = deck; long did = deck.getDid(); @@ -569,27 +569,27 @@ public CardIterator(DB db, int today, Deck deck) { "AND queue != " + Consts.QUEUE_TYPE_SUSPENDED + " " + // ignore suspended cards "order by id;"; Timber.d("Forecast query: %s", query); - cur = db.query(query); + mCur = db.query(query); } public boolean moveToNext() { - return cur.moveToNext(); + return mCur.moveToNext(); } public void current(Card card) { - card.setAll(cur.getLong(0), //Id - cur.getInt(5) == 0 ? 0 : cur.getInt(2), //reps = 0 ? 0 : card interval - cur.getInt(3) > 0 ? cur.getInt(3) : deck.getInitialFactor(), //factor - Math.max(cur.getInt(1) - today, 0), //due + card.setAll(mCur.getLong(0), //Id + mCur.getInt(5) == 0 ? 0 : mCur.getInt(2), //reps = 0 ? 0 : card interval + mCur.getInt(3) > 0 ? mCur.getInt(3) : mDeck.getInitialFactor(), //factor + Math.max(mCur.getInt(1) - mToday, 0), //due 1, //correct -1 //lastreview ); } public void close() { - if (cur != null && !cur.isClosed()) - cur.close(); + if (mCur != null && !mCur.isClosed()) + mCur.close(); } } @@ -606,16 +606,16 @@ public void close() { */ private static class EaseClassifier { - private final Random random; + private final Random mRandom; - private final DB db; - private double[][] probabilities; - private double[][] probabilitiesCumulative; + private final DB mDb; + private double[][] mProbabilities; + private double[][] mProbabilitiesCumulative; //# Prior that half of new cards are answered correctly - private final int[] priorNew = {5, 0, 5, 0}; //half of new cards are answered correctly - private final int[] priorYoung = {1, 0, 9, 0}; //90% of young cards get "good" response - private final int[] priorMature = {1, 0, 9, 0}; //90% of mature cards get "good" response + private final int[] mPriorNew = {5, 0, 5, 0}; //half of new cards are answered correctly + private final int[] mPriorYoung = {1, 0, 9, 0}; //90% of young cards get "good" response + private final int[] mPriorMature = {1, 0, 9, 0}; //90% of mature cards get "good" response //TODO: should we determine these per deck or over decks? @@ -652,9 +652,9 @@ private static class EaseClassifier { + "where type=" + Consts.CARD_TYPE_LRN + " and lastIvl >= 21;"; public EaseClassifier(Time time, DB db) { - this.db = db; + this.mDb = db; - singleReviewOutcome = new ReviewOutcome(null, 0); + mSingleReviewOutcome = new ReviewOutcome(null, 0); long t0 = time.intTimeMS(); calculateCumProbabilitiesForNewEasePerCurrentEase(); @@ -662,15 +662,15 @@ public EaseClassifier(Time time, DB db) { Timber.d("Calculating probability distributions took: %d ms", t1 - t0); - Timber.d("new\t\t%s", Arrays.toString(this.probabilities[0])); - Timber.d("young\t\t%s", Arrays.toString(this.probabilities[1])); - Timber.d("mature\t%s", Arrays.toString(this.probabilities[2])); + Timber.d("new\t\t%s", Arrays.toString(this.mProbabilities[0])); + Timber.d("young\t\t%s", Arrays.toString(this.mProbabilities[1])); + Timber.d("mature\t%s", Arrays.toString(this.mProbabilities[2])); - Timber.d("Cumulative new\t\t%s", Arrays.toString(this.probabilitiesCumulative[0])); - Timber.d("Cumulative young\t\t%s", Arrays.toString(this.probabilitiesCumulative[1])); - Timber.d("Cumulative mature\t%s", Arrays.toString(this.probabilitiesCumulative[2])); + Timber.d("Cumulative new\t\t%s", Arrays.toString(this.mProbabilitiesCumulative[0])); + Timber.d("Cumulative young\t\t%s", Arrays.toString(this.mProbabilitiesCumulative[1])); + Timber.d("Cumulative mature\t%s", Arrays.toString(this.mProbabilitiesCumulative[2])); - random = new Random(); + mRandom = new Random(); } private double[] cumsum(double[] p) { @@ -686,16 +686,16 @@ private double[] cumsum(double[] p) { } private void calculateCumProbabilitiesForNewEasePerCurrentEase() { - this.probabilities = new double[3][]; - this.probabilitiesCumulative = new double[3][]; + this.mProbabilities = new double[3][]; + this.mProbabilitiesCumulative = new double[3][]; - this.probabilities[CARD_TYPE_NEW] = calculateProbabilitiesForNewEaseForCurrentEase(queryNew, priorNew); - this.probabilities[CARD_TYPE_YOUNG] = calculateProbabilitiesForNewEaseForCurrentEase(queryYoung, priorYoung); - this.probabilities[CARD_TYPE_MATURE] = calculateProbabilitiesForNewEaseForCurrentEase(queryMature, priorMature); + this.mProbabilities[CARD_TYPE_NEW] = calculateProbabilitiesForNewEaseForCurrentEase(queryNew, mPriorNew); + this.mProbabilities[CARD_TYPE_YOUNG] = calculateProbabilitiesForNewEaseForCurrentEase(queryYoung, mPriorYoung); + this.mProbabilities[CARD_TYPE_MATURE] = calculateProbabilitiesForNewEaseForCurrentEase(queryMature, mPriorMature); - this.probabilitiesCumulative[CARD_TYPE_NEW] = cumsum(this.probabilities[CARD_TYPE_NEW]); - this.probabilitiesCumulative[CARD_TYPE_YOUNG] = cumsum(this.probabilities[CARD_TYPE_YOUNG]); - this.probabilitiesCumulative[CARD_TYPE_MATURE] = cumsum(this.probabilities[CARD_TYPE_MATURE]); + this.mProbabilitiesCumulative[CARD_TYPE_NEW] = cumsum(this.mProbabilities[CARD_TYPE_NEW]); + this.mProbabilitiesCumulative[CARD_TYPE_YOUNG] = cumsum(this.mProbabilities[CARD_TYPE_YOUNG]); + this.mProbabilitiesCumulative[CARD_TYPE_MATURE] = cumsum(this.mProbabilities[CARD_TYPE_MATURE]); } /** @@ -717,7 +717,7 @@ private double[] calculateProbabilitiesForNewEaseForCurrentEase(String queryNewE int n = prior[REVIEW_OUTCOME_REPEAT] + prior[REVIEW_OUTCOME_HARD] + prior[REVIEW_OUTCOME_GOOD] + prior[REVIEW_OUTCOME_EASY]; - try (Cursor cur = db.query(queryNewEaseCountForCurrentEase)) { + try (Cursor cur = mDb.query(queryNewEaseCountForCurrentEase)) { cur.moveToNext(); freqs[REVIEW_OUTCOME_REPEAT] += cur.getInt(REVIEW_OUTCOME_REPEAT_PLUS_1); //Repeat @@ -740,7 +740,7 @@ private double[] calculateProbabilitiesForNewEaseForCurrentEase(String queryNewE } private int draw(double[] p) { - return searchsorted(p, random.nextDouble()); + return searchsorted(p, mRandom.nextDouble()); } private int searchsorted(double[] p, double random) { @@ -750,17 +750,17 @@ private int searchsorted(double[] p, double random) { return 3; } - private final ReviewOutcome singleReviewOutcome; + private final ReviewOutcome mSingleReviewOutcome; public ReviewOutcome simSingleReview(Card c){ @Consts.CARD_TYPE int type = c.getType(); - int outcome = draw(probabilitiesCumulative[type]); + int outcome = draw(mProbabilitiesCumulative[type]); applyOutcomeToCard(c, outcome); - singleReviewOutcome.setAll(c, 1); - return singleReviewOutcome; + mSingleReviewOutcome.setAll(c, 1); + return mSingleReviewOutcome; } public ReviewOutcome simSingleReview(Card c, int outcome) { @@ -769,9 +769,9 @@ public ReviewOutcome simSingleReview(Card c, int outcome) { //For first review, re-use current card to prevent creating too many objects applyOutcomeToCard(c, outcome); - singleReviewOutcome.setAll(c, probabilities[c_type][outcome]); + mSingleReviewOutcome.setAll(c, mProbabilities[c_type][outcome]); - return singleReviewOutcome; + return mSingleReviewOutcome; } private void applyOutcomeToCard(Card c, int outcome) { @@ -815,11 +815,11 @@ private void applyOutcomeToCard(Card c, int outcome) { public static class TodayStats { - private final Map nLearnedPerDeckId; + private final Map mNLearnedPerDeckId; public TodayStats(Collection col, long dayStartCutoff) { - nLearnedPerDeckId = new HashMap<>(col.getDecks().count()); + mNLearnedPerDeckId = new HashMap<>(col.getDecks().count()); SupportSQLiteDatabase db = col.getDb().getDatabase(); String query = "select cards.did, "+ @@ -831,14 +831,14 @@ public TodayStats(Collection col, long dayStartCutoff) { try (Cursor cur = db.query(query)) { while(cur.moveToNext()) { - nLearnedPerDeckId.put(cur.getLong(0), cur.getInt(1)); + mNLearnedPerDeckId.put(cur.getLong(0), cur.getInt(1)); } } } public int getNLearned(long did) { - if(nLearnedPerDeckId.containsKey(did)) { - return nLearnedPerDeckId.get(did); + if(mNLearnedPerDeckId.containsKey(did)) { + return mNLearnedPerDeckId.get(did); } else { return 0; @@ -848,27 +848,27 @@ public int getNLearned(long did) { public static class NewCardSimulator { - private int nAddedToday; - private int tAdd; + private int mNAddedToday; + private int mTAdd; public NewCardSimulator() { reset(0); } public int simulateNewCard(Deck deck) { - nAddedToday++; - int tElapsed = tAdd; //differs from online - if (nAddedToday >= deck.getNewPerDay()) { - tAdd++; - nAddedToday = 0; + mNAddedToday++; + int tElapsed = mTAdd; //differs from online + if (mNAddedToday >= deck.getNewPerDay()) { + mTAdd++; + mNAddedToday = 0; } return tElapsed; } public void reset(int nAddedToday) { - this.nAddedToday = nAddedToday; - this.tAdd = 0; + this.mNAddedToday = nAddedToday; + this.mTAdd = 0; } } @@ -883,39 +883,39 @@ public void reset(int nAddedToday) */ private class ReviewSimulator { - private final DB db; - private final EaseClassifier classifier; + private final DB mDb; + private final EaseClassifier mClassifier; //TODO: also exists in Review - private final int nTimeBins; - private final int timeBinLength; + private final int mNTimeBins; + private final int mTimeBinLength; - private final int tMax; + private final int mTMax; - private final NewCardSimulator newCardSimulator = new NewCardSimulator(); + private final NewCardSimulator mNewCardSimulator = new NewCardSimulator(); public ReviewSimulator(DB db, EaseClassifier classifier, int nTimeBins, int timeBinLength) { - this.db = db; - this.classifier = classifier; + this.mDb = db; + this.mClassifier = classifier; - this.nTimeBins = nTimeBins; - this.timeBinLength = timeBinLength; + this.mNTimeBins = nTimeBins; + this.mTimeBinLength = timeBinLength; - this.tMax = this.nTimeBins * this.timeBinLength; + this.mTMax = this.mNTimeBins * this.mTimeBinLength; } public SimulationResult simNreviews(int today, Decks decks, String didsStr, TodayStats todayStats) { - SimulationResult simulationResultAggregated = new SimulationResult(nTimeBins, timeBinLength, SimulationResult.DOUBLE_TO_INT_MODE_ROUND); + SimulationResult simulationResultAggregated = new SimulationResult(mNTimeBins, mTimeBinLength, SimulationResult.DOUBLE_TO_INT_MODE_ROUND); - long[] dids = ArrayUtils.stringToLongArray(didsStr); - int nIterations = Settings.getSimulateNIterations(); + long[] dids = mArrayUtils.stringToLongArray(didsStr); + int nIterations = mSettings.getSimulateNIterations(); double nIterationsInv = 1.0 / nIterations; for(long did : dids) { for(int iteration = 0; iteration < nIterations; iteration++) { - newCardSimulator.reset(todayStats.getNLearned(did)); - simulationResultAggregated.add(simNreviews(today, Decks.createDeck(did, decks)), nIterationsInv); + mNewCardSimulator.reset(todayStats.getNLearned(did)); + simulationResultAggregated.add(simNreviews(today, mDecks.createDeck(did, decks)), nIterationsInv); } } @@ -932,10 +932,10 @@ private SimulationResult simNreviews(int today, Deck deck) { //Since it's the average, it can be a non-integer //Adding a review to a non-integer can make it exceed the maximum # reviews per day, but not by 1 or more //So if we take the floor when displaying it, we will display the maximum # reviews - if(Settings.getComputeNDays() > 0) - simulationResult = new SimulationResult(nTimeBins, timeBinLength, SimulationResult.DOUBLE_TO_INT_MODE_FLOOR); + if(mSettings.getComputeNDays() > 0) + simulationResult = new SimulationResult(mNTimeBins, mTimeBinLength, SimulationResult.DOUBLE_TO_INT_MODE_FLOOR); else - simulationResult = new SimulationResult(nTimeBins, timeBinLength, SimulationResult.DOUBLE_TO_INT_MODE_ROUND); + simulationResult = new SimulationResult(mNTimeBins, mTimeBinLength, SimulationResult.DOUBLE_TO_INT_MODE_ROUND); //nSmooth=1 @@ -962,10 +962,10 @@ private SimulationResult simNreviews(int today, Deck deck) { Card card = new Card(0, 0, 0, 0, 0, 0); CardIterator cardIterator = null; - Review review = new Review(deck, simulationResult, classifier, reviews, reviewList); + Review review = new Review(deck, simulationResult, mClassifier, reviews, reviewList); try { - cardIterator = new CardIterator(db, today, deck); + cardIterator = new CardIterator(mDb, today, deck); //int cardN = 0; @@ -973,9 +973,9 @@ private SimulationResult simNreviews(int today, Deck deck) { cardIterator.current(card); - review.newCard(card, newCardSimulator); + review.newCard(card, mNewCardSimulator); - if (review.getT() < tMax) + if (review.getT() < mTMax) reviews.push(review); //Timber.d("Card started: %d", cardN); @@ -992,8 +992,8 @@ private SimulationResult simNreviews(int today, Deck deck) { if(cardIterator != null) cardIterator.close(); } - ArrayUtils.formatMatrix("nReviews", simulationResult.getNReviews(), "%04d "); - ArrayUtils.formatMatrix("nInState", simulationResult.getNInState(), "%04d "); + mArrayUtils.formatMatrix("nReviews", simulationResult.getNReviews(), "%04d "); + mArrayUtils.formatMatrix("nInState", simulationResult.getNInState(), "%04d "); return simulationResult; } @@ -1004,36 +1004,36 @@ private SimulationResult simNreviews(int today, Deck deck) { */ private static class Settings { - private final int computeNDays; - private final double computeMaxError; - private final int simulateNIterations; + private final int mComputeNDays; + private final double mComputeMaxError; + private final int mSimulateNIterations; private final Collection mCol; public Settings(Context context) { SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(context); mCol = CollectionHelper.getInstance().getCol(context); - computeNDays = prefs.getInt("advanced_forecast_stats_compute_n_days", 0); + mComputeNDays = prefs.getInt("advanced_forecast_stats_compute_n_days", 0); int computePrecision = prefs.getInt("advanced_forecast_stats_compute_precision", 90); - computeMaxError = (100-computePrecision)/100.0; + mComputeMaxError = (100-computePrecision)/100.0; - simulateNIterations = prefs.getInt("advanced_forecast_stats_mc_n_iterations", 1); + mSimulateNIterations = prefs.getInt("advanced_forecast_stats_mc_n_iterations", 1); - Timber.d("computeNDays: %s", computeNDays); - Timber.d("computeMaxError: %s", computeMaxError); - Timber.d("simulateNIterations: %s", simulateNIterations); + Timber.d("computeNDays: %s", mComputeNDays); + Timber.d("computeMaxError: %s", mComputeMaxError); + Timber.d("simulateNIterations: %s", mSimulateNIterations); } public int getComputeNDays() { - return computeNDays; + return mComputeNDays; } public double getComputeMaxError() { - return computeMaxError; + return mComputeMaxError; } public int getSimulateNIterations() { - return simulateNIterations; + return mSimulateNIterations; } /** @@ -1220,12 +1220,12 @@ private class SimulationResult { public static final int DOUBLE_TO_INT_MODE_FLOOR = 0; public static final int DOUBLE_TO_INT_MODE_ROUND = 1; - private final int doubleToIntMode; + private final int mDoubleToIntMode; - private final int nTimeBins; - private final int timeBinLength; + private final int mNTimeBins; + private final int mTimeBinLength; - private final int nDays; + private final int mNDays; /** * Forecasted number of reviews per time bin (a time bin contains statistics for 1 or a multiple of days) @@ -1236,13 +1236,13 @@ private class SimulationResult { * 3 = Relearn * Second dimension: time */ - private final double[][] nReviews; + private final double[][] mNReviews; /** * Forecasted number of reviews per day. - * @see #nReviews + * @see #mNReviews */ - private final double[][] nReviewsPerDay; + private final double[][] mNReviewsPerDay; /** * Forecasted number of cards per state @@ -1252,7 +1252,7 @@ private class SimulationResult { * 2 = Mature * Second dimension: time */ - private final double[][] nInState; + private final double[][] mNInState; /** * Create an empty SimulationResult. @@ -1260,19 +1260,19 @@ private class SimulationResult { * @param timeBinLength Length of 1 time bin in days. */ public SimulationResult(int nTimeBins, int timeBinLength, int doubleToIntMode) { - nReviews = ArrayUtils.createDoubleMatrix(REVIEW_TYPE_COUNT, nTimeBins); - nReviewsPerDay = ArrayUtils.createDoubleMatrix(REVIEW_TYPE_COUNT, nTimeBins * timeBinLength); - nInState = ArrayUtils.createDoubleMatrix(CARD_TYPE_COUNT, nTimeBins); + mNReviews = mArrayUtils.createDoubleMatrix(REVIEW_TYPE_COUNT, nTimeBins); + mNReviewsPerDay = mArrayUtils.createDoubleMatrix(REVIEW_TYPE_COUNT, nTimeBins * timeBinLength); + mNInState = mArrayUtils.createDoubleMatrix(CARD_TYPE_COUNT, nTimeBins); - this.nTimeBins = nTimeBins; - this.timeBinLength = timeBinLength; - this.nDays = nTimeBins * timeBinLength; + this.mNTimeBins = nTimeBins; + this.mTimeBinLength = timeBinLength; + this.mNDays = nTimeBins * timeBinLength; - this.doubleToIntMode = doubleToIntMode; + this.mDoubleToIntMode = doubleToIntMode; } - public int getnDays() { - return nDays; + public int getNDays() { + return mNDays; } /** @@ -1287,22 +1287,22 @@ public void add(SimulationResult res2Add, double prob) { for(int i = 0; i < nReviews.length; i++) for(int j = 0; j < nReviews[i].length; j++) - this.nReviews[i][j] += nReviews[i][j] * prob; + this.mNReviews[i][j] += nReviews[i][j] * prob; //This method is only used to aggregate over decks //We do not update nReviewsPerDay since it is not needed for the SimulationResult aggregated over decks. for(int i = 0; i < nInState.length; i++) for(int j = 0; j < nInState[i].length; j++) - this.nInState[i][j] += nInState[i][j] * prob; + this.mNInState[i][j] += nInState[i][j] * prob; } public int[][] getNReviews() { - return ArrayUtils.toIntMatrix(nReviews, doubleToIntMode); + return mArrayUtils.toIntMatrix(mNReviews, mDoubleToIntMode); } public int[][] getNInState() { - return ArrayUtils.toIntMatrix(nInState, doubleToIntMode); + return mArrayUtils.toIntMatrix(mNInState, mDoubleToIntMode); } /** @@ -1319,8 +1319,8 @@ public int[][] getNInState() { * This excludes new cards and relearns as they don't count towards the limit. */ public int nReviewsDoneToday(int tElapsed) { - return (int)(nReviewsPerDay[REVIEW_TYPE_YOUNG][tElapsed] + - nReviewsPerDay[REVIEW_TYPE_MATURE][tElapsed]); + return (int)(mNReviewsPerDay[REVIEW_TYPE_YOUNG][tElapsed] + + mNReviewsPerDay[REVIEW_TYPE_MATURE][tElapsed]); } /** @@ -1329,8 +1329,8 @@ public int nReviewsDoneToday(int tElapsed) { * @param t Day for which to increment */ public void incrementNReviews(int cardType, int t, double prob) { - nReviews[cardType][t / timeBinLength]+= prob; - nReviewsPerDay[cardType][t]+= prob; + mNReviews[cardType][t / mTimeBinLength]+= prob; + mNReviewsPerDay[cardType][t]+= prob; } /** @@ -1342,12 +1342,12 @@ public void incrementNReviews(int cardType, int t, double prob) { public void updateNInState(Card card, int tFrom, int tTo, double prob) { int cardType = card.getType(); - int t0 = tFrom / timeBinLength; - int t1 = tTo / timeBinLength; + int t0 = tFrom / mTimeBinLength; + int t1 = tTo / mTimeBinLength; for(int t = t0; t < t1; t++) - if(t < nTimeBins) { - nInState[cardType][t]+= prob; + if(t < mNTimeBins) { + mNInState[cardType][t]+= prob; } else { return; } @@ -1369,23 +1369,23 @@ public void updateNInState(Card prevCard, Card card, int tFrom, int tTo, double int prevCardType = prevCard.getType(); int cardType = card.getType(); - int t0 = tFrom / timeBinLength; - int t1 = Math.min(lastReview, tTo) / timeBinLength; + int t0 = tFrom / mTimeBinLength; + int t1 = Math.min(lastReview, tTo) / mTimeBinLength; //Replace state set during last review for(int t = t0; t < t1; t++) - if(t < nTimeBins) { - nInState[prevCardType][t]-= prob; + if(t < mNTimeBins) { + mNInState[prevCardType][t]-= prob; } else { break; } - t1 = tTo / timeBinLength; + t1 = tTo / mTimeBinLength; //With state set during new review for(int t = t0; t < t1; t++) - if(t < nTimeBins) { - nInState[cardType][t]+=prob; + if(t < mNTimeBins) { + mNInState[cardType][t]+=prob; } else { return; } @@ -1406,7 +1406,7 @@ private static class PlottableSimulationResult { // 2 = Young // 3 = Mature // 4 = Relearn - private final ArrayList nReviews; + private final ArrayList mNReviews; // Forecasted number of cards per state // First dimension: @@ -1416,19 +1416,19 @@ private static class PlottableSimulationResult { // 2 = Mature // 1 = Zeros (we can't say 'we know x relearn cards on day d') // Second dimension: time - private final double[][] nInState; + private final double[][] mNInState; public PlottableSimulationResult(ArrayList nReviews, double[][] nInState) { - this.nReviews = nReviews; - this.nInState = nInState; + this.mNReviews = nReviews; + this.mNInState = nInState; } public ArrayList getNReviews() { - return nReviews; + return mNReviews; } public double[][] getNInState() { - return nInState; + return mNInState; } } @@ -1438,32 +1438,32 @@ public double[][] getNInState() { * A ReviewOutcome bundles the probability of the outcome and the card with changed state. */ private static class ReviewOutcome { - private Card card; - private double prob; + private Card mCard; + private double mProb; public ReviewOutcome(Card card, double prob) { - this.card = card; - this.prob = prob; + this.mCard = card; + this.mProb = prob; } public void setAll(Card card, double prob) { - this.card = card; - this.prob = prob; + this.mCard = card; + this.mProb = prob; } public Card getCard() { - return card; + return mCard; } public double getProb() { - return prob; + return mProb; } @Override public @NonNull String toString() { return "ReviewOutcome{" + - "card=" + card + - ", prob=" + prob + + "card=" + mCard + + ", prob=" + mProb + '}'; } } @@ -1476,23 +1476,23 @@ private class Review { /** * Deck-specific setting stored separately to save a method call on the deck object) */ - private final int maxReviewsPerDay; + private final int mMaxReviewsPerDay; /** * Number of reviews simulated for this card at time < tElapsed */ - private int nPrevRevs; + private int mNPrevRevs; /** * The probability that the outcomes of the reviews simulated for this card at time < tElapsed are such that * this review [with this state of the card] will occur [at this time (tElapsed)]. */ - private double prob; + private double mProb; /** * The time instant at which the review takes place. */ - private int tElapsed; + private int mTElapsed; /** * The outcome of the review. @@ -1500,66 +1500,66 @@ private class Review { * (to update statistics, deterime probability of specified outcome, and to schedule subsequent reviews) * Only relevant if we are computing (all possible review outcomes), not if simulating (only one possible outcome) */ - private int outcome; + private int mOutcome; /** * Deck-specific settings */ - private final Deck deck; + private final Deck mDeck; /** * State of the card before current review. * Needed to schedule current review but with different outcome and to update statistics. */ - private Card card = new Card(0, 0, 0, 0, 0, 0); - private final Card prevCard = new Card(0, 0, 0, 0, 0, 0); + private Card mCard = new Card(0, 0, 0, 0, 0, 0); + private final Card mPrevCard = new Card(0, 0, 0, 0, 0, 0); /** * State of the card after current review. * Needed to schedule future review. */ - private Card newCard = new Card(0, 0, 0, 0, 0, 0); + private Card mNewCard = new Card(0, 0, 0, 0, 0, 0); /** * Statistics */ - private final SimulationResult simulationResult; + private final SimulationResult mSimulationResult; /** * Classifier which uses probability distribution from review log to predict outcome of review. */ - private final EaseClassifier classifier; + private final EaseClassifier mClassifier; /** * Reviews which are scheduled to be simulated. * For adding current review with other outcome and future review. */ - private final Stack reviews; + private final Stack mReviews; /** * Review objects to be re-used so that we don't have to create new Review objects all the time. * Be careful: it also contains Review objects which are still in use. * So the algorithm using this list has to make sure that it only re-uses Review objects which are not in use anymore. */ - private final List reviewList; + private final List mReviewlist; /** * For creating future reviews which are to be scheduled as a result of the current review. * @see Review(Deck, SimulationResult, EaseClassifier, Stack) */ private Review (Review prevReview, Card card, int nPrevRevs, int tElapsed, double prob) { - this.deck = prevReview.deck; - this.card.setAll(card); - this.simulationResult = prevReview.simulationResult; - this.classifier = prevReview.classifier; - this.reviews = prevReview.reviews; - this.reviewList = prevReview.reviewList; + this.mDeck = prevReview.mDeck; + this.mCard.setAll(card); + this.mSimulationResult = prevReview.mSimulationResult; + this.mClassifier = prevReview.mClassifier; + this.mReviews = prevReview.mReviews; + this.mReviewlist = prevReview.mReviewlist; - this.nPrevRevs = nPrevRevs; - this.tElapsed = tElapsed; - this.prob = prob; + this.mNPrevRevs = nPrevRevs; + this.mTElapsed = tElapsed; + this.mProb = prob; - this.maxReviewsPerDay = deck.getRevPerDay(); + this.mMaxReviewsPerDay = mDeck.getRevPerDay(); } /** @@ -1572,13 +1572,13 @@ private Review (Review prevReview, Card card, int nPrevRevs, int tElapsed, doubl * @param reviews Will be affected by the review. Scheduled future reviews of this card will be added. */ public Review(Deck deck, SimulationResult simulationResult, EaseClassifier classifier, Stack reviews, List reviewList) { - this.deck = deck; - this.simulationResult = simulationResult; - this.classifier = classifier; - this.reviews = reviews; - this.reviewList = reviewList; + this.mDeck = deck; + this.mSimulationResult = simulationResult; + this.mClassifier = classifier; + this.mReviews = reviews; + this.mReviewlist = reviewList; - this.maxReviewsPerDay = deck.getRevPerDay(); + this.mMaxReviewsPerDay = deck.getRevPerDay(); } /** @@ -1589,21 +1589,21 @@ public Review(Deck deck, SimulationResult simulationResult, EaseClassifier class * Next day new cards will be added might be updated if new card limit has been reached. */ public void newCard(Card card, NewCardSimulator newCardSimulator) { - this.card = card; + this.mCard = card; - this.nPrevRevs = 0; - this.prob = 1; - this.outcome = 0; + this.mNPrevRevs = 0; + this.mProb = 1; + this.mOutcome = 0; //# Rate-limit new cards by shifting starting time if (card.getType() == CARD_TYPE_NEW) - tElapsed = newCardSimulator.simulateNewCard(deck); + mTElapsed = newCardSimulator.simulateNewCard(mDeck); else - tElapsed = card.getDue(); + mTElapsed = card.getDue(); // Set state of card between start and first review // New reviews happen with probability 1 - this.simulationResult.updateNInState(card, 0, tElapsed, 1); + this.mSimulationResult.updateNInState(card, 0, mTElapsed, 1); } /** @@ -1611,12 +1611,12 @@ public void newCard(Card card, NewCardSimulator newCardSimulator) { * and hence the due date is known. */ private void existingCard(Card card, int nPrevRevs, int tElapsed, double prob) { - this.card.setAll(card); + this.mCard.setAll(card); - this.nPrevRevs = nPrevRevs; - this.tElapsed = tElapsed; - this.prob = prob; - this.outcome = 0; + this.mNPrevRevs = nPrevRevs; + this.mTElapsed = tElapsed; + this.mProb = prob; + this.mOutcome = 0; } /** @@ -1628,57 +1628,57 @@ private void existingCard(Card card, int nPrevRevs, int tElapsed, double prob) { */ public void simulateReview() { - if(card.getType() == CARD_TYPE_NEW || simulationResult.nReviewsDoneToday(tElapsed) < maxReviewsPerDay || outcome > 0) { + if(mCard.getType() == CARD_TYPE_NEW || mSimulationResult.nReviewsDoneToday(mTElapsed) < mMaxReviewsPerDay || mOutcome > 0) { // Update the forecasted number of reviews - if(outcome == 0) - simulationResult.incrementNReviews(card.getType(), tElapsed, prob); + if(mOutcome == 0) + mSimulationResult.incrementNReviews(mCard.getType(), mTElapsed, mProb); // Simulate response - prevCard.setAll(card); - newCard.setAll(card); + mPrevCard.setAll(mCard); + mNewCard.setAll(mCard); ReviewOutcome reviewOutcome; - if(tElapsed >= Settings.getComputeNDays() || prob < Settings.getComputeMaxError()) - reviewOutcome = classifier.simSingleReview(newCard); + if(mTElapsed >= mSettings.getComputeNDays() || mProb < mSettings.getComputeMaxError()) + reviewOutcome = mClassifier.simSingleReview(mNewCard); else - reviewOutcome = classifier.simSingleReview(newCard, outcome); + reviewOutcome = mClassifier.simSingleReview(mNewCard, mOutcome); //Timber.d("Simulation at t=" + tElapsed + ": outcome " + outcomeIdx + ": " + reviewOutcome.toString() ); - newCard = reviewOutcome.getCard(); + mNewCard = reviewOutcome.getCard(); double outcomeProb = reviewOutcome.getProb(); //writeLog(newCard, outcomeProb); - newCard.setLastReview(tElapsed); + mNewCard.setLastReview(mTElapsed); // If card failed, update "relearn" count - if(newCard.getCorrect() == 0) - simulationResult.incrementNReviews(3, tElapsed, prob * outcomeProb); + if(mNewCard.getCorrect() == 0) + mSimulationResult.incrementNReviews(3, mTElapsed, mProb * outcomeProb); // Set state of card between current and next review - simulationResult.updateNInState(prevCard, newCard, tElapsed, tElapsed + newCard.getIvl(), prob * outcomeProb); + mSimulationResult.updateNInState(mPrevCard, mNewCard, mTElapsed, mTElapsed + mNewCard.getIvl(), mProb * outcomeProb); // Schedule current review, but with other outcome - if(outcomeProb < 1.0 && outcome < 3) - scheduleCurrentReview(prevCard); + if(outcomeProb < 1.0 && mOutcome < 3) + scheduleCurrentReview(mPrevCard); // Advance time to next review - scheduleNextReview(newCard, tElapsed + newCard.getIvl(), prob * outcomeProb); + scheduleNextReview(mNewCard, mTElapsed + mNewCard.getIvl(), mProb * outcomeProb); } else { // Advance time to next review (max. #reviews reached for this day) - simulationResult.updateNInState(card, card, tElapsed, tElapsed + 1, prob); - rescheduleCurrentReview(tElapsed + 1); + mSimulationResult.updateNInState(mCard, mCard, mTElapsed, mTElapsed + 1, mProb); + rescheduleCurrentReview(mTElapsed + 1); } } private void writeLog(Card newCard, double outcomeProb) { String tabs = ""; - for(int d = 0; d nPrevRevs of the current review which were already scheduled have all already been processed before we do the current review. - if(reviewList.size() > nPrevRevs) { - review = reviewList.get(nPrevRevs); - review.existingCard(newCard, nPrevRevs + 1, newTElapsed, newProb); + if(mReviewlist.size() > mNPrevRevs) { + review = mReviewlist.get(mNPrevRevs); + review.existingCard(newCard, mNPrevRevs + 1, newTElapsed, newProb); } else { - if(reviewList.size() == nPrevRevs) { - review = new Review(this, newCard, nPrevRevs + 1, newTElapsed, newProb); - reviewList.add(review); + if(mReviewlist.size() == mNPrevRevs) { + review = new Review(this, newCard, mNPrevRevs + 1, newTElapsed, newProb); + mReviewlist.add(review); } else { throw new IllegalStateException("State of previous reviews of this card should have been saved for determining possible future reviews other than the current one."); } } - this.reviews.push(review); + this.mReviews.push(review); } } public int getT() { - return tElapsed; + return mTElapsed; } } From eff46d215da4c2b67126f010bf853aecebc9cadd Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Wed, 7 Apr 2021 16:57:35 -0500 Subject: [PATCH 066/171] Dependency updates 20210407 (#8504) * Bump protobuf-java from 3.15.6 to 3.15.7 Bumps [protobuf-java](https://github.com/protocolbuffers/protobuf) from 3.15.6 to 3.15.7. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/master/generate_changelog.py) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v3.15.6...v3.15.7) Signed-off-by: dependabot-preview[bot] * Bump desugar_jdk_libs from 1.0.9 to 1.1.5 Bumps [desugar_jdk_libs](https://github.com/google/desugar_jdk_libs) from 1.0.9 to 1.1.5. - [Release notes](https://github.com/google/desugar_jdk_libs/releases) - [Changelog](https://github.com/google/desugar_jdk_libs/blob/master/CHANGELOG.md) - [Commits](https://github.com/google/desugar_jdk_libs/commits) Signed-off-by: dependabot-preview[bot] * Bump auto-service[-annotations] from 1.0-rc7 to 1.0 Bumps [auto-service-annotations](https://github.com/google/auto) from 1.0-rc7 to 1.0. - [Release notes](https://github.com/google/auto/releases) - [Commits](https://github.com/google/auto/commits/auto-value-1.0) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- AnkiDroid/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 6091d0cbe298..c5348e753ab0 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -222,10 +222,10 @@ dependencies { } } - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' compileOnly 'org.jetbrains:annotations:20.1.0' - compileOnly "com.google.auto.service:auto-service-annotations:1.0-rc7" - annotationProcessor "com.google.auto.service:auto-service:1.0-rc7" + compileOnly "com.google.auto.service:auto-service-annotations:1.0" + annotationProcessor "com.google.auto.service:auto-service:1.0" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.activity:activity:1.2.2' @@ -252,7 +252,7 @@ dependencies { // build with ./gradlew rsdroid:assembleRelease // In my experience, using `files()` currently requires a reindex operation, which is slow. // implementation files("C:\\GitHub\\Rust-Test\\rsdroid\\build\\outputs\\aar\\rsdroid-release.aar") - implementation 'com.google.protobuf:protobuf-java:3.15.6' // This is required when loading from a file + implementation 'com.google.protobuf:protobuf-java:3.15.7' // This is required when loading from a file implementation "io.github.david-allison-1:anki-android-backend:$backendVersion" // build with ./gradlew rsdroid-testing:assembleRelease // RobolectricTest.java: replace RustBackendLoader.init(); with RustBackendLoader.loadRsdroid(path); From 5fa8827e0503f775d81704868cbd20d64fe554b0 Mon Sep 17 00:00:00 2001 From: VINISH <76252038+vinishhub@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:09:02 +0530 Subject: [PATCH 067/171] Reverse Activity transition for RTL language (#8470) * Reverse activity transition for rtl language * Update AnkiDroid/src/main/java/com/ichi2/anim/ActivityTransitionAnimation.java Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> --- .../anim/ActivityTransitionAnimation.java | 27 ++++++++++++++----- .../ichi2/anki/AbstractFlashcardViewer.java | 6 ++--- .../java/com/ichi2/anki/AnkiActivity.java | 2 +- .../main/java/com/ichi2/anki/CardBrowser.java | 6 ++--- .../com/ichi2/anki/CardTemplateEditor.java | 6 ++--- .../com/ichi2/anki/CardTemplatePreviewer.java | 6 ++--- .../main/java/com/ichi2/anki/DeckPicker.java | 10 +++---- .../src/main/java/com/ichi2/anki/Info.java | 4 +-- .../java/com/ichi2/anki/ModelBrowser.java | 6 ++--- .../java/com/ichi2/anki/ModelFieldEditor.java | 2 +- .../ichi2/anki/NavigationDrawerActivity.java | 8 +++--- .../main/java/com/ichi2/anki/NoteEditor.java | 6 ++--- .../main/java/com/ichi2/anki/Reviewer.java | 4 +-- .../main/java/com/ichi2/anki/Statistics.java | 2 +- .../com/ichi2/anki/StudyOptionsActivity.java | 4 +-- .../com/ichi2/anki/StudyOptionsFragment.java | 6 ++--- 16 files changed, 60 insertions(+), 45 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anim/ActivityTransitionAnimation.java b/AnkiDroid/src/main/java/com/ichi2/anim/ActivityTransitionAnimation.java index 90287bf4dc32..44c90bb03242 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anim/ActivityTransitionAnimation.java +++ b/AnkiDroid/src/main/java/com/ichi2/anim/ActivityTransitionAnimation.java @@ -2,16 +2,26 @@ import android.app.Activity; +import android.content.Context; +import android.util.LayoutDirection; import com.ichi2.anki.R; public class ActivityTransitionAnimation { public static void slide(Activity activity, Direction direction) { switch (direction) { - case LEFT: - activity.overridePendingTransition(R.anim.slide_left_in, R.anim.slide_left_out); + case START: + if (isRightToLeft(activity)) { + activity.overridePendingTransition(R.anim.slide_right_in, R.anim.slide_right_out); + } else { + activity.overridePendingTransition(R.anim.slide_left_in, R.anim.slide_left_out); + } break; - case RIGHT: - activity.overridePendingTransition(R.anim.slide_right_in, R.anim.slide_right_out); + case END: + if (isRightToLeft(activity)) { + activity.overridePendingTransition(R.anim.slide_left_in, R.anim.slide_left_out); + } else { + activity.overridePendingTransition(R.anim.slide_right_in, R.anim.slide_right_out); + } break; case FADE: activity.overridePendingTransition(R.anim.fade_out, R.anim.fade_in); @@ -32,12 +42,17 @@ public static void slide(Activity activity, Direction direction) { public enum Direction { - LEFT, - RIGHT, + START, + END, FADE, UP, DOWN, DIALOG_EXIT, NONE } + + private static boolean isRightToLeft(Context c) + { + return c.getResources().getConfiguration().getLayoutDirection() == LayoutDirection.RTL; + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 7888378c27de..45f3e1252f8f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -1327,7 +1327,7 @@ protected void editCard() { Intent editCard = new Intent(AbstractFlashcardViewer.this, NoteEditor.class); editCard.putExtra(NoteEditor.EXTRA_CALLER, NoteEditor.CALLER_REVIEWER); sEditorCard = mCurrentCard; - startActivityForResultWithAnimation(editCard, EDIT_CURRENT_CARD, LEFT); + startActivityForResultWithAnimation(editCard, EDIT_CURRENT_CARD, START); } @@ -1868,7 +1868,7 @@ protected void restoreCollectionPreferences() { Intent deckPicker = new Intent(this, DeckPicker.class); deckPicker.putExtra("collectionLoadError", true); // don't currently do anything with this deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityWithAnimation(deckPicker, LEFT); + startActivityWithAnimation(deckPicker, START); } } @@ -2868,7 +2868,7 @@ protected void closeReviewer(int result, boolean saveDeck) { if (saveDeck) { UIUtils.saveCollectionInBackground(); } - finishWithAnimation(RIGHT); + finishWithAnimation(END); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java index 0603e3f80924..2fec48be4304 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiActivity.java @@ -342,7 +342,7 @@ public void startLoadingCollection() { Intent deckPicker = new Intent(this, DeckPicker.class); deckPicker.putExtra("collectionLoadError", true); // don't currently do anything with this deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - startActivityWithAnimation(deckPicker, LEFT); + startActivityWithAnimation(deckPicker, START); } }); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java index ff964cf4b72a..20b2c9190041 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java @@ -789,7 +789,7 @@ public void openNoteEditorForCard(long cardId) { Intent editCard = new Intent(this, NoteEditor.class); editCard.putExtra(NoteEditor.EXTRA_CALLER, NoteEditor.CALLER_CARDBROWSER_EDIT); editCard.putExtra(NoteEditor.EXTRA_CARD_ID, sCardBrowserCard.getId()); - startActivityForResultWithAnimation(editCard, EDIT_CARD, LEFT); + startActivityForResultWithAnimation(editCard, EDIT_CARD, START); //#6432 - FIXME - onCreateOptionsMenu crashes if receiving an activity result from edit card when in multiselect endMultiSelectMode(); } @@ -1340,7 +1340,7 @@ Intent getAddNoteIntent() { } private void addNoteFromCardBrowser() { - startActivityForResultWithAnimation(getAddNoteIntent(), ADD_NOTE, LEFT); + startActivityForResultWithAnimation(getAddNoteIntent(), ADD_NOTE, START); } @@ -2176,7 +2176,7 @@ private void closeCardBrowser(int result) { private void closeCardBrowser(int result, Intent data) { // Set result and finish setResult(result, data); - finishWithAnimation(RIGHT); + finishWithAnimation(END); } /** diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java index 11384211a857..26a046e55796 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java @@ -213,7 +213,7 @@ public MaterialDialog showDiscardChangesDialog() { // Clear the edited model from any cache files, and clear it from this objects memory to discard changes TemporaryModel.clearTempModelFiles(); mTempModel = null; - finishWithAnimation(RIGHT); + finishWithAnimation(END); }) .build(); discardDialog.show(); @@ -507,7 +507,7 @@ public boolean onOptionsItemSelected(MenuItem item) { tempModel.saveToDatabase(saveModelAndExitHandler()); } else { Timber.d("CardTemplateEditor:: model has not changed, exiting"); - mTemplateEditor.finishWithAnimation(RIGHT); + mTemplateEditor.finishWithAnimation(END); } return true; @@ -694,7 +694,7 @@ public void actualOnPostExecute(@NonNull CardTemplateFragment templateFragment, } templateFragment.mTemplateEditor.mTempModel = null; if (result.first) { - templateFragment.mTemplateEditor.finishWithAnimation(RIGHT); + templateFragment.mTemplateEditor.finishWithAnimation(END); } else { Timber.w("CardTemplateFragment:: save model task failed: %s", result.second); UIUtils.showThemedToast(templateFragment.mTemplateEditor, templateFragment.getString(R.string.card_template_editor_save_error, result.second), false); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java index 820a3ba23dcb..4930066bdd59 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java @@ -34,7 +34,7 @@ import androidx.annotation.Nullable; import timber.log.Timber; -import static com.ichi2.anim.ActivityTransitionAnimation.Direction.RIGHT; +import static com.ichi2.anim.ActivityTransitionAnimation.Direction.END; /** * The card template previewer intent must supply one or more cards to show and the index in the list from where @@ -110,7 +110,7 @@ private void closeCardTemplatePreviewer() { Timber.d("CardTemplatePreviewer:: closeCardTemplatePreviewer()"); setResult(RESULT_OK); TemporaryModel.clearTempModelFiles(); - finishWithAnimation(RIGHT); + finishWithAnimation(END); } @@ -124,7 +124,7 @@ public void onBackPressed() { @Override protected void performReload() { // This should not happen. - finishWithAnimation(RIGHT); + finishWithAnimation(END); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 2bb41a592804..6656d8dcc6ea 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -837,7 +837,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } else if (itemId == R.id.action_model_browser_open) { Timber.i("DeckPicker:: Model browser button pressed"); Intent noteTypeBrowser = new Intent(this, ModelBrowser.class); - startActivityForResultWithAnimation(noteTypeBrowser, 0, LEFT); + startActivityForResultWithAnimation(noteTypeBrowser, 0, START); return true; } else if (itemId == R.id.action_restore_backup) { Timber.i("DeckPicker:: Restore from backup button pressed"); @@ -1157,7 +1157,7 @@ private void showCollectionErrorDialog() { public void addNote() { Intent intent = new Intent(DeckPicker.this, NoteEditor.class); intent.putExtra(NoteEditor.EXTRA_CALLER, NoteEditor.CALLER_DECKPICKER); - startActivityForResultWithAnimation(intent, ADD_NOTE, LEFT); + startActivityForResultWithAnimation(intent, ADD_NOTE, START); } @@ -1307,7 +1307,7 @@ private void showStartupScreensAndDialogs(SharedPreferences preferences, int ski if (skip != 0) { startActivityForResultWithAnimation(infoIntent, SHOW_INFO_NEW_VERSION, - LEFT); + START); } else { startActivityForResultWithoutAnimation(infoIntent, SHOW_INFO_NEW_VERSION); } @@ -2251,7 +2251,7 @@ private void openStudyOptions(boolean withDeckOptions) { Intent intent = new Intent(); intent.putExtra("withDeckOptions", withDeckOptions); intent.setClass(this, StudyOptionsActivity.class); - startActivityForResultWithAnimation(intent, SHOW_STUDYOPTIONS, LEFT); + startActivityForResultWithAnimation(intent, SHOW_STUDYOPTIONS, START); } } @@ -2795,7 +2795,7 @@ public void onRequireDeckListUpdate() { private void openReviewer() { Intent reviewer = new Intent(this, Reviewer.class); - startActivityForResultWithAnimation(reviewer, REQUEST_REVIEW, LEFT); + startActivityForResultWithAnimation(reviewer, REQUEST_REVIEW, START); } @Override diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Info.java b/AnkiDroid/src/main/java/com/ichi2/anki/Info.java index e0c18449c3c5..ffdfa49a2637 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Info.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Info.java @@ -41,7 +41,7 @@ import timber.log.Timber; -import static com.ichi2.anim.ActivityTransitionAnimation.Direction.LEFT; +import static com.ichi2.anim.ActivityTransitionAnimation.Direction.START; /** * Shows an about box, which is a small HTML page. @@ -198,7 +198,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { private void finishWithAnimation() { - finishWithAnimation(LEFT); + finishWithAnimation(START); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java index e1cde9c6ca29..3297933743f0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java @@ -55,7 +55,7 @@ import timber.log.Timber; -import static com.ichi2.anim.ActivityTransitionAnimation.Direction.LEFT; +import static com.ichi2.anim.ActivityTransitionAnimation.Direction.START; public class ModelBrowser extends AnkiActivity { @@ -274,7 +274,7 @@ private void fillModelList() { Intent noteOpenIntent = new Intent(ModelBrowser.this, ModelFieldEditor.class); noteOpenIntent.putExtra("title", mModelDisplayList.get(position).getName()); noteOpenIntent.putExtra("noteTypeID", noteTypeID); - startActivityForResultWithAnimation(noteOpenIntent, 0, LEFT); + startActivityForResultWithAnimation(noteOpenIntent, 0, START); }); mModelListView.setOnItemLongClickListener((parent, view, position, id) -> { @@ -485,7 +485,7 @@ private void dismissContextMenu() { private void openTemplateEditor() { Intent intent = new Intent(this, CardTemplateEditor.class); intent.putExtra("modelId", mCurrentID); - startActivityForResultWithAnimation(intent, REQUEST_TEMPLATE_EDIT, LEFT); + startActivityForResultWithAnimation(intent, REQUEST_TEMPLATE_EDIT, START); } // ---------------------------------------------------------------------------- diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java index 6ab3694337cf..e577ac7e9672 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ModelFieldEditor.java @@ -537,7 +537,7 @@ public void closeActivity() { private void closeActivity(int reason) { - finishWithAnimation(RIGHT); + finishWithAnimation(END); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java index aa3b27daf1cc..2605f9e4a3c3 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java @@ -276,7 +276,7 @@ public void onBackPressed() { */ protected void onNavigationPressed() { if (mNavButtonGoesBack) { - finishWithAnimation(RIGHT); + finishWithAnimation(END); } else { openDrawer(); } @@ -300,14 +300,14 @@ public boolean onNavigationItemSelected(final MenuItem item) { Timber.i("Navigating to decks"); Intent deckPicker = new Intent(NavigationDrawerActivity.this, DeckPicker.class); deckPicker.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); // opening DeckPicker should clear back history - startActivityWithAnimation(deckPicker, RIGHT); + startActivityWithAnimation(deckPicker, END); } else if (itemId == R.id.nav_browser) { Timber.i("Navigating to card browser"); openCardBrowser(); } else if (itemId == R.id.nav_stats) { Timber.i("Navigating to stats"); Intent intent = new Intent(NavigationDrawerActivity.this, Statistics.class); - startActivityForResultWithAnimation(intent, REQUEST_STATISTICS, LEFT); + startActivityForResultWithAnimation(intent, REQUEST_STATISTICS, START); } else if (itemId == R.id.nav_night_mode) { Timber.i("Toggling Night Mode"); mNightModeSwitch.performClick(); @@ -333,7 +333,7 @@ protected void openCardBrowser() { if (currentCardId != null) { intent.putExtra("currentCard", currentCardId); } - startActivityForResultWithAnimation(intent, REQUEST_BROWSE_CARDS, LEFT); + startActivityForResultWithAnimation(intent, REQUEST_BROWSE_CARDS, START); } // Override this to specify a specific card id diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java index 2eba5468e10d..f63ba12e6b9e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java @@ -1255,7 +1255,7 @@ private void openNewNoteEditor(Consumer intentEnricher) { intent.putExtra(EXTRA_DID, mCurrentDid); //mutate event with additional properties intentEnricher.consume(intent); - startActivityForResultWithAnimation(intent, REQUEST_ADD, LEFT); + startActivityForResultWithAnimation(intent, REQUEST_ADD, START); } @@ -1363,7 +1363,7 @@ private void closeNoteEditor(int result, @Nullable Intent intent) { if (mCaller == CALLER_CARDEDITOR_INTENT_ADD) { finishWithAnimation(NONE); } else { - finishWithAnimation(RIGHT); + finishWithAnimation(END); } } @@ -1398,7 +1398,7 @@ private void showCardTemplateEditor() { intent.putExtra("ordId", mCurrentEditedCard.getOrd()); Timber.d("showCardTemplateEditor() with ord %s", mCurrentEditedCard.getOrd()); } - startActivityForResultWithAnimation(intent, REQUEST_TEMPLATE_EDIT, LEFT); + startActivityForResultWithAnimation(intent, REQUEST_TEMPLATE_EDIT, START); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index fb423496ae37..7b58183687d0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -167,7 +167,7 @@ protected void onCreate(Bundle savedInstanceState) { if (FirefoxSnackbarWorkaround.handledLaunchFromWebBrowser(getIntent(), this)) { this.setResult(RESULT_CANCELED); - finishWithAnimation(RIGHT); + finishWithAnimation(END); return; } @@ -551,7 +551,7 @@ private void showResetCardDialog() { private void addNote() { Intent intent = new Intent(this, NoteEditor.class); intent.putExtra(NoteEditor.EXTRA_CALLER, NoteEditor.CALLER_REVIEWER_ADD); - startActivityForResultWithAnimation(intent, ADD_NOTE, LEFT); + startActivityForResultWithAnimation(intent, ADD_NOTE, START); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java b/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java index 30c74776e2d5..c0f271ced063 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java @@ -658,7 +658,7 @@ public void onBackPressed() { data.putExtra("originalDeck", getIntent().getLongExtra("selectedDeck", 0L)); } setResult(RESULT_CANCELED, data); - finishWithAnimation(ActivityTransitionAnimation.Direction.RIGHT); + finishWithAnimation(ActivityTransitionAnimation.Direction.END); } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java index 001b37445829..fc86afe0e219 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java @@ -28,7 +28,7 @@ import timber.log.Timber; -import static com.ichi2.anim.ActivityTransitionAnimation.Direction.RIGHT; +import static com.ichi2.anim.ActivityTransitionAnimation.Direction.END; public class StudyOptionsActivity extends NavigationDrawerActivity implements StudyOptionsListener, CustomStudyDialog.CustomStudyListener { @@ -95,7 +95,7 @@ private void closeStudyOptions() { private void closeStudyOptions(int result) { // mCompat.invalidateOptionsMenu(this); setResult(result); - finishWithAnimation(RIGHT); + finishWithAnimation(END); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java index ffa674fbd22b..16c1c1691141 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java @@ -233,7 +233,7 @@ private void closeStudyOptions(int result) { if (!mFragmented && a != null) { a.setResult(result); a.finish(); - ActivityTransitionAnimation.slide(a, RIGHT); + ActivityTransitionAnimation.slide(a, END); } else if (a == null) { // getActivity() can return null if reference to fragment lingers after parent activity has been closed, // which is particularly relevant when using AsyncTasks. @@ -259,7 +259,7 @@ private void openReviewer() { private void animateLeft() { - ActivityTransitionAnimation.slide(getActivity(), LEFT); + ActivityTransitionAnimation.slide(getActivity(), START); } @@ -418,7 +418,7 @@ private void configureToolbarInternal(boolean recur) { final Drawable icon = AppCompatResources.getDrawable(getContext(), R.drawable.ic_arrow_back_white_24dp); icon.setAutoMirrored(true); mToolbar.setNavigationIcon(icon); - mToolbar.setNavigationOnClickListener(v -> ((AnkiActivity) getActivity()).finishWithAnimation(RIGHT)); + mToolbar.setNavigationOnClickListener(v -> ((AnkiActivity) getActivity()).finishWithAnimation(END)); } } catch (IllegalStateException e) { if (!CollectionHelper.getInstance().colIsOpen()) { From daa7526eb4899da1b7b478a14faef8e3ae5a0f4d Mon Sep 17 00:00:00 2001 From: Piyush Goel <46752548+Arnold2381@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:13:34 +0530 Subject: [PATCH 068/171] code style - m prefix for non-public, non-static fields #8387 (#8503) * Changed variables names according to the new lint rule. ("Lint: Enforce 'm' prefix rule for fields") --- .../com/ichi2/anki/CardTemplateEditor.java | 9 +- .../ichi2/anki/multimediacard/AudioView.java | 12 +- .../fields/BasicAudioClipFieldController.java | 6 +- .../BasicAudioRecordingFieldController.java | 10 +- .../com/ichi2/anki/reviewer/CardMarker.java | 20 +- .../java/com/ichi2/async/CollectionTask.java | 396 +++++++++--------- .../java/com/ichi2/ui/ButtonItemAdapter.java | 42 +- .../ichi2/widget/AnkiDroidWidgetSmall.java | 16 +- 8 files changed, 255 insertions(+), 256 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java index 26a046e55796..dc7ffa42b793 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java @@ -53,7 +53,6 @@ import com.ichi2.anki.exception.ConfirmModSchemaException; import com.ichi2.async.TaskListenerWithContext; import com.ichi2.libanki.Collection; -import com.ichi2.libanki.Consts; import com.ichi2.libanki.Deck; import com.ichi2.libanki.Decks; import com.ichi2.libanki.Model; @@ -296,7 +295,7 @@ private CardTemplateFragment getCurrentFragment() { */ public class TemplatePagerAdapter extends FragmentStateAdapter { - private long baseId = 0; + private long mBaseId = 0; public TemplatePagerAdapter(@NonNull FragmentActivity fragmentActivity) { super(fragmentActivity); @@ -321,18 +320,18 @@ public int getItemCount() { @Override public long getItemId(int position) { - return baseId + position; + return mBaseId + position; } @Override public boolean containsItem(long id) { - return (id - baseId < getItemCount() && id - baseId >= 0); + return (id - mBaseId < getItemCount() && id - mBaseId >= 0); } /** Force fragments to reinitialize contents by invalidating previous set of ordinal-based ids */ public void ordinalShift() { - baseId += getItemCount() + 1; + mBaseId += getItemCount() + 1; } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java index dc2fca56e531..b6744d0c1712 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java @@ -226,7 +226,7 @@ public void togglePlay() { } protected class PlayPauseButton extends AppCompatImageButton { - private final OnClickListener onClickListener = new View.OnClickListener() { + private final OnClickListener mOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { if (mAudioPath == null) { @@ -299,7 +299,7 @@ public PlayPauseButton(Context context) { super(context); setImageResource(mResPlayImage); - setOnClickListener(onClickListener); + setOnClickListener(mOnClickListener); } @@ -323,7 +323,7 @@ public void update() { } protected class StopButton extends AppCompatImageButton { - private final OnClickListener onClickListener = v -> { + private final OnClickListener mOnClickListener = v -> { switch (mStatus) { case PAUSED: case PLAYING: @@ -346,7 +346,7 @@ public StopButton(Context context) { super(context); setImageResource(mResStopImage); - setOnClickListener(onClickListener); + setOnClickListener(mOnClickListener); } @@ -358,7 +358,7 @@ public void update() { } protected class RecordButton extends AppCompatImageButton { - private final OnClickListener onClickListener = new View.OnClickListener() { + private final OnClickListener mOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { // Since mAudioPath is not compulsory, we check if it exists @@ -449,7 +449,7 @@ public RecordButton(Context context) { super(context); setImageResource(mResRecordStopImage); - setOnClickListener(onClickListener); + setOnClickListener(mOnClickListener); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioClipFieldController.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioClipFieldController.java index 956cf174926a..89af333e2fb5 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioClipFieldController.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioClipFieldController.java @@ -45,7 +45,7 @@ public class BasicAudioClipFieldController extends FieldControllerBase implement private static final int ACTIVITY_SELECT_AUDIO_CLIP = 1; - private File storingDirectory; + private File mStoringDirectory; private TextView mTvAudioClip; @@ -54,7 +54,7 @@ public class BasicAudioClipFieldController extends FieldControllerBase implement public void createUI(Context context, LinearLayout layout) { Collection col = CollectionHelper.getInstance().getCol(context); - storingDirectory = new File(col.getMedia().dir()); + mStoringDirectory = new File(col.getMedia().dir()); Button mBtnLibrary = new Button(mActivity); mBtnLibrary.setText(mActivity.getText(R.string.multimedia_editor_image_field_editing_library)); @@ -134,7 +134,7 @@ private void handleAudioSelection(Intent data) { try { clipCopy = File.createTempFile("ankidroid_audioclip_" + audioClipFullNameParts[0], "." + audioClipFullNameParts[1], - storingDirectory); + mStoringDirectory); Timber.d("audio clip picker file path is: %s", clipCopy.getAbsolutePath()); } catch (Exception e) { Timber.e(e, "Could not create temporary audio file. "); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioRecordingFieldController.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioRecordingFieldController.java index 186299229f70..d24b0743f685 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioRecordingFieldController.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicAudioRecordingFieldController.java @@ -34,7 +34,7 @@ public class BasicAudioRecordingFieldController extends FieldControllerBase impl /** * This controller always return a temporary path where it writes the audio */ - private String tempAudioPath; + private String mTempAudioPath; private AudioView mAudioView; @@ -48,21 +48,21 @@ public void createUI(Context context, LinearLayout layout) { File f = new File(origAudioPath); if (f.exists()) { - tempAudioPath = f.getAbsolutePath(); + mTempAudioPath = f.getAbsolutePath(); bExist = true; } } if (!bExist) { - tempAudioPath = AudioView.generateTempAudioFile(mActivity); + mTempAudioPath = AudioView.generateTempAudioFile(mActivity); } mAudioView = AudioView.createRecorderInstance(mActivity, R.drawable.av_play, R.drawable.av_pause, - R.drawable.av_stop, R.drawable.av_rec, R.drawable.av_rec_stop, tempAudioPath); + R.drawable.av_stop, R.drawable.av_rec, R.drawable.av_rec_stop, mTempAudioPath); mAudioView.setOnRecordingFinishEventListener(v -> { // currentFilePath.setText("Recording done, you can preview it. Hit save after finish"); // FIXME is this okay if it is still null? - mField.setAudioPath(tempAudioPath); + mField.setAudioPath(mTempAudioPath); mField.setHasTemporaryMedia(true); }); layout.addView(mAudioView, LinearLayout.LayoutParams.MATCH_PARENT); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/CardMarker.java b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/CardMarker.java index 0ba74a6ce7be..808002864fb3 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/CardMarker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/CardMarker.java @@ -26,22 +26,22 @@ public class CardMarker { public @interface FlagDef {} @NonNull - private final ImageView markView; + private final ImageView mMarkView; @NonNull - private final ImageView flagView; + private final ImageView mFlagView; public CardMarker(@NonNull ImageView markView, @NonNull ImageView flagView) { - this.markView = markView; - this.flagView = flagView; + this.mMarkView = markView; + this.mFlagView = flagView; } /** Sets the mark icon on a card (the star) */ public void displayMark(boolean markStatus) { if (markStatus) { - markView.setVisibility(View.VISIBLE); - markView.setImageResource(R.drawable.ic_star_white_bordered_24dp); + mMarkView.setVisibility(View.VISIBLE); + mMarkView.setImageResource(R.drawable.ic_star_white_bordered_24dp); } else { - markView.setVisibility(View.INVISIBLE); + mMarkView.setVisibility(View.INVISIBLE); } } @@ -62,7 +62,7 @@ public void displayFlag(@FlagDef int flagStatus) { break; case FLAG_NONE: default: - flagView.setVisibility(View.INVISIBLE); + mFlagView.setVisibility(View.INVISIBLE); break; } } @@ -70,7 +70,7 @@ public void displayFlag(@FlagDef int flagStatus) { private void setFlagView(@DrawableRes int drawableId) { //set the resource before to ensure we display the correct icon. - flagView.setImageResource(drawableId); - flagView.setVisibility(View.VISIBLE); + mFlagView.setImageResource(drawableId); + mFlagView.setVisibility(View.VISIBLE); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index 0aec4b615d2e..e037950c5d69 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -235,13 +235,13 @@ protected void onCancelled(){ } public static class AddNote extends Task { - private final Note note; - private final Models.AllowEmpty allowEmpty; + private final Note mNote; + private final Models.AllowEmpty mAllowEmpty; public AddNote(Note note, Models.AllowEmpty allowEmpty) { - this.note = note; - this.allowEmpty = allowEmpty; + this.mNote = note; + this.mAllowEmpty = allowEmpty; } public AddNote(Note note) { @@ -254,7 +254,7 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener try { DB db = col.getDb(); db.executeInTransaction(() -> { - int value = col.addNote(note, allowEmpty); + int value = col.addNote(mNote, mAllowEmpty); collectionTask.doProgress(value); }); } catch (RuntimeException e) { @@ -268,33 +268,33 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener public static class UpdateNote extends Task, BooleanGetter> { - private final Card editCard; - private final boolean fromReviewer; - private final boolean canAccessScheduler; + private final Card mEditCard; + private final boolean mFromReviewer; + private final boolean mCanAccessScheduler; public UpdateNote(Card editCard, boolean fromReviewer, boolean canAccessScheduler) { - this.editCard = editCard; - this.fromReviewer = fromReviewer; - this.canAccessScheduler = canAccessScheduler; + this.mEditCard = editCard; + this.mFromReviewer = fromReviewer; + this.mCanAccessScheduler = canAccessScheduler; } protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener> collectionTask) { Timber.d("doInBackgroundUpdateNote"); // Save the note AbstractSched sched = col.getSched(); - Note editNote = editCard.note(); + Note editNote = mEditCard.note(); try { col.getDb().executeInTransaction(() -> { // TODO: undo integration editNote.flush(); // flush card too, in case, did has been changed - editCard.flush(); - if (fromReviewer) { + mEditCard.flush(); + if (mFromReviewer) { Card newCard; - if (col.getDecks().active().contains(editCard.getDid()) || !canAccessScheduler) { - newCard = editCard; + if (col.getDecks().active().contains(mEditCard.getDid()) || !mCanAccessScheduler) { + newCard = mEditCard; newCard.load(); // reload qa-cache newCard.q(true); @@ -303,7 +303,7 @@ protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener(newCard, null)); // check: are there deleted too? } else { - collectionTask.doProgress(new PairWithCard<>(editCard, editNote.stringTags())); + collectionTask.doProgress(new PairWithCard<>(mEditCard, editNote.stringTags())); } }); } catch (RuntimeException e) { @@ -315,7 +315,7 @@ protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener collectionTask) { - Timber.i("Answering card %d", oldCard.getId()); - col.getSched().answerCard(oldCard, ease); + Timber.i("Answering card %d", mOldCard.getId()); + col.getSched().answerCard(mOldCard, mEase); return super.task(col, collectionTask); } } @@ -377,11 +377,11 @@ protected List task(Collection col, ProgressSenderAndCancelList } public static class SaveCollection extends Task { - private final boolean syncIgnoresDatabaseModification; + private final boolean mSyncIgnoresDatabaseModification; public SaveCollection(boolean syncIgnoresDatabaseModification) { - this.syncIgnoresDatabaseModification = syncIgnoresDatabaseModification; + this.mSyncIgnoresDatabaseModification = syncIgnoresDatabaseModification; } @@ -389,7 +389,7 @@ protected Void task(Collection col, ProgressSenderAndCancelListener collec Timber.d("doInBackgroundSaveCollection"); if (col != null) { try { - if (syncIgnoresDatabaseModification) { + if (mSyncIgnoresDatabaseModification) { SyncStatus.ignoreDatabaseModification(col::save); } else { col.save(); @@ -405,57 +405,57 @@ protected Void task(Collection col, ProgressSenderAndCancelListener collec private static class UndoSuspendCard extends Undoable { - private final Card suspendedCard; + private final Card mSuspendedCard; public UndoSuspendCard(Card suspendedCard) { super(R.string.menu_suspend_card); - this.suspendedCard = suspendedCard; + this.mSuspendedCard = suspendedCard; } public @Nullable Card undo(@NonNull Collection col) { - Timber.i("UNDO: Suspend Card %d", suspendedCard.getId()); - suspendedCard.flush(false); - return suspendedCard; + Timber.i("UNDO: Suspend Card %d", mSuspendedCard.getId()); + mSuspendedCard.flush(false); + return mSuspendedCard; } } private static class UndoDeleteNote extends Undoable { - private final Note note; - private final ArrayList allCs; - private final @NonNull Card card; + private final Note mNote; + private final ArrayList mAllCs; + private final @NonNull Card mCard; public UndoDeleteNote(Note note, ArrayList allCs, @NonNull Card card) { super(R.string.menu_delete_note); - this.note = note; - this.allCs = allCs; - this.card = card; + this.mNote = note; + this.mAllCs = allCs; + this.mCard = card; } public @Nullable Card undo(@NonNull Collection col) { Timber.i("Undo: Delete note"); - ArrayList ids = new ArrayList<>(allCs.size() + 1 ); - note.flush(note.getMod(), false); - ids.add(note.getId()); - for (Card c : allCs) { + ArrayList ids = new ArrayList<>(mAllCs.size() + 1 ); + mNote.flush(mNote.getMod(), false); + ids.add(mNote.getId()); + for (Card c : mAllCs) { c.flush(false); ids.add(c.getId()); } col.getDb().execute("DELETE FROM graves WHERE oid IN " + Utils.ids2str(ids)); - return card; + return mCard; } } public static abstract class DismissNote extends Task { - private final Card card; + private final Card mCard; public DismissNote(Card card) { - this.card = card; + this.mCard = card; } protected abstract void actualTask(Collection col, Card card); @@ -465,7 +465,7 @@ protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener { col.getSched().deferReset(); - actualTask(col, card); + actualTask(col, mCard); // With sHadCardQueue set, getCard() resets the scheduler prior to getting the next card collectionTask.doProgress(col.getSched().getCard()); }); @@ -562,27 +562,27 @@ protected void actualTask(Collection col, Card card) { protected static class UndoSuspendCardMulti extends Undoable { - private final Card[] cards; - private final boolean[] originalSuspended; + private final Card[] mCards; + private final boolean[] mOriginalSuspended; /** @param hasUnsuspended whether there were any unsuspended card (in which card the action was "Suspend", * otherwise the action was "Unsuspend") */ public UndoSuspendCardMulti(Card[] cards, boolean[] originalSuspended, boolean hasUnsuspended) { super((hasUnsuspended) ? R.string.menu_suspend_card : R.string.card_browser_unsuspend_card); - this.cards = cards; - this.originalSuspended = originalSuspended; + this.mCards = cards; + this.mOriginalSuspended = originalSuspended; } public @Nullable Card undo(@NonNull Collection col) { Timber.i("Undo: Suspend multiple cards"); - int nbOfCards = cards.length; + int nbOfCards = mCards.length; List toSuspendIds = new ArrayList<>(nbOfCards); List toUnsuspendIds = new ArrayList<>(nbOfCards); for (int i = 0; i < nbOfCards; i++) { - Card card = cards[i]; - if (originalSuspended[i]) { + Card card = mCards[i]; + if (mOriginalSuspended[i]) { toSuspendIds.add(card.getId()); } else { toUnsuspendIds.add(card.getId()); @@ -609,26 +609,26 @@ public UndoSuspendCardMulti(Card[] cards, boolean[] originalSuspended, private static class UndoDeleteNoteMulti extends Undoable { - private final Note[] notesArr; - private final List allCards; + private final Note[] mNotesArr; + private final List mAllCards; public UndoDeleteNoteMulti(Note[] notesArr, List allCards) { super(R.string.card_browser_delete_card); - this.notesArr = notesArr; - this.allCards = allCards; + this.mNotesArr = notesArr; + this.mAllCards = allCards; } public @Nullable Card undo(@NonNull Collection col) { Timber.i("Undo: Delete notes"); // undo all of these at once instead of one-by-one - ArrayList ids = new ArrayList<>(notesArr.length + allCards.size()); - for (Note n : notesArr) { + ArrayList ids = new ArrayList<>(mNotesArr.length + mAllCards.size()); + for (Note n : mNotesArr) { n.flush(n.getMod(), false); ids.add(n.getId()); } - for (Card c : allCards) { + for (Card c : mAllCards) { c.flush(false); ids.add(c.getId()); } @@ -640,24 +640,24 @@ public UndoDeleteNoteMulti(Note[] notesArr, List allCards) { private static class UndoChangeDeckMulti extends Undoable { - private final Card[] cards; - private final long[] originalDids; + private final Card[] mCards; + private final long[] mOriginalDids; public UndoChangeDeckMulti(Card[] cards, long[] originalDids) { super(R.string.undo_action_change_deck_multi); - this.cards = cards; - this.originalDids = originalDids; + this.mCards = cards; + this.mOriginalDids = originalDids; } public @Nullable Card undo(@NonNull Collection col) { Timber.i("Undo: Change Decks"); // move cards to original deck - for (int i = 0; i < cards.length; i++) { - Card card = cards[i]; + for (int i = 0; i < mCards.length; i++) { + Card card = mCards[i]; card.load(); - card.setDid(originalDids[i]); + card.setDid(mOriginalDids[i]); Note note = card.note(); note.flush(); card.flush(); @@ -668,40 +668,40 @@ public UndoChangeDeckMulti(Card[] cards, long[] originalDids) { } private static class UndoMarkNoteMulti extends Undoable { - private final List originalMarked; - private final List originalUnmarked; + private final List mOriginalMarked; + private final List mOriginalUnmarked; /** @param hasUnmarked whether there were any unmarked card (in which card the action was "mark", * otherwise the action was "Unmark") */ public UndoMarkNoteMulti(List originalMarked, List originalUnmarked, boolean hasUnmarked) { super((hasUnmarked) ? R.string.card_browser_mark_card : R.string.card_browser_unmark_card); - this.originalMarked = originalMarked; - this.originalUnmarked = originalUnmarked; + this.mOriginalMarked = originalMarked; + this.mOriginalUnmarked = originalUnmarked; } public @Nullable Card undo(@NonNull Collection col) { Timber.i("Undo: Mark notes"); - CardUtils.markAll(originalMarked, true); - CardUtils.markAll(originalUnmarked, false); + CardUtils.markAll(mOriginalMarked, true); + CardUtils.markAll(mOriginalUnmarked, false); return null; // don't fetch new card } } private static class UndoRepositionRescheduleResetCards extends Undoable { - private final Card[] cards_copied; + private final Card[] mCardsCopied; public UndoRepositionRescheduleResetCards(@StringRes int undoNameId, Card[] cards_copied) { super(undoNameId); - this.cards_copied = cards_copied; + this.mCardsCopied = cards_copied; } public @Nullable Card undo(@NonNull Collection col) { - Timber.i("Undoing action of type %s on %d cards", getClass(), cards_copied.length); - for (Card card : cards_copied) { + Timber.i("Undoing action of type %s on %d cards", getClass(), mCardsCopied.length); + for (Card card : mCardsCopied) { card.flush(false); } // /* card schedule change undone, reset and get @@ -1095,19 +1095,19 @@ public int getNumCardsToRender() { public static class SearchCards extends Task, List> { - private final String query; - private final boolean order; - private final int numCardsToRender; - private final int column1Index; - private final int column2Index; + private final String mQuery; + private final boolean mOrder; + private final int mNumCardsToRender; + private final int mColumn1Index; + private final int mColumn2Index; public SearchCards(String query, boolean order, int numCardsToRender, int column1Index, int column2Index) { - this.query = query; - this.order = order; - this.numCardsToRender = numCardsToRender; - this.column1Index = column1Index; - this.column2Index = column2Index; + this.mQuery = query; + this.mOrder = order; + this.mNumCardsToRender = numCardsToRender; + this.mColumn1Index = column1Index; + this.mColumn2Index = column2Index; } @@ -1118,7 +1118,7 @@ protected List task(Collection col, ProgressSenderAndCanc return null; } List searchResult = new ArrayList<>(); - List searchResult_ = col.findCards(query, order, new PartialSearch(searchResult, column1Index, column2Index, numCardsToRender, collectionTask, col)); + List searchResult_ = col.findCards(mQuery, mOrder, new PartialSearch(searchResult, mColumn1Index, mColumn2Index, mNumCardsToRender, collectionTask, col)); Timber.d("The search found %d cards", searchResult_.size()); int position = 0; for (Long cid : searchResult_) { @@ -1126,12 +1126,12 @@ protected List task(Collection col, ProgressSenderAndCanc searchResult.add(card); } // Render the first few items - for (int i = 0; i < Math.min(numCardsToRender, searchResult.size()); i++) { + for (int i = 0; i < Math.min(mNumCardsToRender, searchResult.size()); i++) { if (collectionTask.isCancelled()) { Timber.d("doInBackgroundSearchCards was cancelled so return null"); return null; } - searchResult.get(i).load(false, column1Index, column2Index); + searchResult.get(i).load(false, mColumn1Index, mColumn2Index); } // Finish off the task if (collectionTask.isCancelled()) { @@ -1145,19 +1145,19 @@ protected List task(Collection col, ProgressSenderAndCanc public static class RenderBrowserQA extends Task, List>> { - private final CardBrowser.CardCollection cards; - private final Integer startPos; - private final Integer n; - private final int column1Index; - private final int column2Index; + private final CardBrowser.CardCollection mCards; + private final Integer mStartPos; + private final Integer mN; + private final int mColumn1Index; + private final int mColumn2Index; public RenderBrowserQA(CardBrowser.CardCollection cards, Integer mStartPos, Integer n, int column1Index, int column2Index) { - this.cards = cards; - this.startPos = mStartPos; - this.n = n; - this.column1Index = column1Index; - this.column2Index = column2Index; + this.mCards = cards; + this.mStartPos = mStartPos; + this.mN = n; + this.mColumn1Index = column1Index; + this.mColumn2Index = column2Index; } @@ -1166,18 +1166,18 @@ protected Pair, List> ta List invalidCardIds = new ArrayList<>(); // for each specified card in the browser list - for (int i = startPos; i < startPos + n; i++) { + for (int i = mStartPos; i < mStartPos + mN; i++) { // Stop if cancelled if (collectionTask.isCancelled()) { Timber.d("doInBackgroundRenderBrowserQA was aborted"); return null; } - if (i < 0 || i >= cards.size()) { + if (i < 0 || i >= mCards.size()) { continue; } CardBrowser.CardCache card; try { - card = cards.get(i); + card = mCards.get(i); } catch (IndexOutOfBoundsException e) { //even though we test against card.size() above, there's still a race condition @@ -1203,11 +1203,11 @@ protected Pair, List> ta continue; } // Update item - card.load(false, column1Index, column2Index); - float progress = (float) i / n * 100; + card.load(false, mColumn1Index, mColumn2Index); + float progress = (float) i / mN * 100; collectionTask.doProgress((int) progress); } - return new Pair<>(cards, invalidCardIds); + return new Pair<>(mCards, invalidCardIds); } } @@ -1246,11 +1246,11 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col public static class UpdateValuesFromDeck extends Task { - private final boolean reset; + private final boolean mReset; public UpdateValuesFromDeck(boolean reset) { - this.reset = reset; + this.mReset = reset; } @@ -1258,7 +1258,7 @@ public StudyOptionsFragment.DeckStudyData task(Collection col, ProgressSenderAnd Timber.d("doInBackgroundUpdateValuesFromDeck"); try { AbstractSched sched = col.getSched(); - if (reset) { + if (mReset) { // reset actually required because of counts, which is used in getCollectionTaskListener sched.resetCounts(); } @@ -1276,15 +1276,15 @@ public StudyOptionsFragment.DeckStudyData task(Collection col, ProgressSenderAnd public static class DeleteDeck extends Task { - private final long did; + private final long mDid; public DeleteDeck(long did) { - this.did = did; + this.mDid = did; } protected int[] task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackgroundDeleteDeck"); - col.getDecks().rem(did, true); + col.getDecks().rem(mDid, true); // TODO: if we had "undo delete note" like desktop client then we won't need this. col.clearUndo(); return null; @@ -1309,18 +1309,18 @@ protected StudyOptionsFragment.DeckStudyData task(Collection col, ProgressSender } public static class ImportAdd extends Task> { - private final String path; + private final String mPath; public ImportAdd(String path) { - this.path = path; + this.mPath = path; } protected Triple task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackgroundImportAdd"); Resources res = AnkiDroidApp.getInstance().getBaseContext().getResources(); - AnkiPackageImporter imp = new AnkiPackageImporter(col, path); + AnkiPackageImporter imp = new AnkiPackageImporter(col, mPath); imp.setProgressCallback(new TaskManager.ProgressCallback(collectionTask, res)); try { imp.run(); @@ -1334,11 +1334,11 @@ protected Triple task(Collection col, Prog public static class ImportReplace extends Task { - private final String path; + private final String mPath; public ImportReplace(String path) { - this.path = path; + this.mPath = path; } @@ -1358,7 +1358,7 @@ protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener> { - private final String apkgPath; - private final Long did; - private final Boolean includeSched; - private final Boolean includeMedia; + private final String mApkgPath; + private final Long mDid; + private final Boolean mIncludeSched; + private final Boolean mIncludeMedia; public ExportApkg(String apkgPath, Long did, Boolean includeSched, Boolean includeMedia) { - this.apkgPath = apkgPath; - this.did = did; - this.includeSched = includeSched; - this.includeMedia = includeMedia; + this.mApkgPath = apkgPath; + this.mDid = did; + this.mIncludeSched = includeSched; + this.mIncludeMedia = includeMedia; } @@ -1498,8 +1498,8 @@ protected Pair task(Collection col, ProgressSenderAndCancelList Timber.d("doInBackgroundExportApkg"); try { - AnkiPackageExporter exporter = new AnkiPackageExporter(col, did, includeSched, includeMedia); - exporter.exportInto(apkgPath, col.getContext()); + AnkiPackageExporter exporter = new AnkiPackageExporter(col, mDid, mIncludeSched, mIncludeMedia); + exporter.exportInto(mApkgPath, col.getContext()); } catch (FileNotFoundException e) { Timber.e(e, "FileNotFoundException in doInBackgroundExportApkg"); return new Pair<>(false, null); @@ -1513,57 +1513,57 @@ protected Pair task(Collection col, ProgressSenderAndCancelList Timber.e(e, "ImportExportException in doInBackgroundExportApkg"); return new Pair<>(true, e.getMessage()); } - return new Pair<>(false, apkgPath); + return new Pair<>(false, mApkgPath); } } public static class Reorder extends Task { - private final DeckConfig conf; + private final DeckConfig mConf; public Reorder(DeckConfig conf) { - this.conf = conf; + this.mConf = conf; } protected Boolean task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackgroundReorder"); - col.getSched().resortConf(conf); + col.getSched().resortConf(mConf); return true; } } public static class ConfChange extends Task { - private final Deck deck; - private final DeckConfig conf; + private final Deck mDeck; + private final DeckConfig mConf; public ConfChange(Deck deck, DeckConfig conf) { - this.deck = deck; - this.conf = conf; + this.mDeck = deck; + this.mConf = conf; } protected Boolean task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackgroundConfChange"); try { - long newConfId = conf.getLong("id"); + long newConfId = mConf.getLong("id"); // If new config has a different sorting order, reorder the cards - int oldOrder = col.getDecks().getConf(deck.getLong("conf")).getJSONObject("new").getInt("order"); + int oldOrder = col.getDecks().getConf(mDeck.getLong("conf")).getJSONObject("new").getInt("order"); int newOrder = col.getDecks().getConf(newConfId).getJSONObject("new").getInt("order"); if (oldOrder != newOrder) { switch (newOrder) { case 0: - col.getSched().randomizeCards(deck.getLong("id")); + col.getSched().randomizeCards(mDeck.getLong("id")); break; case 1: - col.getSched().orderCards(deck.getLong("id")); + col.getSched().orderCards(mDeck.getLong("id")); break; } } - col.getDecks().setConf(deck, newConfId); + col.getDecks().setConf(mDeck, newConfId); col.save(); return true; } catch (JSONException e) { @@ -1574,17 +1574,17 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col } public static class ConfReset extends Task { - private final DeckConfig conf; + private final DeckConfig mConf; public ConfReset(DeckConfig conf) { - this.conf = conf; + this.mConf = conf; } protected Boolean task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackgroundConfReset"); - col.getDecks().restoreToDefault(conf); + col.getDecks().restoreToDefault(mConf); col.save(); return null; } @@ -1592,11 +1592,11 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col public static class ConfRemove extends Task { - private final DeckConfig conf; + private final DeckConfig mConf; public ConfRemove(DeckConfig conf) { - this.conf = conf; + this.mConf = conf; } @@ -1608,11 +1608,11 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col // When a conf is deleted, all decks using it revert to the default conf. // Cards must be reordered according to the default conf. - int order = conf.getJSONObject("new").getInt("order"); + int order = mConf.getJSONObject("new").getInt("order"); int defaultOrder = col.getDecks().getConf(1).getJSONObject("new").getInt("order"); if (order != defaultOrder) { - conf.getJSONObject("new").put("order", defaultOrder); - col.getSched().resortConf(conf); + mConf.getJSONObject("new").put("order", defaultOrder); + col.getSched().resortConf(mConf); } col.save(); return true; @@ -1624,26 +1624,26 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col } public static class ConfSetSubdecks extends Task { - private final Deck deck; - private final DeckConfig conf; + private final Deck mDeck; + private final DeckConfig mConf; public ConfSetSubdecks(Deck deck, DeckConfig conf) { - this.deck = deck; - this.conf = conf; + this.mDeck = deck; + this.mConf = conf; } protected Boolean task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackgroundConfSetSubdecks"); try { - TreeMap children = col.getDecks().children(deck.getLong("id")); + TreeMap children = col.getDecks().children(mDeck.getLong("id")); for (long childDid : children.values()) { Deck child = col.getDecks().get(childDid); if (child.isDyn()) { continue; } - boolean changed = new ConfChange(child, conf).task(col, collectionTask); + boolean changed = new ConfChange(child, mConf).task(col, collectionTask); if (!changed) { return false; } @@ -1680,20 +1680,20 @@ protected PairWithBoolean>> task(Collection col, ProgressSende public static class DeleteMedia extends Task { - private final List unused; + private final List mUnused; public DeleteMedia(List unused) { - this.unused = unused; + this.mUnused = unused; } protected Integer task(Collection col, ProgressSenderAndCancelListener collectionTask) { com.ichi2.libanki.Media m = col.getMedia(); - for (String fname : unused) { + for (String fname : mUnused) { m.removeFile(fname); } - return unused.size(); + return mUnused.size(); } } @@ -1702,29 +1702,29 @@ protected Integer task(Collection col, ProgressSenderAndCancelListener col * Handles everything for a model change at once - template add / deletes as well as content updates */ public static class SaveModel extends Task> { - private final Model model; - private final ArrayList templateChanges; + private final Model mModel; + private final ArrayList mTemplateChanges; public SaveModel(Model model, ArrayList templateChanges) { - this.model = model; - this.templateChanges = templateChanges; + this.mModel = model; + this.mTemplateChanges = templateChanges; } protected Pair task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackgroundSaveModel"); - Model oldModel = col.getModels().get(model.getLong("id")); + Model oldModel = col.getModels().get(mModel.getLong("id")); // TODO need to save all the cards that will go away, for undo // (do I need to remove them from graves during undo also?) // - undo (except for cards) could just be Models.update(model) / Models.flush() / Collection.reset() (that was prior "undo") - JSONArray newTemplates = model.getJSONArray("tmpls"); + JSONArray newTemplates = mModel.getJSONArray("tmpls"); col.getDb().getDatabase().beginTransaction(); try { - for (Object[] change : templateChanges) { + for (Object[] change : mTemplateChanges) { JSONArray oldTemplates = oldModel.getJSONArray("tmpls"); switch ((TemporaryModel.ChangeType) change[1]) { case ADD: @@ -1732,7 +1732,7 @@ protected Pair task(Collection col, ProgressSenderAndCancelList try { col.getModels().addTemplate(oldModel, newTemplates.getJSONObject((int) change[0])); } catch (Exception e) { - Timber.e(e, "Unable to add template %s to model %s", change[0], model.getLong("id")); + Timber.e(e, "Unable to add template %s to model %s", change[0], mModel.getLong("id")); return new Pair<>(false, e.getLocalizedMessage()); } break; @@ -1741,7 +1741,7 @@ protected Pair task(Collection col, ProgressSenderAndCancelList try { col.getModels().remTemplate(oldModel, oldTemplates.getJSONObject((int) change[0])); } catch (Exception e) { - Timber.e(e, "Unable to delete template %s from model %s", change[0], model.getLong("id")); + Timber.e(e, "Unable to delete template %s from model %s", change[0], mModel.getLong("id")); return new Pair<>(false, e.getLocalizedMessage()); } break; @@ -1751,8 +1751,8 @@ protected Pair task(Collection col, ProgressSenderAndCancelList } } - col.getModels().save(model, true); - col.getModels().update(model); + col.getModels().save(mModel, true); + col.getModels().update(mModel); col.reset(); col.save(); if (col.getDb().getDatabase().inTransaction()) { @@ -1802,18 +1802,18 @@ protected Pair, ArrayList> task(Collection col, Progre * and all notes associated with it */ public static class DeleteModel extends Task { - private final long modID; + private final long mModID; public DeleteModel(long modID) { - this.modID = modID; + this.mModID = modID; } protected Boolean task(Collection col, ProgressSenderAndCancelListener collectionTask) { Timber.d("doInBackGroundDeleteModel"); try { - col.getModels().rem(col.getModels().get(modID)); + col.getModels().rem(col.getModels().get(mModID)); col.save(); } catch (ConfirmModSchemaException e) { e.log(); @@ -1828,13 +1828,13 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col * Deletes the given field in the given model */ public static class DeleteField extends Task { - private final Model model; - private final JSONObject field; + private final Model mModel; + private final JSONObject mField; public DeleteField(Model model, JSONObject field) { - this.model = model; - this.field = field; + this.mModel = model; + this.mField = field; } @@ -1843,7 +1843,7 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col try { - col.getModels().remField(model, field); + col.getModels().remField(mModel, mField); col.save(); } catch (ConfirmModSchemaException e) { //Should never be reached @@ -1858,15 +1858,15 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col * Repositions the given field in the given model */ public static class RepositionField extends Task { - private final Model model; - private final JSONObject field; - private final int index; + private final Model mModel; + private final JSONObject mField; + private final int mIndex; public RepositionField(Model model, JSONObject field, int index) { - this.model = model; - this.field = field; - this.index = index; + this.mModel = model; + this.mField = field; + this.mIndex = index; } @@ -1874,7 +1874,7 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col Timber.d("doInBackgroundRepositionField"); try { - col.getModels().moveField(model, field, index); + col.getModels().moveField(mModel, mField, mIndex); col.save(); } catch (ConfirmModSchemaException e) { e.log(); @@ -1889,19 +1889,19 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col * Adds a field with name in given model */ public static class AddField extends Task { - private final Model model; - private final String fieldName; + private final Model mModel; + private final String mFieldName; public AddField(Model model, String fieldName) { - this.model = model; - this.fieldName = fieldName; + this.mModel = model; + this.mFieldName = fieldName; } protected Boolean task(Collection col, ProgressSenderAndCancelListener collectionTask){ Timber.d("doInBackgroundRepositionField"); - col.getModels().addFieldModChanged(model, col.getModels().newField(fieldName)); + col.getModels().addFieldModChanged(mModel, col.getModels().newField(mFieldName)); col.save(); return true; } @@ -1911,20 +1911,20 @@ protected Boolean task(Collection col, ProgressSenderAndCancelListener col * Adds a field of with name in given model */ public static class ChangeSortField extends Task { - private final Model model; - private final int idx; + private final Model mModel; + private final int mIdx; public ChangeSortField(Model model, int idx) { - this.model = model; - this.idx = idx; + this.mModel = model; + this.mIdx = idx; } protected Boolean task(Collection col, ProgressSenderAndCancelListener collectionTask){ try { Timber.d("doInBackgroundChangeSortField"); - col.getModels().setSortIdx(model, idx); + col.getModels().setSortIdx(mModel, mIdx); col.save(); } catch(Exception e){ Timber.e(e, "Error changing sort field"); @@ -1945,18 +1945,18 @@ protected List task(Collection col, ProgressSenderAndCancelListener> { - private final CardBrowser.CardCollection checkedCards; + private final CardBrowser.CardCollection mCheckedCards; public CheckCardSelection(CardBrowser.CardCollection checkedCards) { - this.checkedCards = checkedCards; + this.mCheckedCards = checkedCards; } protected @Nullable Pair task(Collection col, ProgressSenderAndCancelListener collectionTask) { boolean hasUnsuspended = false; boolean hasUnmarked = false; - for (CardBrowser.CardCache c: checkedCards) { + for (CardBrowser.CardCache c: mCheckedCards) { if (collectionTask.isCancelled()) { Timber.v("doInBackgroundCheckCardSelection: cancelled."); return null; diff --git a/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java b/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java index 6af08a79e095..72a072b26880 100644 --- a/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java +++ b/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java @@ -43,21 +43,21 @@ of this software and associated documentation files (the "Software"), to deal */ public class ButtonItemAdapter extends RecyclerView.Adapter { - private final ArrayList items; - private ItemCallback itemCallback; - private ButtonCallback buttonCallback; + private final ArrayList mItems; + private ItemCallback mItemCallback; + private ButtonCallback mButtonCallback; public ButtonItemAdapter(ArrayList items) { - this.items = items; + this.mItems = items; } public void remove(String searchName) { - items.remove(searchName); + mItems.remove(searchName); } public void setCallbacks(ItemCallback itemCallback, ButtonCallback buttonCallback) { - this.itemCallback = itemCallback; - this.buttonCallback = buttonCallback; + this.mItemCallback = itemCallback; + this.mButtonCallback = buttonCallback; } @Override @@ -70,13 +70,13 @@ public void setCallbacks(ItemCallback itemCallback, ButtonCallback buttonCallbac @Override public void onBindViewHolder(@NonNull ButtonVH holder, int position) { - holder.title.setText(items.get(position)); - holder.button.setTag(items.get(position)); + holder.mTitle.setText(mItems.get(position)); + holder.mButton.setTag(mItems.get(position)); } @Override public int getItemCount() { - return items.size(); + return mItems.size(); } public interface ItemCallback { @@ -89,30 +89,30 @@ public interface ButtonCallback { class ButtonVH extends RecyclerView.ViewHolder implements View.OnClickListener { - private final TextView title; - private final ImageButton button; - private final ButtonItemAdapter adapter; + private final TextView mTitle; + private final ImageButton mButton; + private final ButtonItemAdapter mAdapter; ButtonVH(View itemView, ButtonItemAdapter adapter) { super(itemView); - title = itemView.findViewById(R.id.card_browser_my_search_name_textview); - button = itemView.findViewById(R.id.card_browser_my_search_remove_button); + mTitle = itemView.findViewById(R.id.card_browser_my_search_name_textview); + mButton = itemView.findViewById(R.id.card_browser_my_search_remove_button); - this.adapter = adapter; + this.mAdapter = adapter; itemView.setOnClickListener(this); - button.setOnClickListener(this); + mButton.setOnClickListener(this); } @Override public void onClick(View view) { - if (adapter.itemCallback == null) { + if (mAdapter.mItemCallback == null) { return; } if (view instanceof ImageButton) { - adapter.buttonCallback.onButtonClicked(items.get(getAdapterPosition())); + mAdapter.mButtonCallback.onButtonClicked(mItems.get(getAdapterPosition())); } else { - adapter.itemCallback.onItemClicked(items.get(getAdapterPosition())); + mAdapter.mItemCallback.onItemClicked(mItems.get(getAdapterPosition())); } } } @@ -123,7 +123,7 @@ public void onClick(View view) { * the saved searches in any way, prior to displaying them again */ public void notifyAdapterDataSetChanged() { - Collections.sort(items, String::compareToIgnoreCase); + Collections.sort(mItems, String::compareToIgnoreCase); super.notifyDataSetChanged(); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.java b/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.java index 86f853f17591..553c58f3c060 100644 --- a/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.java +++ b/AnkiDroid/src/main/java/com/ichi2/widget/AnkiDroidWidgetSmall.java @@ -115,7 +115,7 @@ private static void updateWidgetDimensions(Context context, RemoteViews updateVi public static class UpdateService extends Service { /** The cached number of total due cards. */ - private int dueCardsCount; + private int mDueCardsCount; public void doUpdate(Context context) { @@ -173,14 +173,14 @@ public void onReceive(Context context, Intent intent) { } } else { // If we do not have a cached version, always update. - if (dueCardsCount == 0 || updateDueDecksNow) { + if (mDueCardsCount == 0 || updateDueDecksNow) { // Compute the total number of cards due. int[] counts = WidgetStatus.fetchSmall(context); - dueCardsCount = counts[0]; + mDueCardsCount = counts[0]; /* The cached estimated reviewing time. */ int eta = counts[1]; - if (dueCardsCount <= 0) { - if (dueCardsCount == 0) { + if (mDueCardsCount <= 0) { + if (mDueCardsCount == 0) { updateViews.setViewVisibility(R.id.ankidroid_widget_small_finish_layout, View.VISIBLE); } else { updateViews.setViewVisibility(R.id.ankidroid_widget_small_finish_layout, View.INVISIBLE); @@ -189,10 +189,10 @@ public void onReceive(Context context, Intent intent) { } else { updateViews.setViewVisibility(R.id.ankidroid_widget_small_finish_layout, View.INVISIBLE); updateViews.setViewVisibility(R.id.widget_due, View.VISIBLE); - updateViews.setTextViewText(R.id.widget_due, Integer.toString(dueCardsCount)); - updateViews.setContentDescription(R.id.widget_due, context.getResources().getQuantityString(R.plurals.widget_cards_due, dueCardsCount, dueCardsCount)); + updateViews.setTextViewText(R.id.widget_due, Integer.toString(mDueCardsCount)); + updateViews.setContentDescription(R.id.widget_due, context.getResources().getQuantityString(R.plurals.widget_cards_due, mDueCardsCount, mDueCardsCount)); } - if (eta <= 0 || dueCardsCount <= 0) { + if (eta <= 0 || mDueCardsCount <= 0) { updateViews.setViewVisibility(R.id.widget_eta, View.INVISIBLE); } else { updateViews.setViewVisibility(R.id.widget_eta, View.VISIBLE); From a1b88a5a898bb8bc7c58daa48a87c702fadc8fb0 Mon Sep 17 00:00:00 2001 From: Mrudul Tora Date: Thu, 8 Apr 2021 09:20:29 +0530 Subject: [PATCH 069/171] Move Support AnkiDroid to navigation drawer (#8486) * Moved Support AnkiDroid to navigation drawer * Removed Support AnkiDroid from HelpDialogBox * Fixed old unit test and added a test for createInstanceForSupportAnkiDroid * Fixed lint error --- .../ichi2/anki/NavigationDrawerActivity.java | 3 ++ .../com/ichi2/anki/dialogs/HelpDialog.java | 28 +++++++++++++------ .../main/res/drawable/ic_heart_black_24dp.xml | 10 ------- .../res/drawable/ic_support_ankidroid.xml | 10 +++++++ .../src/main/res/menu/navigation_drawer.xml | 4 +++ .../anki/dialogs/utils/HelpDialogTest.java | 13 ++++++++- 6 files changed, 49 insertions(+), 19 deletions(-) delete mode 100644 AnkiDroid/src/main/res/drawable/ic_heart_black_24dp.xml create mode 100644 AnkiDroid/src/main/res/drawable/ic_support_ankidroid.xml diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java index 2605f9e4a3c3..d75c43be1b05 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java @@ -320,6 +320,9 @@ public boolean onNavigationItemSelected(final MenuItem item) { } else if (itemId == R.id.nav_help) { Timber.i("Navigating to help"); showDialogFragment(HelpDialog.createInstance(this)); + } else if (itemId == R.id.support_ankidroid) { + Timber.i("Navigating to support AnkiDroid"); + showDialogFragment(HelpDialog.createInstanceForSupportAnkiDroid(this)); } }; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java index c8ca1a8b0c44..6c25c1f37c94 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java @@ -71,14 +71,6 @@ public static DialogFragment createInstance(Context context) { new FunctionItem(R.string.help_item_report_bug, R.drawable.ic_bug_report_black_24dp, UsageAnalytics.Actions.OPENED_REPORT_BUG, HelpDialog::openFeedback), exceptionReportItem ), - new ItemHeader(R.string.help_title_support_ankidroid, R.drawable.ic_heart_black_24dp, UsageAnalytics.Actions.OPENED_SUPPORT_ANKIDROID, - new LinkItem(R.string.help_item_support_opencollective_donate, R.drawable.ic_donate_black_24dp, UsageAnalytics.Actions.OPENED_DONATE, R.string.link_opencollective_donate), - new LinkItem(R.string.multimedia_editor_trans_translate, R.drawable.ic_language_black_24dp, UsageAnalytics.Actions.OPENED_TRANSLATE, R.string.link_translation), - new LinkItem(R.string.help_item_support_develop_ankidroid, R.drawable.ic_build_black_24, UsageAnalytics.Actions.OPENED_DEVELOP, R.string.link_ankidroid_development_guide), - rateAppItem, - new LinkItem(R.string.help_item_support_other_ankidroid, R.drawable.ic_help_black_24dp, UsageAnalytics.Actions.OPENED_OTHER, R.string.link_contribution), - new FunctionItem(R.string.send_feedback, R.drawable.ic_email_black_24dp, UsageAnalytics.Actions.OPENED_SEND_FEEDBACK, HelpDialog::openFeedback) - ), new ItemHeader(R.string.help_title_community, R.drawable.ic_people_black_24dp, UsageAnalytics.Actions.OPENED_COMMUNITY, new LinkItem(R.string.help_item_anki_forums, R.drawable.ic_forum_black_24dp, UsageAnalytics.Actions.OPENED_ANKI_FORUMS, R.string.link_anki_forum), new LinkItem(R.string.help_item_reddit, R.drawable.ic_mail_outline_black_24dp, UsageAnalytics.Actions.OPENED_REDDIT, R.string.link_reddit), @@ -98,6 +90,26 @@ public static DialogFragment createInstance(Context context) { return RecursivePictureMenu.createInstance(itemList, R.string.help); } + public static DialogFragment createInstanceForSupportAnkiDroid(Context context) { + UsageAnalytics.sendAnalyticsEvent(UsageAnalytics.Category.LINK_CLICKED, UsageAnalytics.Actions.OPENED_SUPPORT_ANKIDROID); + RateAppItem rateAppItem = new RateAppItem(R.string.help_item_support_rate_ankidroid, R.drawable.ic_star_black_24, UsageAnalytics.Actions.OPENED_RATE); + Item[] allItems = { + new LinkItem(R.string.help_item_support_opencollective_donate, R.drawable.ic_donate_black_24dp, UsageAnalytics.Actions.OPENED_DONATE, R.string.link_opencollective_donate), + new LinkItem(R.string.multimedia_editor_trans_translate, R.drawable.ic_language_black_24dp, UsageAnalytics.Actions.OPENED_TRANSLATE, R.string.link_translation), + new LinkItem(R.string.help_item_support_develop_ankidroid, R.drawable.ic_build_black_24, UsageAnalytics.Actions.OPENED_DEVELOP, R.string.link_ankidroid_development_guide), + rateAppItem, + new LinkItem(R.string.help_item_support_other_ankidroid, R.drawable.ic_help_black_24dp, UsageAnalytics.Actions.OPENED_OTHER, R.string.link_contribution), + new FunctionItem(R.string.send_feedback, R.drawable.ic_email_black_24dp, UsageAnalytics.Actions.OPENED_SEND_FEEDBACK, HelpDialog::openFeedback) + }; + + ArrayList itemList = new ArrayList<>(Arrays.asList(allItems)); + + if (!IntentUtil.canOpenIntent(context, AnkiDroidApp.getMarketIntent(context))) { + RecursivePictureMenu.removeFrom(itemList, rateAppItem); + } + return RecursivePictureMenu.createInstance(itemList, R.string.help_title_support_ankidroid); + } + public static class RateAppItem extends Item implements Parcelable { public RateAppItem(@StringRes int titleRes, @DrawableRes int iconRes, String analyticsRes) { diff --git a/AnkiDroid/src/main/res/drawable/ic_heart_black_24dp.xml b/AnkiDroid/src/main/res/drawable/ic_heart_black_24dp.xml deleted file mode 100644 index fad6e25601fb..000000000000 --- a/AnkiDroid/src/main/res/drawable/ic_heart_black_24dp.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/AnkiDroid/src/main/res/drawable/ic_support_ankidroid.xml b/AnkiDroid/src/main/res/drawable/ic_support_ankidroid.xml new file mode 100644 index 000000000000..b9b10ed97771 --- /dev/null +++ b/AnkiDroid/src/main/res/drawable/ic_support_ankidroid.xml @@ -0,0 +1,10 @@ + + + diff --git a/AnkiDroid/src/main/res/menu/navigation_drawer.xml b/AnkiDroid/src/main/res/menu/navigation_drawer.xml index 7f9267a2e950..21229ca3223f 100644 --- a/AnkiDroid/src/main/res/menu/navigation_drawer.xml +++ b/AnkiDroid/src/main/res/menu/navigation_drawer.xml @@ -29,5 +29,9 @@ android:id="@+id/nav_help" android:icon="@drawable/ic_help_black_24dp" android:title="@string/help"/> + \ No newline at end of file diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/HelpDialogTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/HelpDialogTest.java index 1e256f92b80e..9e3f6f9bbf94 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/HelpDialogTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/utils/HelpDialogTest.java @@ -41,6 +41,17 @@ public void testMenuDoesNotCrash() { RecyclerView v = RecursivePictureMenuUtil.getRecyclerViewFor(dialog); - assertThat(v.getChildCount(), is(4)); + assertThat(v.getChildCount(), is(3)); + } + + @Test + public void testmenuSupportAnkiDroidDoesNotCrash() { + RecursivePictureMenu dialog = (RecursivePictureMenu) HelpDialog.createInstanceForSupportAnkiDroid(getTargetContext()); + + super.openDialogFragmentUsingActivity(dialog); + + RecyclerView v = RecursivePictureMenuUtil.getRecyclerViewFor(dialog); + + assertThat(v.getChildCount(), is(5)); } } From 4ef6ce6552f80fc6496839ce9cffaf20c85a5e6f Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:24:03 +0530 Subject: [PATCH 070/171] Add focus function on EditText for keyboard opening (#8465) * Add utility function to handle focus on EditText and opening of keyboard * Remove unused imports * Update AnkiDroid/src/main/java/com/ichi2/utils/AndroidUiUtils.java Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> --- .../main/java/com/ichi2/ui/FixedEditText.java | 11 ++----- .../java/com/ichi2/utils/AndroidUiUtils.java | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/ui/FixedEditText.java b/AnkiDroid/src/main/java/com/ichi2/ui/FixedEditText.java index ea20d7be01e3..41ee39ea2224 100644 --- a/AnkiDroid/src/main/java/com/ichi2/ui/FixedEditText.java +++ b/AnkiDroid/src/main/java/com/ichi2/ui/FixedEditText.java @@ -42,7 +42,8 @@ import android.graphics.Canvas; import android.util.AttributeSet; import android.view.MotionEvent; -import android.view.inputmethod.InputMethodManager; + +import com.ichi2.utils.AndroidUiUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -137,16 +138,10 @@ public boolean performLongClick() { } } - /** * Focuses the edit text and opens the soft keyboard. */ public void focusWithKeyboard() { - // Required on some Android 9,10 devices to show keyboard: https://stackoverflow.com/a/7784904 - this.postDelayed(() -> { - this.requestFocus(); - InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT); - }, 200); + AndroidUiUtils.setFocusAndOpenKeyboard(this); } } \ No newline at end of file diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/AndroidUiUtils.java b/AnkiDroid/src/main/java/com/ichi2/utils/AndroidUiUtils.java index deb7b820c4a8..a6adf9746b1f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/AndroidUiUtils.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/AndroidUiUtils.java @@ -19,6 +19,12 @@ import android.app.UiModeManager; import android.content.Context; import android.content.res.Configuration; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.NonNull; import static androidx.core.content.ContextCompat.getSystemService; @@ -31,4 +37,27 @@ public static boolean isRunningOnTv(Context context) { return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; } + /** + * This method is used for setting the focus on an EditText which is used in a dialog + * and for opening the keyboard. + * @param view The EditText which requires the focus to be set. + * @param window The window where the view is present. + */ + public static void setFocusAndOpenKeyboard(View view, @NonNull Window window) { + view.requestFocus(); + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + } + + /** + * Focuses on View and opens the soft keyboard. + * @param view The View which requires the focus to be set (typically an EditText). + */ + public static void setFocusAndOpenKeyboard(View view) { + // Required on some Android 9, 10 devices to show keyboard: https://stackoverflow.com/a/7784904 + view.postDelayed(() -> { + view.requestFocus(); + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + }, 200); + } } From 2ec3cded64ed7992050104d87a3d717de76f1883 Mon Sep 17 00:00:00 2001 From: Mani <12841290+infinyte7@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:25:38 +0530 Subject: [PATCH 071/171] Get deck name using JS API (#8500) * get deck name * Added test for getting deck name using js api --- .../ichi2/anki/AbstractFlashcardViewer.java | 5 +++++ .../java/com/ichi2/anki/ReviewerTest.java | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 45f3e1252f8f..76f027f9875a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -4098,6 +4098,11 @@ public boolean ankiIsInNightMode() { @JavascriptInterface public boolean ankiIsDisplayingAnswer() { return isDisplayingAnswer(); }; + @JavascriptInterface + public String ankiGetDeckName() { + return Decks.basename(getCol().getDecks().get(mCurrentCard.getDid()).getString("name")); + } + @JavascriptInterface public boolean ankiIsActiveNetworkMetered() { try { diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.java index fe5965e58559..80ea147a9589 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/ReviewerTest.java @@ -246,6 +246,27 @@ public void baseDeckName() { assertThat(reviewer.getSupportActionBar().getTitle(), is("B")); } + @Test + public void jsAnkiGetDeckName() { + Collection col = getCol(); + Models models = col.getModels(); + Decks decks = col.getDecks(); + + Long didAb = addDeck("A::B"); + Model basic = models.byName(AnkiDroidApp.getAppResources().getString(R.string.basic_model_name)); + basic.put("did", didAb); + addNoteUsingBasicModel("foo", "bar"); + + Long didA = addDeck("A"); + decks.select(didA); + + Reviewer reviewer = startReviewer(); + JavaScriptFunction javaScriptFunction = reviewer.javaScriptFunction(); + + waitForAsyncTasksToComplete(); + assertThat(javaScriptFunction.ankiGetDeckName(), is("B")); + } + private void toggleWhiteboard(ReviewerForMenuItems reviewer) { reviewer.toggleWhiteboard(); From 559b3f1e47c1dbdb6395007b31592d311337e003 Mon Sep 17 00:00:00 2001 From: Shubhankar Bhadra <42507632+shobhi1310@users.noreply.github.com> Date: Thu, 8 Apr 2021 09:28:54 +0530 Subject: [PATCH 072/171] only perform card action if card is same as when touch started (#8480) --- .../ichi2/anki/AbstractFlashcardViewer.java | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 76f027f9875a..5d867e0560e7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -438,31 +438,42 @@ public void onClick(View view) { } }; - private final View.OnClickListener mSelectEaseHandler = new View.OnClickListener() { + private final View.OnTouchListener mSelectEaseHandler = new View.OnTouchListener() { + Card mPrevCard; @Override - public void onClick(View view) { - // Ignore what is most likely an accidental double-tap. - if (SystemClock.elapsedRealtime() - mLastClickTime < DOUBLE_TAP_IGNORE_THRESHOLD) { - return; + public boolean onTouch(View view, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + // Save card when button pressed + mPrevCard = mCurrentCard; + return true; } - mLastClickTime = SystemClock.elapsedRealtime(); - mTimeoutHandler.removeCallbacks(mShowQuestionTask); - int id = view.getId(); - if (id == R.id.flashcard_layout_ease1) { - Timber.i("AbstractFlashcardViewer:: EASE_1 pressed"); - answerCard(Consts.BUTTON_ONE); - } else if (id == R.id.flashcard_layout_ease2) { - Timber.i("AbstractFlashcardViewer:: EASE_2 pressed"); - answerCard(Consts.BUTTON_TWO); - } else if (id == R.id.flashcard_layout_ease3) { - Timber.i("AbstractFlashcardViewer:: EASE_3 pressed"); - answerCard(Consts.BUTTON_THREE); - } else if (id == R.id.flashcard_layout_ease4) { - Timber.i("AbstractFlashcardViewer:: EASE_4 pressed"); - answerCard(Consts.BUTTON_FOUR); - } else { - mCurrentEase = 0; + // Perform intended action only if the button has been pressed for current card + if (event.getAction() == MotionEvent.ACTION_UP && mPrevCard == mCurrentCard) { + // Ignore what is most likely an accidental double-tap. + if (SystemClock.elapsedRealtime() - mLastClickTime < DOUBLE_TAP_IGNORE_THRESHOLD) { + return false; + } + mLastClickTime = SystemClock.elapsedRealtime(); + mTimeoutHandler.removeCallbacks(mShowQuestionTask); + int id = view.getId(); + if (id == R.id.flashcard_layout_ease1) { + Timber.i("AbstractFlashcardViewer:: EASE_1 pressed"); + answerCard(Consts.BUTTON_ONE); + } else if (id == R.id.flashcard_layout_ease2) { + Timber.i("AbstractFlashcardViewer:: EASE_2 pressed"); + answerCard(Consts.BUTTON_TWO); + } else if (id == R.id.flashcard_layout_ease3) { + Timber.i("AbstractFlashcardViewer:: EASE_3 pressed"); + answerCard(Consts.BUTTON_THREE); + } else if (id == R.id.flashcard_layout_ease4) { + Timber.i("AbstractFlashcardViewer:: EASE_4 pressed"); + answerCard(Consts.BUTTON_FOUR); + } else { + mCurrentEase = 0; + } + return true; } + return false; } }; @@ -1516,19 +1527,19 @@ protected void initLayout() { mEase1 = findViewById(R.id.ease1); mEase1Layout = findViewById(R.id.flashcard_layout_ease1); - mEase1Layout.setOnClickListener(mSelectEaseHandler); + mEase1Layout.setOnTouchListener(mSelectEaseHandler); mEase2 = findViewById(R.id.ease2); mEase2Layout = findViewById(R.id.flashcard_layout_ease2); - mEase2Layout.setOnClickListener(mSelectEaseHandler); + mEase2Layout.setOnTouchListener(mSelectEaseHandler); mEase3 = findViewById(R.id.ease3); mEase3Layout = findViewById(R.id.flashcard_layout_ease3); - mEase3Layout.setOnClickListener(mSelectEaseHandler); + mEase3Layout.setOnTouchListener(mSelectEaseHandler); mEase4 = findViewById(R.id.ease4); mEase4Layout = findViewById(R.id.flashcard_layout_ease4); - mEase4Layout.setOnClickListener(mSelectEaseHandler); + mEase4Layout.setOnTouchListener(mSelectEaseHandler); mNext1 = findViewById(R.id.nextTime1); mNext2 = findViewById(R.id.nextTime2); From 55069d5d6348a1bd447ef65042a59ce84ea5f798 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 7 Apr 2021 20:03:52 +0100 Subject: [PATCH 073/171] tests: Fix NullPointerException Removes log noise Slight perf improvement as we don't get the `throw` And `PackageManager packageManager = getPackageManager();` was unused --- AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index e91f4a459614..f5715492571f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -25,7 +25,6 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.net.Uri; @@ -41,7 +40,6 @@ import android.util.Log; import android.view.ViewConfiguration; import android.webkit.CookieManager; -import android.webkit.WebView; import com.ichi2.anki.analytics.AnkiDroidCrashReportDialog; import com.ichi2.anki.contextmenu.AnkiCardContextMenu; @@ -72,9 +70,7 @@ import org.acra.sender.HttpSender; import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -730,8 +726,11 @@ private HashMap fetchWebViewInformation() { webViewInfo.put("WEBVIEW_VER_NAME", ""); webViewInfo.put("WEBVIEW_VER_CODE", ""); try { - PackageManager packageManager = getPackageManager(); PackageInfo pi = WebViewCompat.getCurrentWebViewPackage(this); + if (pi == null) { + Timber.w("Could not get WebView package information"); + return webViewInfo; + } webViewInfo.put("WEBVIEW_VER_NAME", pi.versionName); webViewInfo.put("WEBVIEW_VER_CODE", String.valueOf(PackageInfoCompat.getLongVersionCode(pi))); } catch (Throwable e) { From 717c95648bec4c49525d753c91f5a7873d30f96d Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 8 Apr 2021 03:59:07 +0100 Subject: [PATCH 074/171] fix crash: NullPointerException while syncing ReminderService was refactored in a commit which moved `deckDueTree` outside a try..catch https://github.com/ankidroid/Anki-Android/commit/5128cc2c756ce1434367eb58807650581f26c661#diff-eab9b1b76312c13e47b4f32c7297da450ce522c4f6d15c79e55a8a8a5c51364fR172 We put it back in, and add a test to simulate a thrown exception when accessing the collection. We explicitly test getSched() to target this call Add in a helper method to CollectionHelper to allow us to simulate getDb() failures due to closing the collection while syncing Fixes #8264 --- .../java/com/ichi2/anki/CollectionHelper.java | 4 +++ .../ichi2/anki/services/ReminderService.java | 4 +-- .../anki/services/ReminderServiceTest.java | 27 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java index b3e58558b0ec..14f0f2175c26 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java @@ -400,4 +400,8 @@ public static int getDatabaseVersion(Context context) throws UnknownDatabaseVers } } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public void setColForTests(Collection col) { + this.mCollection = col; + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/services/ReminderService.java b/AnkiDroid/src/main/java/com/ichi2/anki/services/ReminderService.java index 0ece8b0340b4..a6d66c6729d1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/services/ReminderService.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/services/ReminderService.java @@ -169,9 +169,9 @@ private List getDeckOptionDue(Collection col, long dConfId, boo return null; } - List dues = col.getSched().deckDueTree(); - List decks = new ArrayList<>(dues.size()); try { + List dues = col.getSched().deckDueTree(); + List decks = new ArrayList<>(dues.size()); // This loop over top level deck only. No notification will ever occur for subdecks. for (DeckDueTreeNode node : dues) { JSONObject deck = col.getDecks().get(node.getDid(), false); diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/services/ReminderServiceTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/services/ReminderServiceTest.java index 7c259fcea76a..ea2b4d9b1aa9 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/services/ReminderServiceTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/services/ReminderServiceTest.java @@ -4,7 +4,9 @@ import android.content.Context; import android.content.Intent; +import com.ichi2.anki.CollectionHelper; import com.ichi2.anki.RobolectricTest; +import com.ichi2.libanki.Collection; import org.junit.Test; import org.junit.runner.RunWith; @@ -15,6 +17,10 @@ import static com.ichi2.anki.services.ReminderService.EXTRA_DECK_ID; import static com.ichi2.anki.services.ReminderService.EXTRA_DECK_OPTION_ID; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @RunWith(AndroidJUnit4.class) @@ -47,6 +53,27 @@ public void testReminderServiceNullCollection() { } + /** + * #8264: Crash on sync - getSched().getDueTree() failed + */ + @Test + public void testDatabaseFailureWhileSyncingDoesNotCrash() { + // If getCol() fails, it triggers different exception handling in the service. + // The cause was getSched().deckDueTree() + Collection baseCol = getCol(); + Collection mockCol = spy(baseCol); + when(mockCol.getSched()).thenThrow(new IllegalStateException("Unit test: simulating database exception")); + + CollectionHelper.getInstance().setColForTests(mockCol); + + buildDefaultDeckReminders(); + + // We retry after a database timeout so getSched is called twice + //noinspection ResultOfMethodCallIgnored + verify(mockCol, times(2)).getSched(); + } + + private ShadowNotificationManager getNotificationManagerShadow() { return shadowOf((NotificationManager) getTargetContext().getSystemService(Context.NOTIFICATION_SERVICE)); } From 2888bd068baaea1f5a084d3fbc1866bcd0e1771f Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 7 Apr 2021 03:38:48 +0100 Subject: [PATCH 075/171] NF: Extract AudioRecorder from AudioView I want an end-to-end integration test which does not depend on system components - being able to replace the "record" call makes it easy to verify that the chain of operations is working correctly As a side effect: simplifies AudioView --- .../anki/multimediacard/AudioRecorder.java | 103 ++++++++++++++++++ .../ichi2/anki/multimediacard/AudioView.java | 67 +++--------- 2 files changed, 117 insertions(+), 53 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioRecorder.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioRecorder.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioRecorder.java new file mode 100644 index 000000000000..9bf72f97a8b5 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioRecorder.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2013 Bibek Shrestha + * Copyright (c) 2013 Zaur Molotnikov + * Copyright (c) 2013 Nicolas Raoul + * Copyright (c) 2013 Flavio Lerda + * Copyright (c) 2021 David Allison + * + * 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.multimediacard; + +import android.media.MediaRecorder; + +import java.io.IOException; + +import androidx.annotation.Nullable; +import timber.log.Timber; + +public class AudioRecorder { + + @Nullable + private MediaRecorder mRecorder = null; + + @Nullable + private Runnable mOnRecordingInitialized; + + + private MediaRecorder initMediaRecorder(String audioPath) { + MediaRecorder mr = new MediaRecorder(); + mr.setAudioSource(MediaRecorder.AudioSource.MIC); + mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + onRecordingInitialized(); + mr.setOutputFile(audioPath); // audioPath could change + return mr; + } + + + private void onRecordingInitialized() { + if (mOnRecordingInitialized != null) { + mOnRecordingInitialized.run(); + } + } + + public void startRecording(String audioPath) throws IOException { + boolean highSampling = false; + try { + // try high quality AAC @ 44.1kHz / 192kbps first + // can throw IllegalArgumentException if codec isn't supported + mRecorder = initMediaRecorder(audioPath); + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + mRecorder.setAudioChannels(2); + mRecorder.setAudioSamplingRate(44100); + mRecorder.setAudioEncodingBitRate(192000); + // this can also throw IOException if output path is invalid + mRecorder.prepare(); + mRecorder.start(); + highSampling = true; + } catch (Exception e) { + Timber.w(e); + // in all cases, fall back to low sampling + } + + if (!highSampling) { + // if we are here, either the codec didn't work or output file was invalid + // fall back on default + mRecorder = initMediaRecorder(audioPath); + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + + mRecorder.prepare(); + mRecorder.start(); + } + } + + + public void stopRecording() { + if (mRecorder != null) { + mRecorder.stop(); + } + } + + + public void setOnRecordingInitializedHandler(Runnable onRecordingInitialized) { + this.mOnRecordingInitialized = onRecordingInitialized; + } + + + public void release() { + if (mRecorder != null) { + mRecorder.release(); + } + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java index b6744d0c1712..992695487534 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java @@ -22,7 +22,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.media.MediaPlayer; -import android.media.MediaRecorder; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -50,7 +49,7 @@ public class AudioView extends LinearLayout { protected StopButton mStop = null; protected RecordButton mRecord = null; - private MediaRecorder mRecorder = null; + private final AudioRecorder mAudioRecorder = new AudioRecorder(); private MediaPlayer mPlayer = null; private OnRecordingFinishEventListener mOnRecordingFinishEventListener = null; @@ -104,6 +103,8 @@ String generateTempAudioFile(@NonNull Context context) { private AudioView(Context context, int resPlay, int resPause, int resStop, String audioPath) { super(context); + mAudioRecorder.setOnRecordingInitializedHandler(() -> mStatus = Status.INITIALIZED); + mContext = context; mResPlayImage = resPlay; @@ -188,9 +189,9 @@ public void notifyRecord() { public void notifyStopRecord() { - if (mRecorder != null && mStatus == Status.RECORDING) { + if (mStatus == Status.RECORDING) { try { - mRecorder.stop(); + mAudioRecorder.stopRecording(); } catch (RuntimeException e) { Timber.i(e, "Recording stop failed, this happens if stop was hit immediately after start"); UIUtils.showThemedToast(mContext, gtxt(R.string.multimedia_editor_audio_view_recording_failed), true); @@ -208,9 +209,7 @@ public void notifyStopRecord() { } public void notifyReleaseRecorder() { - if (mRecorder != null) { - mRecorder.release(); - } + mAudioRecorder.release(); } public void toggleRecord() { @@ -366,7 +365,7 @@ public void onClick(View v) { return; } - //We can get to this screen without permissions through the "Pronunciation" feature. + // We can get to this screen without permissions through the "Pronunciation" feature. if (!Permissions.canRecordAudio(mContext)) { Timber.w("Audio recording permission denied."); UIUtils.showThemedToast(mContext, @@ -378,42 +377,15 @@ public void onClick(View v) { switch (mStatus) { case IDLE: // If not already recorded or not already played case STOPPED: // if already recorded or played - boolean highSampling = false; + try { - // try high quality AAC @ 44.1kHz / 192kbps first - // can throw IllegalArgumentException if codec isn't supported - mRecorder = initMediaRecorder(); - mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); - mRecorder.setAudioChannels(2); - mRecorder.setAudioSamplingRate(44100); - mRecorder.setAudioEncodingBitRate(192000); - // this can also throw IOException if output path is invalid - mRecorder.prepare(); - mRecorder.start(); - highSampling = true; + mAudioRecorder.startRecording(mAudioPath); } catch (Exception e) { - Timber.w(e); - // in all cases, fall back to low sampling - } - - if (!highSampling) { - // if we are here, either the codec didn't work or output file was invalid - // fall back on default - try { - mRecorder = initMediaRecorder(); - mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); - - mRecorder.prepare(); - mRecorder.start(); - - } catch (Exception e) { - // either output file failed or codec didn't work, in any case fail out - Timber.e("RecordButton.onClick() :: error recording to %s\n%s", mAudioPath, e.getMessage()); - UIUtils.showThemedToast(mContext, gtxt(R.string.multimedia_editor_audio_view_recording_failed), true); - mStatus = Status.STOPPED; - break; - } - + // either output file failed or codec didn't work, in any case fail out + Timber.e("RecordButton.onClick() :: error recording to %s\n%s", mAudioPath, e.getMessage()); + UIUtils.showThemedToast(mContext, gtxt(R.string.multimedia_editor_audio_view_recording_failed), true); + mStatus = Status.STOPPED; + break; } mStatus = Status.RECORDING; @@ -431,17 +403,6 @@ public void onClick(View v) { break; } } - - private MediaRecorder initMediaRecorder() { - MediaRecorder mr = new MediaRecorder(); - mr.setAudioSource(MediaRecorder.AudioSource.MIC); - mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); - mStatus = Status.INITIALIZED; - mr.setOutputFile(mAudioPath); // audioPath - // could - // change - return mr; - } }; From bdb0c0564879d9833efef71ed6e6a77f343c7453 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 7 Apr 2021 23:58:17 +0100 Subject: [PATCH 076/171] NF: Extract AudioPlayer from AudioView I want an end-to-end integration test which does not depend on system components - being able to replace the "play" call makes it easy to verify that the chain of operations is working correctly As a side effect: simplifies AudioView --- .../anki/multimediacard/AudioPlayer.java | 94 +++++++++++++++++++ .../ichi2/anki/multimediacard/AudioView.java | 27 ++---- 2 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioPlayer.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioPlayer.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioPlayer.java new file mode 100644 index 000000000000..df1a816612cf --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioPlayer.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2013 Bibek Shrestha + * Copyright (c) 2013 Zaur Molotnikov + * Copyright (c) 2013 Nicolas Raoul + * Copyright (c) 2013 Flavio Lerda + * Copyright (c) 2021 David Allison + * + * 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.multimediacard; + +import android.media.MediaPlayer; + +import java.io.IOException; + +import androidx.annotation.Nullable; +import timber.log.Timber; + +public class AudioPlayer { + + private MediaPlayer mPlayer; + @Nullable private Runnable mOnStoppingListener; + @Nullable private Runnable mOnStoppedListener; + + + public void play(String audioPath) throws IOException { + mPlayer = new MediaPlayer(); + mPlayer.setDataSource(audioPath); + mPlayer.setOnCompletionListener(mp -> { + onStopping(); + mPlayer.stop(); + onStopped(); + }); + mPlayer.prepare(); + mPlayer.start(); + } + + + private void onStopped() { + if (mOnStoppedListener == null) { + return; + } + mOnStoppedListener.run(); + } + + + private void onStopping() { + if (mOnStoppingListener == null) { + return; + } + mOnStoppingListener.run(); + } + + + public void start() { + mPlayer.start(); + } + + + public void stop() { + try { + mPlayer.prepare(); + mPlayer.seekTo(0); + } catch (Exception e) { + Timber.e(e); + } + } + + + public void pause() { + mPlayer.pause(); + } + + + public void setOnStoppingListener(Runnable listener) { + this.mOnStoppingListener = listener; + } + + + public void setOnStoppedListener(Runnable listener) { + this.mOnStoppedListener = listener; + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java index 992695487534..f2c07a809c89 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java @@ -21,10 +21,10 @@ import android.annotation.SuppressLint; import android.content.Context; -import android.media.MediaPlayer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.appcompat.widget.AppCompatImageButton; import android.view.Gravity; @@ -50,7 +50,7 @@ public class AudioView extends LinearLayout { protected RecordButton mRecord = null; private final AudioRecorder mAudioRecorder = new AudioRecorder(); - private MediaPlayer mPlayer = null; + private final AudioPlayer mPlayer = new AudioPlayer(); private OnRecordingFinishEventListener mOnRecordingFinishEventListener = null; @@ -64,7 +64,8 @@ public class AudioView extends LinearLayout { private final Context mContext; - enum Status { + @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) + public enum Status { IDLE, // Default initial state INITIALIZED, // When datasource has been set PLAYING, PAUSED, STOPPED, // The different possible states once playing @@ -103,6 +104,9 @@ String generateTempAudioFile(@NonNull Context context) { private AudioView(Context context, int resPlay, int resPause, int resStop, String audioPath) { super(context); + mPlayer.setOnStoppingListener(() -> mStatus = Status.STOPPED); + mPlayer.setOnStoppedListener(this::notifyStop); + mAudioRecorder.setOnRecordingInitializedHandler(() -> mStatus = Status.INITIALIZED); mContext = context; @@ -235,15 +239,7 @@ public void onClick(View v) { switch (mStatus) { case IDLE: try { - mPlayer = new MediaPlayer(); - mPlayer.setDataSource(getAudioPath()); - mPlayer.setOnCompletionListener(mp -> { - mStatus = Status.STOPPED; - mPlayer.stop(); - notifyStop(); - }); - mPlayer.prepare(); - mPlayer.start(); + mPlayer.play(getAudioPath()); setImageResource(mResPauseImage); mStatus = Status.PLAYING; @@ -267,12 +263,7 @@ public void onClick(View v) { // -> Play, start from beginning mStatus = Status.PLAYING; setImageResource(mResPauseImage); - try { - mPlayer.prepare(); - mPlayer.seekTo(0); - } catch (Exception e) { - Timber.e(e); - } + mPlayer.stop(); mPlayer.start(); notifyPlay(); break; From 81aee9505593db591235e8744a3967c90cf89f98 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 7 Apr 2021 19:40:59 +0100 Subject: [PATCH 077/171] infra: ignored tests: Audio Recording Shortcuts This sets up the tests and infrastructure, to simplify the diff of the fixes Related: #7780 --- .../main/java/com/ichi2/anki/Reviewer.java | 9 +- .../ichi2/anki/multimediacard/AudioView.java | 22 +- .../KeyboardShortcutIntegrationTest.java | 231 ++++++++++++++++++ 3 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index 7b58183687d0..8b4e61fb1455 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -63,6 +63,8 @@ import com.ichi2.anki.cardviewer.CardAppearance; import com.ichi2.anki.dialogs.ConfirmationDialog; +import com.ichi2.anki.multimediacard.AudioPlayer; +import com.ichi2.anki.multimediacard.AudioRecorder; import com.ichi2.anki.multimediacard.AudioView; import com.ichi2.anki.dialogs.RescheduleDialog; import com.ichi2.anki.reviewer.PeripheralKeymap; @@ -466,7 +468,8 @@ protected void recordVoice() { * @return Whether the mic toolbar is usable */ @SuppressWarnings("BooleanMethodIsAlwaysInverted") - private boolean openMicToolbar() { + @VisibleForTesting + public boolean openMicToolbar() { if (mMicToolBar == null || mMicToolBar.getVisibility() != View.VISIBLE) { openOrToggleMicToolbar(); } @@ -1273,6 +1276,10 @@ Whiteboard getWhiteboard() { return mWhiteboard; } + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public AudioView getAudioView() { + return mMicToolBar; + } /** * Inner class which implements the submenu for the Suspend button diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java index f2c07a809c89..213c0507c591 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java @@ -49,8 +49,8 @@ public class AudioView extends LinearLayout { protected StopButton mStop = null; protected RecordButton mRecord = null; - private final AudioRecorder mAudioRecorder = new AudioRecorder(); - private final AudioPlayer mPlayer = new AudioPlayer(); + private AudioRecorder mAudioRecorder = new AudioRecorder(); + private AudioPlayer mPlayer = new AudioPlayer(); private OnRecordingFinishEventListener mOnRecordingFinishEventListener = null; @@ -422,4 +422,22 @@ public void update() { public interface OnRecordingFinishEventListener { void onRecordingFinish(View v); } + + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public void setRecorder(@NonNull AudioRecorder recorder) { + this.mAudioRecorder = recorder; + } + + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public void setPlayer(@NonNull AudioPlayer player) { + this.mPlayer = player; + } + + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public Status getStatus() { + return mStatus; + } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java new file mode 100644 index 000000000000..c1f92c6f09a5 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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.reviewer; + +import android.Manifest; +import android.app.Application; +import android.content.Intent; +import android.view.KeyEvent; + +import com.ichi2.anki.Reviewer; +import com.ichi2.anki.RobolectricTest; +import com.ichi2.anki.multimediacard.AudioPlayer; +import com.ichi2.anki.multimediacard.AudioRecorder; +import com.ichi2.anki.multimediacard.AudioView; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Shadows; + +import java.io.IOException; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(AndroidJUnit4.class) +public class KeyboardShortcutIntegrationTest extends RobolectricTest { + + private Reviewer reviewer; + + + @Before + @Override + public void setUp() { + super.setUp(); + addNoteUsingBasicModel("Hello", "World"); + Shadows.shadowOf((Application) ApplicationProvider.getApplicationContext()).grantPermissions(Manifest.permission.RECORD_AUDIO); + reviewer = super.startActivityNormallyOpenCollectionWithIntent(Reviewer.class, new Intent()); + waitForAsyncTasksToComplete(); + } + + + /** + * Press "Shift + V" + * Release "Shift", then release "V" + * + * Expected: Recording is triggered + * #7780 + */ + @Test + @Ignore("7780") + public void testActionsOccurOnKeyDown() throws IOException { + AudioRecorder recorder = setupRecorderMock(); + + pressShiftAndVThenRelease(); + + verify(recorder, times(1)).startRecording(anyString()); + verifyNoMoreInteractions(recorder); + } + + /** + * Press "Shift + V" + * While recording press "V" + * + * Expected: Current state is stopped and the desired state is triggered + * #7780 + */ + @Test + @Ignore("7780") + public void playingWhileRecordingStopsRecordingAndStartsPlaying() throws IOException { + AudioPlayer player = setupPlayerMock(); + AudioRecorder recorder = setupRecorderMock(); + + pressShiftAndVThenRelease(); + + verify(recorder, times(1)).startRecording(anyString()); + verifyNoMoreInteractions(recorder, player); + assertStatus(AudioView.Status.RECORDING); + + // now we're set up and recording, pressing V should move us to playing + + pressVThenRelease(); + + verify(recorder, times(1)).stopRecording(); + verify(player, times(1)).play(anyString()); + verifyNoMoreInteractions(recorder, player); + assertStatus(AudioView.Status.PLAYING); + } + + + /** + * Press " V" + * While playing press "Shift + V" + * + * Expected: Current state is stopped and the desired state is triggered + * #7780 + */ + @Test + @Ignore("7780") + public void recordingWhilePlayingStopsPlayingAndStartsRecording() throws IOException { + Shadows.shadowOf((Application) ApplicationProvider.getApplicationContext()).grantPermissions(Manifest.permission.RECORD_AUDIO); + + AudioPlayer player = setupPlayerMock(); + AudioRecorder recorder = setupRecorderMock(); + + // We don't need anything recorded as the mock doesn't mind + pressVThenRelease(); + + verify(player, times(1)).play(anyString()); + verifyNoMoreInteractions(recorder, player); + assertStatus(AudioView.Status.PLAYING); + + // now we're set up and recording, pressing Shift + V should move us to recording + pressShiftAndVThenRelease(); + + verify(player, times(1)).stop(); + verify(recorder, times(1)).startRecording(anyString()); + verifyNoMoreInteractions(recorder, player); + assertStatus(AudioView.Status.RECORDING); + + } + + + protected void assertStatus(AudioView.Status recording) { + assertThat(reviewer.getAudioView().getStatus(), is(recording)); + } + + + protected AudioPlayer setupPlayerMock() { + assertThat(reviewer.openMicToolbar(), is(true)); + AudioPlayer player = mock(AudioPlayer.class); + reviewer.getAudioView().setPlayer(player); + return player; + } + + + protected AudioRecorder setupRecorderMock() { + assertThat(reviewer.openMicToolbar(), is(true)); + AudioRecorder recorder = mock(AudioRecorder.class); + reviewer.getAudioView().setRecorder(recorder); + return recorder; + } + + + private void pressVThenRelease() { + depressVKey(); + releaseVKey(); + } + + + protected void pressShiftAndVThenRelease() { + depressShiftKey(); + depressVKeyWithShiftHeld(); + releaseShiftKey(); + releaseVKey(); + } + + + private void releaseVKey() { + KeyEvent mock = mock(KeyEvent.class); + when(mock.getKeyCode()).thenReturn(KeyEvent.KEYCODE_V); + when(mock.getUnicodeChar()).thenReturn((int) 'v'); + when(mock.getUnicodeChar(anyInt())).thenReturn((int) 'v'); + reviewer.onKeyUp(KeyEvent.KEYCODE_V, mock); + } + + + private void releaseShiftKey() { + KeyEvent mock = mock(KeyEvent.class); + when(mock.getKeyCode()).thenReturn(KeyEvent.KEYCODE_SHIFT_LEFT); + reviewer.onKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT, mock); + } + + private void depressVKey() { + KeyEvent mock = mock(KeyEvent.class); + when(mock.getKeyCode()).thenReturn(KeyEvent.KEYCODE_V); + when(mock.getUnicodeChar()).thenReturn((int) 'v'); + when(mock.getUnicodeChar(anyInt())).thenReturn((int) 'v'); + reviewer.onKeyDown(KeyEvent.KEYCODE_V, mock); + } + + private void depressVKeyWithShiftHeld() { + KeyEvent mock = mock(KeyEvent.class); + when(mock.isShiftPressed()).thenReturn(true); + when(mock.getKeyCode()).thenReturn(KeyEvent.KEYCODE_V); + reviewer.onKeyDown(KeyEvent.KEYCODE_V, mock); + } + + + private void depressShiftKey() { + + KeyEvent mock = mock(KeyEvent.class); + when(mock.getAction()).thenReturn(0); + when(mock.getDeviceId()).thenReturn(9); + when(mock.getDownTime()).thenReturn(35660208L); + when(mock.getEventTime()).thenReturn(35660208L); + when(mock.getFlags()).thenReturn(8); + when(mock.getKeyCode()).thenReturn(KeyEvent.KEYCODE_SHIFT_LEFT); + when(mock.getMetaState()).thenReturn(65); + when(mock.getScanCode()).thenReturn(42); + when(mock.getSource()).thenReturn(257); + when(mock.getRepeatCount()).thenReturn(0); + + reviewer.onKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT, mock); + } +} From e439280bf3382a4af4146316f2ada063cce0f8ca Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 7 Apr 2021 22:28:17 +0100 Subject: [PATCH 078/171] Fix test: testActionsOccurOnKeyDown Shift + V for voice recording wasn't working too well as we were handling keypresses on key up: If we released shift before V, this triggered "V" - play, rather than "Shift + V" - record So, handle keypresses on key down This may require some thought in Reviewer.java on other actions happening in onKeyUp Fixes part of #7780 --- .../com/ichi2/anki/reviewer/PeripheralKeymapTest.java | 1 + .../java/com/ichi2/anki/reviewer/PeripheralKeymap.java | 10 +++++----- .../anki/reviewer/KeyboardShortcutIntegrationTest.java | 1 - .../com/ichi2/anki/reviewer/PeripheralKeymapTest.java | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java b/AnkiDroid/src/androidTest/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java index 1cfee6f9dd99..99f0587c9567 100644 --- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java @@ -45,6 +45,7 @@ public void testNumpadAction() { PeripheralKeymap peripheralKeymap = new PeripheralKeymap(MockReviewerUi.displayingAnswer(), processed::add); peripheralKeymap.setup(); + peripheralKeymap.onKeyDown(KeyEvent.KEYCODE_NUMPAD_1, getNumpadEvent(KeyEvent.KEYCODE_NUMPAD_1)); peripheralKeymap.onKeyUp(KeyEvent.KEYCODE_NUMPAD_1, getNumpadEvent(KeyEvent.KEYCODE_NUMPAD_1)); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralKeymap.java b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralKeymap.java index 5b517ce42a91..d75d783bae2b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralKeymap.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralKeymap.java @@ -57,12 +57,7 @@ public void setup() { } - @SuppressWarnings( {"unused", "RedundantSuppression"}) public boolean onKeyDown(int keyCode, KeyEvent event) { - return false; - } - - public boolean onKeyUp(int keyCode, KeyEvent event) { if (!mHasSetup) { return false; } @@ -73,6 +68,11 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { } } + @SuppressWarnings( {"unused", "RedundantSuppression"}) + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; + } + private static class KeyMap { public final HashMap> mKeyCodeToCommand = new HashMap<>(); public final HashMap> mUnicodeToCommand = new HashMap<>(); diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java index c1f92c6f09a5..622955aea2dd 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java @@ -73,7 +73,6 @@ public void setUp() { * #7780 */ @Test - @Ignore("7780") public void testActionsOccurOnKeyDown() throws IOException { AudioRecorder recorder = setupRecorderMock(); diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java index 63fd5ed0d6b3..bf2373f2158c 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/PeripheralKeymapTest.java @@ -45,7 +45,7 @@ public void flagAndAnswerDoNotConflict() { assertThat((char) event.getUnicodeChar(), is('\0')); assertThat((char) event.getUnicodeChar(0), is('1')); - peripheralKeymap.onKeyUp(8, event); + peripheralKeymap.onKeyDown(8, event); assertThat(processed, hasSize(1)); assertThat(processed.get(0), is(ViewerCommand.COMMAND_TOGGLE_FLAG_RED)); From 169afa07bad9473073f6c6fcbc804fc3ab5156f1 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 7 Apr 2021 23:47:27 +0100 Subject: [PATCH 079/171] Audio: Cancel current action if shortcut pressed When pressing "Play", recording finishes and playing occurs When pressing "Record", playing stops and recording occurs Fixes #7780 --- .../ichi2/anki/multimediacard/AudioView.java | 23 +++++++++++++++++++ .../KeyboardShortcutIntegrationTest.java | 3 --- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java index 213c0507c591..d5a9cf458f36 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java @@ -216,18 +216,41 @@ public void notifyReleaseRecorder() { mAudioRecorder.release(); } + /** Stops playing and records */ public void toggleRecord() { + stopPlaying(); + if (mRecord != null) { mRecord.callOnClick(); } } + + /** Stops recording and presses the play button */ public void togglePlay() { + // cancelling recording is done via pressing the record button again + stopRecording(); + if (mPlayPause != null) { mPlayPause.callOnClick(); } } + /** If recording is occurring, stop it */ + private void stopRecording() { + if (mStatus == Status.RECORDING && mRecord != null) { + mRecord.callOnClick(); + } + } + + /** If playing, stop it */ + protected void stopPlaying() { + // The stop button only applies to the player, and does nothing if no action is needed + if (mStop != null) { + mStop.callOnClick(); + } + } + protected class PlayPauseButton extends AppCompatImageButton { private final OnClickListener mOnClickListener = new View.OnClickListener() { @Override diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java index 622955aea2dd..4311950bd9a8 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/KeyboardShortcutIntegrationTest.java @@ -28,7 +28,6 @@ import com.ichi2.anki.multimediacard.AudioView; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Shadows; @@ -90,7 +89,6 @@ public void testActionsOccurOnKeyDown() throws IOException { * #7780 */ @Test - @Ignore("7780") public void playingWhileRecordingStopsRecordingAndStartsPlaying() throws IOException { AudioPlayer player = setupPlayerMock(); AudioRecorder recorder = setupRecorderMock(); @@ -120,7 +118,6 @@ public void playingWhileRecordingStopsRecordingAndStartsPlaying() throws IOExcep * #7780 */ @Test - @Ignore("7780") public void recordingWhilePlayingStopsPlayingAndStartsRecording() throws IOException { Shadows.shadowOf((Application) ApplicationProvider.getApplicationContext()).grantPermissions(Manifest.permission.RECORD_AUDIO); From a88a8ed44cce22ddefb0dbbd61b6c2b2037f6a07 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 8 Apr 2021 00:01:22 +0100 Subject: [PATCH 080/171] lint: AudioView - fix unnecessary null --- .../main/java/com/ichi2/anki/multimediacard/AudioView.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java index d5a9cf458f36..2d04f7607481 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java @@ -45,8 +45,8 @@ public class AudioView extends LinearLayout { protected final String mAudioPath; - protected PlayPauseButton mPlayPause = null; - protected StopButton mStop = null; + protected PlayPauseButton mPlayPause; + protected StopButton mStop; protected RecordButton mRecord = null; private AudioRecorder mAudioRecorder = new AudioRecorder(); From 95b310de511f67cd0af0504a64696ce59590fe73 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 8 Apr 2021 00:03:57 +0100 Subject: [PATCH 081/171] lint: AudioView - fix copyright --- .../ichi2/anki/multimediacard/AudioView.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java index 2d04f7607481..8dd215b8f387 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/AudioView.java @@ -1,21 +1,21 @@ -/**************************************************************************************** - * Copyright (c) 2013 Bibek Shrestha * - * Copyright (c) 2013 Zaur Molotnikov * - * Copyright (c) 2013 Nicolas Raoul * - * Copyright (c) 2013 Flavio Lerda * - * * - * 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 . * - ****************************************************************************************/ +/* + * Copyright (c) 2013 Bibek Shrestha + * Copyright (c) 2013 Zaur Molotnikov + * Copyright (c) 2013 Nicolas Raoul + * Copyright (c) 2013 Flavio Lerda + * + * 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.multimediacard; From 64c23e09dd9a673c3cd7cb04ec9756c563ffa609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=E1=BA=B7ng=20=C4=90o=C3=A0n=20=C4=90=E1=BB=A9c=20Tr?= =?UTF-8?q?ung?= Date: Thu, 8 Apr 2021 00:31:00 -0400 Subject: [PATCH 082/171] Add unit test for TextCardExporter (#8498) * Add unit test for TextCardExporter This was from Exporting.java so we would need to fix the original file too if we are going to use it Co-authored-by: Tarekk Mohamed Abdalla --- .../ichi2/libanki/TextCardExporterTest.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/libanki/TextCardExporterTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/TextCardExporterTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/TextCardExporterTest.java new file mode 100644 index 000000000000..ead18215d045 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/TextCardExporterTest.java @@ -0,0 +1,90 @@ +/* + Copyright (c) 2021 Trung Dang + 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.libanki; + +import com.ichi2.anki.RobolectricTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) +public class TextCardExporterTest extends RobolectricTest { + private Collection col; + private List noteList = new ArrayList(); + + + @Before + public void setUp() { + super.setUp(); + col = getCol(); + Note note = col.newNote(); + note.setItem("Front", "foo"); + note.setItem("Back", "bar
"); + note.setTagsFromStr("tag, tag2"); + col.addNote(note); + noteList.add(note); + // with a different note + note = col.newNote(); + note.setItem("Front", "baz"); + note.setItem("Back", "qux"); + note.model().put("did", addDeck("new col")); + col.addNote(note); + noteList.add(note); + } + + + @Test + public void testExportTextCardWithHTML() throws IOException { + Path tempExportDir = Files.createTempDirectory("AnkiDroid-test_export_textcard"); + File exportedFile = new File(tempExportDir.toFile(), "export.txt"); + + TextCardExporter exporter = new TextCardExporter(col, true); + exporter.doExport(exportedFile.getAbsolutePath()); + String content = new String(Files.readAllBytes(Paths.get(exportedFile.getAbsolutePath()))); + String expected = ""; + // Alternatively we can choose to strip styling from content, instead of adding styling to expected + expected += String.format(Locale.US, "", noteList.get(0).model().getString("css")); + expected += "foo\tbar
\n"; + expected += String.format(Locale.US, "", noteList.get(1).model().getString("css")); + expected += "baz\tqux\n"; + assertEquals(expected, content); + } + + + @Test + public void testExportTextCardWithoutHTML() throws IOException { + Path tempExportDir = Files.createTempDirectory("AnkiDroid-test_export_textcard"); + File exportedFile = new File(tempExportDir.toFile(), "export.txt"); + + TextCardExporter exporter = new TextCardExporter(col, false); + exporter.doExport(exportedFile.getAbsolutePath()); + String content = new String(Files.readAllBytes(Paths.get(exportedFile.getAbsolutePath()))); + String expected = "foo\tbar\nbaz\tqux\n"; + assertEquals(expected, content); + } +} From 39257490624ab7749db7a2df2a1e5b463979567b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=E1=BA=B7ng=20=C4=90o=C3=A0n=20=C4=90=E1=BB=A9c=20Tr?= =?UTF-8?q?ung?= Date: Thu, 8 Apr 2021 00:42:23 -0400 Subject: [PATCH 083/171] Replace timebox notice with a popup (#8484) * Replace timebox notice with a popup * Changing the animation to END --- .../java/com/ichi2/anki/AbstractFlashcardViewer.java | 12 ++++++++++-- AnkiDroid/src/main/res/values/02-strings.xml | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 5d867e0560e7..35544dc01bd9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -648,8 +648,16 @@ private void dealWithTimeBox() { int nMins = elapsed.first / 60; String mins = res.getQuantityString(R.plurals.in_minutes, nMins, nMins); String timeboxMessage = res.getQuantityString(R.plurals.timebox_reached, nCards, nCards, mins); - UIUtils.showThemedToast(AbstractFlashcardViewer.this, timeboxMessage, true); - getCol().startTimebox(); + new MaterialDialog.Builder(AbstractFlashcardViewer.this) + .title(res.getString(R.string.timebox_reached_title)) + .content(timeboxMessage) + .positiveText(R.string.dialog_continue) + .negativeText(R.string.close) + .cancelable(true) + .onNegative((materialDialog, dialogAction) -> finishWithAnimation(END)) + .onPositive((materialDialog, dialogAction) -> getCol().startTimebox()) + .cancelListener(materialDialog -> getCol().startTimebox()) + .show(); } } diff --git a/AnkiDroid/src/main/res/values/02-strings.xml b/AnkiDroid/src/main/res/values/02-strings.xml index 7e52a650bfdd..43eb2b1e2dd8 100644 --- a/AnkiDroid/src/main/res/values/02-strings.xml +++ b/AnkiDroid/src/main/res/values/02-strings.xml @@ -110,6 +110,7 @@ %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s From a44c210fd921f9d09e3feef42e1bebeccc6b0f77 Mon Sep 17 00:00:00 2001 From: Enno Hermann Date: Wed, 7 Apr 2021 11:57:48 +0200 Subject: [PATCH 084/171] Flip +/- buttons in IncrementerNumberRangePreference --- .../ichi2/preferences/IncrementerNumberRangePreference.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreference.java b/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreference.java index e00342854b0a..6d8b11d278dd 100644 --- a/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreference.java +++ b/AnkiDroid/src/main/java/com/ichi2/preferences/IncrementerNumberRangePreference.java @@ -57,9 +57,9 @@ public IncrementerNumberRangePreference(Context context) { @Override protected View onCreateDialogView() { - mLinearLayout.addView(mIncrementButton); - mLinearLayout.addView(mEditText); mLinearLayout.addView(mDecrementButton); + mLinearLayout.addView(mEditText); + mLinearLayout.addView(mIncrementButton); return mLinearLayout; } From 3195ca29525b7431f661e62b4d2d5dba843dccd8 Mon Sep 17 00:00:00 2001 From: AnkiDroid Translations Date: Thu, 8 Apr 2021 04:47:38 +0000 Subject: [PATCH 085/171] Updated strings from Crowdin --- .../src/main/res/values-af/02-strings.xml | 1 + .../src/main/res/values-af/10-preferences.xml | 2 ++ .../src/main/res/values-am/02-strings.xml | 1 + .../src/main/res/values-am/10-preferences.xml | 2 ++ .../src/main/res/values-ar/02-strings.xml | 1 + .../src/main/res/values-ar/10-preferences.xml | 2 ++ .../src/main/res/values-az/02-strings.xml | 1 + .../src/main/res/values-az/10-preferences.xml | 2 ++ .../src/main/res/values-be/02-strings.xml | 1 + .../src/main/res/values-be/10-preferences.xml | 2 ++ .../src/main/res/values-bg/02-strings.xml | 1 + .../src/main/res/values-bg/10-preferences.xml | 2 ++ .../src/main/res/values-bn/02-strings.xml | 1 + .../src/main/res/values-bn/10-preferences.xml | 2 ++ .../src/main/res/values-ca/02-strings.xml | 1 + .../src/main/res/values-ca/10-preferences.xml | 2 ++ .../src/main/res/values-ckb/02-strings.xml | 1 + .../main/res/values-ckb/10-preferences.xml | 2 ++ .../src/main/res/values-cs/02-strings.xml | 1 + .../src/main/res/values-cs/10-preferences.xml | 2 ++ .../src/main/res/values-da/02-strings.xml | 1 + .../src/main/res/values-da/10-preferences.xml | 2 ++ .../src/main/res/values-de/02-strings.xml | 1 + .../src/main/res/values-de/10-preferences.xml | 2 ++ .../src/main/res/values-el/02-strings.xml | 1 + .../src/main/res/values-el/10-preferences.xml | 2 ++ .../src/main/res/values-eo/02-strings.xml | 1 + .../src/main/res/values-eo/10-preferences.xml | 2 ++ .../src/main/res/values-es-rAR/02-strings.xml | 1 + .../main/res/values-es-rAR/10-preferences.xml | 2 ++ .../src/main/res/values-es-rES/02-strings.xml | 1 + .../main/res/values-es-rES/10-preferences.xml | 2 ++ .../src/main/res/values-et/02-strings.xml | 1 + .../src/main/res/values-et/10-preferences.xml | 2 ++ .../src/main/res/values-eu/02-strings.xml | 1 + .../src/main/res/values-eu/10-preferences.xml | 2 ++ .../src/main/res/values-fa/02-strings.xml | 1 + .../src/main/res/values-fa/10-preferences.xml | 2 ++ .../src/main/res/values-fi/02-strings.xml | 1 + .../src/main/res/values-fi/10-preferences.xml | 2 ++ .../src/main/res/values-fil/02-strings.xml | 1 + .../main/res/values-fil/10-preferences.xml | 2 ++ .../src/main/res/values-fr/02-strings.xml | 1 + .../src/main/res/values-fr/10-preferences.xml | 2 ++ .../src/main/res/values-fy/02-strings.xml | 1 + .../src/main/res/values-fy/10-preferences.xml | 2 ++ .../src/main/res/values-ga/02-strings.xml | 1 + .../src/main/res/values-ga/10-preferences.xml | 2 ++ .../src/main/res/values-gl/02-strings.xml | 1 + .../src/main/res/values-gl/10-preferences.xml | 2 ++ .../src/main/res/values-got/02-strings.xml | 1 + .../main/res/values-got/10-preferences.xml | 2 ++ .../src/main/res/values-gu/02-strings.xml | 1 + .../src/main/res/values-gu/10-preferences.xml | 2 ++ .../src/main/res/values-heb/02-strings.xml | 1 + .../main/res/values-heb/10-preferences.xml | 2 ++ .../src/main/res/values-hi/02-strings.xml | 1 + .../src/main/res/values-hi/07-cardbrowser.xml | 4 ++-- .../src/main/res/values-hi/10-preferences.xml | 2 ++ .../src/main/res/values-hr/02-strings.xml | 1 + .../src/main/res/values-hr/10-preferences.xml | 2 ++ .../src/main/res/values-hu/02-strings.xml | 1 + .../src/main/res/values-hu/10-preferences.xml | 2 ++ .../src/main/res/values-hy/02-strings.xml | 1 + .../src/main/res/values-hy/10-preferences.xml | 2 ++ .../src/main/res/values-ind/02-strings.xml | 1 + .../main/res/values-ind/10-preferences.xml | 2 ++ .../src/main/res/values-is/02-strings.xml | 1 + .../src/main/res/values-is/10-preferences.xml | 2 ++ .../src/main/res/values-it/02-strings.xml | 1 + .../src/main/res/values-it/10-preferences.xml | 2 ++ .../src/main/res/values-ja/02-strings.xml | 1 + .../src/main/res/values-ja/10-preferences.xml | 2 ++ .../src/main/res/values-jv/02-strings.xml | 1 + .../src/main/res/values-jv/10-preferences.xml | 2 ++ .../src/main/res/values-ka/02-strings.xml | 1 + .../src/main/res/values-ka/10-preferences.xml | 2 ++ .../src/main/res/values-kk/02-strings.xml | 1 + .../src/main/res/values-kk/10-preferences.xml | 2 ++ .../src/main/res/values-km/02-strings.xml | 1 + .../src/main/res/values-km/10-preferences.xml | 2 ++ .../src/main/res/values-ko/02-strings.xml | 1 + .../src/main/res/values-ko/10-preferences.xml | 2 ++ .../src/main/res/values-ku/02-strings.xml | 1 + .../src/main/res/values-ku/10-preferences.xml | 2 ++ .../src/main/res/values-ky/02-strings.xml | 1 + .../src/main/res/values-ky/10-preferences.xml | 2 ++ .../src/main/res/values-lt/02-strings.xml | 1 + .../src/main/res/values-lt/10-preferences.xml | 2 ++ .../src/main/res/values-lv/02-strings.xml | 1 + .../src/main/res/values-lv/10-preferences.xml | 2 ++ .../src/main/res/values-mk/02-strings.xml | 1 + .../src/main/res/values-mk/10-preferences.xml | 2 ++ .../src/main/res/values-mn/02-strings.xml | 1 + .../src/main/res/values-mn/10-preferences.xml | 2 ++ .../src/main/res/values-mr/02-strings.xml | 1 + .../src/main/res/values-mr/10-preferences.xml | 2 ++ .../src/main/res/values-ms/02-strings.xml | 1 + .../src/main/res/values-ms/10-preferences.xml | 2 ++ .../src/main/res/values-my/02-strings.xml | 1 + .../src/main/res/values-my/10-preferences.xml | 2 ++ .../src/main/res/values-nl/02-strings.xml | 1 + .../src/main/res/values-nl/10-preferences.xml | 2 ++ .../src/main/res/values-nn/02-strings.xml | 1 + .../src/main/res/values-nn/10-preferences.xml | 2 ++ .../src/main/res/values-no/02-strings.xml | 1 + .../src/main/res/values-no/10-preferences.xml | 2 ++ .../src/main/res/values-or/02-strings.xml | 3 ++- .../src/main/res/values-or/10-preferences.xml | 6 +++-- .../src/main/res/values-pa/02-strings.xml | 1 + .../src/main/res/values-pa/10-preferences.xml | 2 ++ .../src/main/res/values-pl/02-strings.xml | 1 + .../src/main/res/values-pl/10-preferences.xml | 2 ++ .../src/main/res/values-pt-rBR/02-strings.xml | 1 + .../main/res/values-pt-rBR/10-preferences.xml | 2 ++ .../src/main/res/values-pt-rPT/02-strings.xml | 1 + .../main/res/values-pt-rPT/10-preferences.xml | 2 ++ .../src/main/res/values-ro/02-strings.xml | 1 + .../src/main/res/values-ro/10-preferences.xml | 2 ++ .../src/main/res/values-ru/02-strings.xml | 1 + .../src/main/res/values-ru/10-preferences.xml | 2 ++ .../src/main/res/values-sat/02-strings.xml | 1 + .../main/res/values-sat/10-preferences.xml | 2 ++ .../src/main/res/values-sk/02-strings.xml | 1 + .../src/main/res/values-sk/10-preferences.xml | 2 ++ .../src/main/res/values-sl/02-strings.xml | 1 + .../src/main/res/values-sl/10-preferences.xml | 2 ++ .../src/main/res/values-sq/02-strings.xml | 1 + .../src/main/res/values-sq/10-preferences.xml | 2 ++ .../src/main/res/values-sr/02-strings.xml | 1 + .../src/main/res/values-sr/10-preferences.xml | 2 ++ .../src/main/res/values-ss/02-strings.xml | 1 + .../src/main/res/values-ss/10-preferences.xml | 2 ++ .../src/main/res/values-sv/02-strings.xml | 1 + .../src/main/res/values-sv/10-preferences.xml | 2 ++ .../src/main/res/values-sw/02-strings.xml | 1 + .../src/main/res/values-sw/10-preferences.xml | 2 ++ .../src/main/res/values-ta/02-strings.xml | 1 + .../src/main/res/values-ta/10-preferences.xml | 2 ++ .../src/main/res/values-te/02-strings.xml | 1 + .../src/main/res/values-te/10-preferences.xml | 2 ++ .../src/main/res/values-tg/02-strings.xml | 1 + .../src/main/res/values-tg/10-preferences.xml | 2 ++ .../src/main/res/values-tgl/02-strings.xml | 1 + .../main/res/values-tgl/10-preferences.xml | 2 ++ .../src/main/res/values-th/02-strings.xml | 1 + .../src/main/res/values-th/10-preferences.xml | 2 ++ .../src/main/res/values-ti/02-strings.xml | 1 + .../src/main/res/values-ti/10-preferences.xml | 2 ++ .../src/main/res/values-tn/02-strings.xml | 1 + .../src/main/res/values-tn/10-preferences.xml | 2 ++ .../src/main/res/values-tr/02-strings.xml | 1 + .../src/main/res/values-tr/10-preferences.xml | 2 ++ .../src/main/res/values-ts/02-strings.xml | 1 + .../src/main/res/values-ts/10-preferences.xml | 2 ++ .../src/main/res/values-tt/02-strings.xml | 9 +++---- .../src/main/res/values-tt/03-dialogs.xml | 4 ++-- .../src/main/res/values-tt/04-network.xml | 2 +- .../src/main/res/values-tt/06-statistics.xml | 12 +++++----- .../src/main/res/values-tt/10-preferences.xml | 24 ++++++++++--------- .../res/values-tt/16-multimedia-editor.xml | 2 +- .../main/res/values-tt/18-standard-models.xml | 2 +- .../src/main/res/values-uk/02-strings.xml | 1 + .../src/main/res/values-uk/10-preferences.xml | 2 ++ .../src/main/res/values-ur/02-strings.xml | 1 + .../src/main/res/values-ur/10-preferences.xml | 2 ++ .../src/main/res/values-uz/02-strings.xml | 1 + .../src/main/res/values-uz/10-preferences.xml | 2 ++ .../src/main/res/values-ve/02-strings.xml | 1 + .../src/main/res/values-ve/10-preferences.xml | 2 ++ .../src/main/res/values-vi/02-strings.xml | 1 + .../src/main/res/values-vi/10-preferences.xml | 2 ++ .../src/main/res/values-wo/02-strings.xml | 1 + .../src/main/res/values-wo/10-preferences.xml | 2 ++ .../src/main/res/values-xh/02-strings.xml | 1 + .../src/main/res/values-xh/10-preferences.xml | 2 ++ .../src/main/res/values-yue/02-strings.xml | 1 + .../main/res/values-yue/10-preferences.xml | 2 ++ .../src/main/res/values-zh-rCN/02-strings.xml | 3 ++- .../main/res/values-zh-rCN/10-preferences.xml | 6 +++-- .../src/main/res/values-zh-rTW/02-strings.xml | 1 + .../main/res/values-zh-rTW/10-preferences.xml | 2 ++ .../src/main/res/values-zu/02-strings.xml | 1 + .../src/main/res/values-zu/10-preferences.xml | 2 ++ 184 files changed, 301 insertions(+), 34 deletions(-) diff --git a/AnkiDroid/src/main/res/values-af/02-strings.xml b/AnkiDroid/src/main/res/values-af/02-strings.xml index 66353c0179b6..0a6331dd1daf 100644 --- a/AnkiDroid/src/main/res/values-af/02-strings.xml +++ b/AnkiDroid/src/main/res/values-af/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (van \"%2$s\") Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d kaart gestudeer in %2$s %1$d kaarte gestudeer in %2$s diff --git a/AnkiDroid/src/main/res/values-af/10-preferences.xml b/AnkiDroid/src/main/res/values-af/10-preferences.xml index 7e8333a3fd4d..81b6ae19dd0d 100644 --- a/AnkiDroid/src/main/res/values-af/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-af/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-am/02-strings.xml b/AnkiDroid/src/main/res/values-am/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-am/02-strings.xml +++ b/AnkiDroid/src/main/res/values-am/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-am/10-preferences.xml b/AnkiDroid/src/main/res/values-am/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-am/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-am/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ar/02-strings.xml b/AnkiDroid/src/main/res/values-ar/02-strings.xml index 0653eda62a5d..8ffacfdbec44 100644 --- a/AnkiDroid/src/main/res/values-ar/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ar/02-strings.xml @@ -122,6 +122,7 @@ إعادة ترتيب البطاقات… %1$s (من “%2$s”) البطاقات غير المُوزعة ستُحذف. إذا لم يتبقَّ لملحوظة أي بطاقة، ستُحذف. هل أنت متأكد من الاستمرار؟ + Timebox reached درست %1$d بطاقة خلال %2$s درست %1$d بطاقة خلال %2$s diff --git a/AnkiDroid/src/main/res/values-ar/10-preferences.xml b/AnkiDroid/src/main/res/values-ar/10-preferences.xml index 9d81b2033754..045b7d2f988d 100644 --- a/AnkiDroid/src/main/res/values-ar/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ar/10-preferences.xml @@ -299,6 +299,8 @@ في محرر الملحوظات، حوّل أي ظهور لـ <br> إلى سطور جديدة عند تحرير بطاقة. تنسيق الإجابة الكتابية البسيطة يُصلِح وجود ‘􏿾’ في نتائِج الأجابة المكتوبة + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer فتح سجل التغييرات diff --git a/AnkiDroid/src/main/res/values-az/02-strings.xml b/AnkiDroid/src/main/res/values-az/02-strings.xml index 383601013123..c90199dedc6c 100644 --- a/AnkiDroid/src/main/res/values-az/02-strings.xml +++ b/AnkiDroid/src/main/res/values-az/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-az/10-preferences.xml b/AnkiDroid/src/main/res/values-az/10-preferences.xml index b06135a2a15c..36348c009612 100644 --- a/AnkiDroid/src/main/res/values-az/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-az/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-be/02-strings.xml b/AnkiDroid/src/main/res/values-be/02-strings.xml index d9a3ad62789a..414df65d0d3f 100644 --- a/AnkiDroid/src/main/res/values-be/02-strings.xml +++ b/AnkiDroid/src/main/res/values-be/02-strings.xml @@ -122,6 +122,7 @@ Перамешванне картак… %1$s (з “%2$s”) Не супастаўленыя карткі будуць выдаленыя. Калі нататка не мае ніводнай карткі, то яна будзе згубленая. Працягнуць? + Timebox reached %1$d картка вывучана за %2$s %1$d карткі вывучана за %2$s diff --git a/AnkiDroid/src/main/res/values-be/10-preferences.xml b/AnkiDroid/src/main/res/values-be/10-preferences.xml index 014956a27fe6..14e1fa286524 100644 --- a/AnkiDroid/src/main/res/values-be/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-be/10-preferences.xml @@ -298,6 +298,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-bg/02-strings.xml b/AnkiDroid/src/main/res/values-bg/02-strings.xml index 1e3f6f3aa986..2417d46b9297 100644 --- a/AnkiDroid/src/main/res/values-bg/02-strings.xml +++ b/AnkiDroid/src/main/res/values-bg/02-strings.xml @@ -122,6 +122,7 @@ Пренареждане на карти… %1$s (от \"%2$s\") Несъпоставените карти ще бъдат отстранени. Бележки, останали без карти ще бъдат загубени. Продължавате ли? + Timebox reached %1$d карта изучена за %2$s %1$d карти изучени за %2$s diff --git a/AnkiDroid/src/main/res/values-bg/10-preferences.xml b/AnkiDroid/src/main/res/values-bg/10-preferences.xml index 05c37370fcaf..75b426f9ea3c 100644 --- a/AnkiDroid/src/main/res/values-bg/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-bg/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-bn/02-strings.xml b/AnkiDroid/src/main/res/values-bn/02-strings.xml index 422a09473ff8..e50f29408b5c 100644 --- a/AnkiDroid/src/main/res/values-bn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-bn/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-bn/10-preferences.xml b/AnkiDroid/src/main/res/values-bn/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-bn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-bn/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ca/02-strings.xml b/AnkiDroid/src/main/res/values-ca/02-strings.xml index 94867941a446..0e22fc413c7c 100644 --- a/AnkiDroid/src/main/res/values-ca/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ca/02-strings.xml @@ -122,6 +122,7 @@ Reordenant cartes… %1$s (de \"%2$s\") S\'esborrarà qualsevol carta assignada a res. Si una anotació té cap carta restant, serà perduda. Esteu segur que voleu continuar? + Timebox reached %1$d carta estudiada en %2$s %1$d cartes estudiades en %2$s diff --git a/AnkiDroid/src/main/res/values-ca/10-preferences.xml b/AnkiDroid/src/main/res/values-ca/10-preferences.xml index 35e4987b107b..f1564b0d37f3 100644 --- a/AnkiDroid/src/main/res/values-ca/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ca/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ckb/02-strings.xml b/AnkiDroid/src/main/res/values-ckb/02-strings.xml index df5a6436313b..adf902cd7d7f 100644 --- a/AnkiDroid/src/main/res/values-ckb/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ckb/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d کارتت خوێندووە لە %2$s دا %1$d کارتت خوێندووە لە %2$s دا diff --git a/AnkiDroid/src/main/res/values-ckb/10-preferences.xml b/AnkiDroid/src/main/res/values-ckb/10-preferences.xml index 0c650f4631d5..7d526057ca33 100644 --- a/AnkiDroid/src/main/res/values-ckb/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ckb/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-cs/02-strings.xml b/AnkiDroid/src/main/res/values-cs/02-strings.xml index 4a19d2b38964..00e8caa7f515 100644 --- a/AnkiDroid/src/main/res/values-cs/02-strings.xml +++ b/AnkiDroid/src/main/res/values-cs/02-strings.xml @@ -122,6 +122,7 @@ Mění se pořadí karet… %1$s (z \'%2$s\') Karty mapované na prázdný cíl budou odstraněny. Nezbývají-li už v poznámce žádné karty, bude poznámka ztracena. Opravdu chcete pokračovat? + Timebox reached %1$d karta studována za %2$s %1$d karty studovány za %2$s diff --git a/AnkiDroid/src/main/res/values-cs/10-preferences.xml b/AnkiDroid/src/main/res/values-cs/10-preferences.xml index 3ae12ddc36e0..ccf19637ba61 100644 --- a/AnkiDroid/src/main/res/values-cs/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-cs/10-preferences.xml @@ -295,6 +295,8 @@ V editoru poznámek převede při úpravě karty každý výskyt <br> na nový řádek. Jednoduché formátování napsané odpovědi Opraví „􏿾“, které se vyskytuje ve výsledcích napsaných odpovědí + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Otevřít seznam změn diff --git a/AnkiDroid/src/main/res/values-da/02-strings.xml b/AnkiDroid/src/main/res/values-da/02-strings.xml index 3013a4b20169..c4f6bd824139 100644 --- a/AnkiDroid/src/main/res/values-da/02-strings.xml +++ b/AnkiDroid/src/main/res/values-da/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-da/10-preferences.xml b/AnkiDroid/src/main/res/values-da/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-da/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-da/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-de/02-strings.xml b/AnkiDroid/src/main/res/values-de/02-strings.xml index 7ae8b55ef20f..0c18e1d9ae0f 100644 --- a/AnkiDroid/src/main/res/values-de/02-strings.xml +++ b/AnkiDroid/src/main/res/values-de/02-strings.xml @@ -122,6 +122,7 @@ Sortiere Karten… %1$s (von »%2$s«) Alle leeren Karten werden gelöscht. Sind sämtliche Karten einer Notiz gelöscht, wird diese ebenfalls entfernt. Fortfahren? + Timebox reached %1$d Karte gelernt in %2$s %1$d Karten gelernt in %2$s diff --git a/AnkiDroid/src/main/res/values-de/10-preferences.xml b/AnkiDroid/src/main/res/values-de/10-preferences.xml index 3ecf07dd097e..3b96826b1e62 100644 --- a/AnkiDroid/src/main/res/values-de/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-de/10-preferences.xml @@ -291,6 +291,8 @@ Beim Bearbeiten einer Karte im Notizeditor alle vorkommenden »<br>« in Zeilenbrüche umwandeln. Einfache Formatierung von Antworten Behebt das Erscheinen von »􏿾« in eingetippten Antworten + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Änderungsprotokoll öffnen diff --git a/AnkiDroid/src/main/res/values-el/02-strings.xml b/AnkiDroid/src/main/res/values-el/02-strings.xml index 3f2ae5df7a55..8c602771aa1e 100644 --- a/AnkiDroid/src/main/res/values-el/02-strings.xml +++ b/AnkiDroid/src/main/res/values-el/02-strings.xml @@ -122,6 +122,7 @@ Αναδιάταξη καρτών… %1$s (από “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-el/10-preferences.xml b/AnkiDroid/src/main/res/values-el/10-preferences.xml index 20810aad1b0e..130339e19292 100644 --- a/AnkiDroid/src/main/res/values-el/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-el/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-eo/02-strings.xml b/AnkiDroid/src/main/res/values-eo/02-strings.xml index 4afa6bd682f9..b09018eda9eb 100644 --- a/AnkiDroid/src/main/res/values-eo/02-strings.xml +++ b/AnkiDroid/src/main/res/values-eo/02-strings.xml @@ -122,6 +122,7 @@ Reordigado de kartoj… %1$s (de “%2$s”) Ĉiuj senasignitaj kartoj estos forigitaj. Se noto ne havas restajn kartojn, ĝi ankaŭ perdiĝos. Ĉu vi certe volas pluigi? + Timebox reached Lernis %1$d karton dum %2$s Lernis %1$d kartojn dum %2$s diff --git a/AnkiDroid/src/main/res/values-eo/10-preferences.xml b/AnkiDroid/src/main/res/values-eo/10-preferences.xml index 64b8388b22a2..ac2cd193033d 100644 --- a/AnkiDroid/src/main/res/values-eo/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-eo/10-preferences.xml @@ -291,6 +291,8 @@ En la kart‑redaktilo, konverti ĉiujn <br> al novaj linioj dum redakti karton. Simpla aranĝo de enigita respondo Ripari “􏿾” aperantan en entajpita respondo + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Montri ŝanĝoprotokolon 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 507103fc7c5a..a467fcb57d7b 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/02-strings.xml @@ -122,6 +122,7 @@ Reordenando tarjetas... %1$s (de \'%2$s\') Se eliminará cualquier tarjeta sin asignación. Si una nota no posee tarjetas restantes se perderá. ¿Está seguro de que desea continuar? + Timebox reached %1$d tarjeta estudiada en %2$s %1$d tarjetas estudiadas en %2$s 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 1a18e3b99f4e..d87d6c9e9387 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog 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 a56b1780ded7..e4da9b5d708d 100644 --- a/AnkiDroid/src/main/res/values-es-rES/02-strings.xml +++ b/AnkiDroid/src/main/res/values-es-rES/02-strings.xml @@ -122,6 +122,7 @@ Reorganizando tarjetas… %1$s (de \"%2$s\") Las tarjetas asignadas a nada se eliminarán. Si una nota no tiene cartas restantes, se perderán. ¿Seguro que deseas continuar? + Timebox reached %1$d tarjeta estudiada en %2$s %1$d tarjetas estudiadas en %2$s 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 87a70ee4bbe2..388c7e8024f7 100644 --- a/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-es-rES/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-et/02-strings.xml b/AnkiDroid/src/main/res/values-et/02-strings.xml index 570101baea22..accb54e5cd3c 100644 --- a/AnkiDroid/src/main/res/values-et/02-strings.xml +++ b/AnkiDroid/src/main/res/values-et/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-et/10-preferences.xml b/AnkiDroid/src/main/res/values-et/10-preferences.xml index f75091b15682..e24ffc123c06 100644 --- a/AnkiDroid/src/main/res/values-et/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-et/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-eu/02-strings.xml b/AnkiDroid/src/main/res/values-eu/02-strings.xml index f2aa7f880851..47d05f048f94 100644 --- a/AnkiDroid/src/main/res/values-eu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-eu/02-strings.xml @@ -122,6 +122,7 @@ Txartelak berrordenatzen… %1$s (“%2$s”(e)tik) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-eu/10-preferences.xml b/AnkiDroid/src/main/res/values-eu/10-preferences.xml index 5211099bd38b..f4f6f1d2edef 100644 --- a/AnkiDroid/src/main/res/values-eu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-eu/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-fa/02-strings.xml b/AnkiDroid/src/main/res/values-fa/02-strings.xml index f314225f31e5..10b5b384e0f8 100644 --- a/AnkiDroid/src/main/res/values-fa/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fa/02-strings.xml @@ -122,6 +122,7 @@ مرتب سازی مجدد کارتها... %1$s (از \"%2$s\") هرکارتی که با هیچ چیز پیوند نخورده باشد حذف خواهد شد. اگر یک یادداشت هیچ کارت باقی مانده ای نداشته باشد، از دست خواهد رفت. ادامه می دهید؟ + Timebox reached %1$d کارت در %2$s مطالعه شد %1$d کارت در %2$s مطالعه شد diff --git a/AnkiDroid/src/main/res/values-fa/10-preferences.xml b/AnkiDroid/src/main/res/values-fa/10-preferences.xml index 5d8cee5b7f26..5ea01c9d028d 100644 --- a/AnkiDroid/src/main/res/values-fa/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fa/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-fi/02-strings.xml b/AnkiDroid/src/main/res/values-fi/02-strings.xml index 324844fd01e7..51382a5f4685 100644 --- a/AnkiDroid/src/main/res/values-fi/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fi/02-strings.xml @@ -122,6 +122,7 @@ Uudelleenjärjestetään kortteja... %1$s (”%2$s”:sta) Kortit, joita ei ole liitetty mihinkään, poistetaan. Jos muistiinpanoon ei liity jäljelle jääviä kortteja, se katoaa. Oletko varma, että haluat jatkaa? + Timebox reached %1$d korttia opiskeltu ajassa %2$s %1$d korttia opiskeltu ajassa %2$s diff --git a/AnkiDroid/src/main/res/values-fi/10-preferences.xml b/AnkiDroid/src/main/res/values-fi/10-preferences.xml index 9e43961a0a36..c07ccb9a2a9d 100644 --- a/AnkiDroid/src/main/res/values-fi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fi/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-fil/02-strings.xml b/AnkiDroid/src/main/res/values-fil/02-strings.xml index b855635005d0..22930b0bc1e0 100644 --- a/AnkiDroid/src/main/res/values-fil/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fil/02-strings.xml @@ -122,6 +122,7 @@ Pagsasaayos muli ng mga kard… %1$s (mula sa \"%2$s\") Ang anumang mga kard na nakabalangkas sa wala ay matatanggal. Kung ang isang tala ay walang natitirang mga kard, ito ay mawawala. Sigurado ka bang gusto mong magpatuloy? + Timebox reached %1$d ang kard na sinuri sa %2$s %1$d ang kard na sinuri sa %2$s diff --git a/AnkiDroid/src/main/res/values-fil/10-preferences.xml b/AnkiDroid/src/main/res/values-fil/10-preferences.xml index a213c69ec7de..bae80049f6cd 100644 --- a/AnkiDroid/src/main/res/values-fil/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fil/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-fr/02-strings.xml b/AnkiDroid/src/main/res/values-fr/02-strings.xml index 86c818ab639c..689a446b7776 100644 --- a/AnkiDroid/src/main/res/values-fr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fr/02-strings.xml @@ -122,6 +122,7 @@ Tri des cartes... %1$s (de « %2$s ») Toute carte sans connexion sera effacée. Si une note n’a aucune carte restante, elle sera perdue. Êtes-vous sûr de vouloir continuer ? + Timebox reached %1$d carte étudiée en %2$s %1$d cartes étudiées en %2$s diff --git a/AnkiDroid/src/main/res/values-fr/10-preferences.xml b/AnkiDroid/src/main/res/values-fr/10-preferences.xml index 9d83f37b3b09..3fca09e41ee7 100644 --- a/AnkiDroid/src/main/res/values-fr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fr/10-preferences.xml @@ -291,6 +291,8 @@ Dans l\'éditeur de notes, convertissez toutes les instances de <br> en nouvelles lignes lors de l\'édition d\'une carte. Formatage simple des réponses saisies Fixe « <unk> » dans les résultats des types de réponses + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Ouvrir le journal des modifications diff --git a/AnkiDroid/src/main/res/values-fy/02-strings.xml b/AnkiDroid/src/main/res/values-fy/02-strings.xml index b6ae15acb2e7..340959d16800 100644 --- a/AnkiDroid/src/main/res/values-fy/02-strings.xml +++ b/AnkiDroid/src/main/res/values-fy/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-fy/10-preferences.xml b/AnkiDroid/src/main/res/values-fy/10-preferences.xml index 0977ec86d4ab..c252d5cd000e 100644 --- a/AnkiDroid/src/main/res/values-fy/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-fy/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ga/02-strings.xml b/AnkiDroid/src/main/res/values-ga/02-strings.xml index c94cdaa05da8..47bc4a806e3a 100644 --- a/AnkiDroid/src/main/res/values-ga/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ga/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ga/10-preferences.xml b/AnkiDroid/src/main/res/values-ga/10-preferences.xml index 967ff2e81369..80bddc00d786 100644 --- a/AnkiDroid/src/main/res/values-ga/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ga/10-preferences.xml @@ -298,6 +298,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-gl/02-strings.xml b/AnkiDroid/src/main/res/values-gl/02-strings.xml index 93e4fbaaafa9..797636dd1ba9 100644 --- a/AnkiDroid/src/main/res/values-gl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-gl/02-strings.xml @@ -122,6 +122,7 @@ Reordenando os cartóns… %1$s (de “%2$s”) Eliminaranse os cartóns asignados a ren. Se unha nota non ten cartóns restantes, perderase. Tes a certeza de querer continuar? + Timebox reached %1$d cartón estudado en %2$s %1$d cartóns estudados en %2$s diff --git a/AnkiDroid/src/main/res/values-gl/10-preferences.xml b/AnkiDroid/src/main/res/values-gl/10-preferences.xml index 0b510ad38b4a..f0b8b4967e2d 100644 --- a/AnkiDroid/src/main/res/values-gl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-gl/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-got/02-strings.xml b/AnkiDroid/src/main/res/values-got/02-strings.xml index 1fd6aad7ddaf..f59a42ad69d7 100644 --- a/AnkiDroid/src/main/res/values-got/02-strings.xml +++ b/AnkiDroid/src/main/res/values-got/02-strings.xml @@ -122,6 +122,7 @@ Kartos aftra atgaraihtjand… %1$s (fram \'%2$s\') Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-got/10-preferences.xml b/AnkiDroid/src/main/res/values-got/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-got/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-got/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-gu/02-strings.xml b/AnkiDroid/src/main/res/values-gu/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-gu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-gu/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-gu/10-preferences.xml b/AnkiDroid/src/main/res/values-gu/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-gu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-gu/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-heb/02-strings.xml b/AnkiDroid/src/main/res/values-heb/02-strings.xml index 94cfc5cd5c0a..a6da6ce865be 100644 --- a/AnkiDroid/src/main/res/values-heb/02-strings.xml +++ b/AnkiDroid/src/main/res/values-heb/02-strings.xml @@ -122,6 +122,7 @@ מסדר מחדש את הקלפים… %1$s (מתוך \"%2$s\") כרטיס שממופה לכלום ימחק. אם לרשימה אין כרטסיות נותרות, היא תמחק. האם אתה בטוח ברצונך להמשיך? + Timebox reached %1$d כרטסיה נלמדו ב%2$s %1$d כרטסיות נלמדו ב%2$s diff --git a/AnkiDroid/src/main/res/values-heb/10-preferences.xml b/AnkiDroid/src/main/res/values-heb/10-preferences.xml index 80cca9ede33d..93fd40e53a3d 100644 --- a/AnkiDroid/src/main/res/values-heb/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-heb/10-preferences.xml @@ -296,6 +296,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-hi/02-strings.xml b/AnkiDroid/src/main/res/values-hi/02-strings.xml index b244aed3aacc..ada195bd2b9c 100644 --- a/AnkiDroid/src/main/res/values-hi/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hi/02-strings.xml @@ -122,6 +122,7 @@ कार्ड reordering... %1$s (\"%2$s\" से) कुछ नहीं के लिए मैप किए गए किसी भी कार्ड को हटा दिया जाएगा । यदि किसी नोट में कोई शेष कार्ड नहीं है, तो वह खो जाएगा । क्या आप निश्चयपूर्वक जारी रखना चाहते हैं? + Timebox reached %1$d कार्ड में अध्ययन किया %2$s %1$d कार्ड में अध्ययन किया %2$s diff --git a/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml index 87b5883a666e..06236ba55bbc 100644 --- a/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml @@ -108,9 +108,9 @@ Card Id Note Id समय - Date + दिनांक Rating - Type + प्रकार Learn Review Relearn diff --git a/AnkiDroid/src/main/res/values-hi/10-preferences.xml b/AnkiDroid/src/main/res/values-hi/10-preferences.xml index e7c2d240719e..788f1794a3e8 100644 --- a/AnkiDroid/src/main/res/values-hi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hi/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer परिवर्तन लॉग खोलें diff --git a/AnkiDroid/src/main/res/values-hr/02-strings.xml b/AnkiDroid/src/main/res/values-hr/02-strings.xml index f3120f6ad517..905cbacd24dc 100644 --- a/AnkiDroid/src/main/res/values-hr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hr/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-hr/10-preferences.xml b/AnkiDroid/src/main/res/values-hr/10-preferences.xml index 41e5032a1026..6e02ec4b7f4a 100644 --- a/AnkiDroid/src/main/res/values-hr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hr/10-preferences.xml @@ -294,6 +294,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-hu/02-strings.xml b/AnkiDroid/src/main/res/values-hu/02-strings.xml index 8f17d7d5983f..dac24df3ff47 100644 --- a/AnkiDroid/src/main/res/values-hu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hu/02-strings.xml @@ -122,6 +122,7 @@ Újrarendelési kártyák... %1$s (az “%2$s”) Az összes üres kártyát töröljük. Ha a jegyzet összes kártyája törlődik, akkor ez is eltávolításra kerül. Folytatod? + Timebox reached %1$d kártyát tanultál %2$s alatt %1$d kártyát tanultál %2$s alatt diff --git a/AnkiDroid/src/main/res/values-hu/10-preferences.xml b/AnkiDroid/src/main/res/values-hu/10-preferences.xml index d6f8c95dbc59..e597e325c7d1 100644 --- a/AnkiDroid/src/main/res/values-hu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hu/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-hy/02-strings.xml b/AnkiDroid/src/main/res/values-hy/02-strings.xml index 3c9a8d3763a7..d6f26779574a 100644 --- a/AnkiDroid/src/main/res/values-hy/02-strings.xml +++ b/AnkiDroid/src/main/res/values-hy/02-strings.xml @@ -122,6 +122,7 @@ Քարտերի վերադասավորում… %1$s («%2$s»-ից) Բոլոր չարտապատկերված քարտերը ջնջվելու են: Եթե գրառումը քարտ չունի, ապա այն կվերանա: Վստա՞հ եք, որ ուզում եք շարունակել: + Timebox reached %1$d քարտ սովորեցիք %2$s %1$d քարտ սովորեցիք %2$s diff --git a/AnkiDroid/src/main/res/values-hy/10-preferences.xml b/AnkiDroid/src/main/res/values-hy/10-preferences.xml index e07d3ad1d3a4..ef6844494715 100644 --- a/AnkiDroid/src/main/res/values-hy/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-hy/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ind/02-strings.xml b/AnkiDroid/src/main/res/values-ind/02-strings.xml index 730a81f011c7..c992f0bd596b 100644 --- a/AnkiDroid/src/main/res/values-ind/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ind/02-strings.xml @@ -122,6 +122,7 @@ Mengurutkan ulang kartu… %1$s (dari “%2$s”) Semua kartu yang yang dibuat kosong akan dihapus. Jika catatan tidak memiliki sisa kartu, kartu tersebut akan hilang. Apa kamu yakin? + Timebox reached %1$d kartu dipelajari dalam %2$s diff --git a/AnkiDroid/src/main/res/values-ind/10-preferences.xml b/AnkiDroid/src/main/res/values-ind/10-preferences.xml index 8418ef295f40..778f656fb312 100644 --- a/AnkiDroid/src/main/res/values-ind/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ind/10-preferences.xml @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-is/02-strings.xml b/AnkiDroid/src/main/res/values-is/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-is/02-strings.xml +++ b/AnkiDroid/src/main/res/values-is/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-is/10-preferences.xml b/AnkiDroid/src/main/res/values-is/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-is/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-is/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-it/02-strings.xml b/AnkiDroid/src/main/res/values-it/02-strings.xml index b30b691a9eb7..85c15aa3753d 100644 --- a/AnkiDroid/src/main/res/values-it/02-strings.xml +++ b/AnkiDroid/src/main/res/values-it/02-strings.xml @@ -122,6 +122,7 @@ Riordino le carte... %1$s (di \'%2$s\') Verranno eliminate eventuali carte senza connessione. Se una nota non ha nessuna carta rimanente, sarà perduta. Sei sicuro di voler continuare? + Timebox reached %1$d carta studiata in %2$s %1$d carte studiate in %2$s diff --git a/AnkiDroid/src/main/res/values-it/10-preferences.xml b/AnkiDroid/src/main/res/values-it/10-preferences.xml index 2ddcc4c67f98..64aebb447c1e 100644 --- a/AnkiDroid/src/main/res/values-it/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-it/10-preferences.xml @@ -291,6 +291,8 @@ Nell\'Editor delle Note, converti ogni istanza di <br> in newline quando modifichi una carta. Formattazione semplice della risposta Correzioni ‘􏿾’ che appaiono nei risultati delle risposte digitate + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Apri il registro dei cambiamenti diff --git a/AnkiDroid/src/main/res/values-ja/02-strings.xml b/AnkiDroid/src/main/res/values-ja/02-strings.xml index 8b8ace3c318c..de3c955c84c3 100644 --- a/AnkiDroid/src/main/res/values-ja/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ja/02-strings.xml @@ -122,6 +122,7 @@ カードを並べ替えています... %1$s (\'%2$s\'から) Nothing に設定したカードは削除され、同じノートタイプに1つもカードが残っていない場合は、そのノートタイプも同時に失われます。それでも処理を続行しますか。 + Timebox reached %1$d枚のカードを%2$sで学習しました diff --git a/AnkiDroid/src/main/res/values-ja/10-preferences.xml b/AnkiDroid/src/main/res/values-ja/10-preferences.xml index 4539dc0ec31e..dc8066bad984 100644 --- a/AnkiDroid/src/main/res/values-ja/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ja/10-preferences.xml @@ -289,6 +289,8 @@ ノートエディターでは、カードを編集するときに <br> を改行に変換します。 文字入力回答でシンプルなフォントを使用 文字入力して回答する種類のカードの解答画面で、自分の回答に文字化け(‘􏿾’の表示)が起こる不具合を回避します + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-jv/02-strings.xml b/AnkiDroid/src/main/res/values-jv/02-strings.xml index 267c3c8675ec..ac8d8f13238b 100644 --- a/AnkiDroid/src/main/res/values-jv/02-strings.xml +++ b/AnkiDroid/src/main/res/values-jv/02-strings.xml @@ -122,6 +122,7 @@ Menata ulang kartu… %1$s (dari \"%2$s\") Setiap kartu yang dipetakan ke tidak akan dihapus. Jika sebuah catatan tidak memiliki kartu yang tersisa, kartu tersebut akan hilang. Apakah anda yakin ingin melanjutkan? + Timebox reached Kartu %1$d dipelajari di %2$s diff --git a/AnkiDroid/src/main/res/values-jv/10-preferences.xml b/AnkiDroid/src/main/res/values-jv/10-preferences.xml index e34753ec51e1..8e61726abb23 100644 --- a/AnkiDroid/src/main/res/values-jv/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-jv/10-preferences.xml @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ka/02-strings.xml b/AnkiDroid/src/main/res/values-ka/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-ka/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ka/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ka/10-preferences.xml b/AnkiDroid/src/main/res/values-ka/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-ka/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ka/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-kk/02-strings.xml b/AnkiDroid/src/main/res/values-kk/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-kk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-kk/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-kk/10-preferences.xml b/AnkiDroid/src/main/res/values-kk/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-kk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-kk/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-km/02-strings.xml b/AnkiDroid/src/main/res/values-km/02-strings.xml index cefa222a2e20..25883887932e 100644 --- a/AnkiDroid/src/main/res/values-km/02-strings.xml +++ b/AnkiDroid/src/main/res/values-km/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-km/10-preferences.xml b/AnkiDroid/src/main/res/values-km/10-preferences.xml index e34753ec51e1..8e61726abb23 100644 --- a/AnkiDroid/src/main/res/values-km/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-km/10-preferences.xml @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ko/02-strings.xml b/AnkiDroid/src/main/res/values-ko/02-strings.xml index 097ac4eb667b..7bbfc91dee51 100644 --- a/AnkiDroid/src/main/res/values-ko/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ko/02-strings.xml @@ -123,6 +123,7 @@ 카드 순서 다시 정리중... %1$s (\'%2$s\'에서) 연결되지 않은 모든 카드는 삭제됩니다. 만약 노트에 남아있는 카드가 없다면 버려질 것입니다. 계속 진행할까요? + Timebox reached %1$d 카드가 %2$s에 학습됨 diff --git a/AnkiDroid/src/main/res/values-ko/10-preferences.xml b/AnkiDroid/src/main/res/values-ko/10-preferences.xml index 9cfb214f30c8..c8d96e33accc 100644 --- a/AnkiDroid/src/main/res/values-ko/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ko/10-preferences.xml @@ -289,6 +289,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ku/02-strings.xml b/AnkiDroid/src/main/res/values-ku/02-strings.xml index 2670cabb1504..44ca3a123aec 100644 --- a/AnkiDroid/src/main/res/values-ku/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ku/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ku/10-preferences.xml b/AnkiDroid/src/main/res/values-ku/10-preferences.xml index 0c650f4631d5..7d526057ca33 100644 --- a/AnkiDroid/src/main/res/values-ku/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ku/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ky/02-strings.xml b/AnkiDroid/src/main/res/values-ky/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-ky/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ky/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ky/10-preferences.xml b/AnkiDroid/src/main/res/values-ky/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-ky/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ky/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-lt/02-strings.xml b/AnkiDroid/src/main/res/values-lt/02-strings.xml index b46d2819c388..959348d938a8 100644 --- a/AnkiDroid/src/main/res/values-lt/02-strings.xml +++ b/AnkiDroid/src/main/res/values-lt/02-strings.xml @@ -122,6 +122,7 @@ Keičiamas kortelių eiliškumas… %1$s (nuo “%2$s”) Bet kokios tuščios kortelės bus ištrintos. Jei užrašas neturės susijusių kortelių, jis bus ištrintas. Ar tikrai norite tęsti? + Timebox reached Per %2$s išmokote %1$d kortelę Per %2$s išmokote %1$d korteles diff --git a/AnkiDroid/src/main/res/values-lt/10-preferences.xml b/AnkiDroid/src/main/res/values-lt/10-preferences.xml index b3eaa87b67a6..e514d624b094 100644 --- a/AnkiDroid/src/main/res/values-lt/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-lt/10-preferences.xml @@ -296,6 +296,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-lv/02-strings.xml b/AnkiDroid/src/main/res/values-lv/02-strings.xml index f849bae430cc..03ce7ced5e37 100644 --- a/AnkiDroid/src/main/res/values-lv/02-strings.xml +++ b/AnkiDroid/src/main/res/values-lv/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d cards studied in %2$s %1$d card studied in %2$s diff --git a/AnkiDroid/src/main/res/values-lv/10-preferences.xml b/AnkiDroid/src/main/res/values-lv/10-preferences.xml index b789126f9b0e..12a286353739 100644 --- a/AnkiDroid/src/main/res/values-lv/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-lv/10-preferences.xml @@ -294,6 +294,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-mk/02-strings.xml b/AnkiDroid/src/main/res/values-mk/02-strings.xml index be3ac715a6c3..50add434b47a 100644 --- a/AnkiDroid/src/main/res/values-mk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-mk/02-strings.xml @@ -122,6 +122,7 @@ Преуредување картички… %1$s (од “%2$s“) Сите неодбележани картички да бидат избришани. Ако белешката нема преостанати картички, таа ќе биде изгубена. Дали сте сигурни дека сакате да продолжите? + Timebox reached %1$d картичка проучена во %2$s %1$d картичка проучена во %2$s diff --git a/AnkiDroid/src/main/res/values-mk/10-preferences.xml b/AnkiDroid/src/main/res/values-mk/10-preferences.xml index 88b547a3e41b..946c3bbb85a5 100644 --- a/AnkiDroid/src/main/res/values-mk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-mk/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-mn/02-strings.xml b/AnkiDroid/src/main/res/values-mn/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-mn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-mn/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-mn/10-preferences.xml b/AnkiDroid/src/main/res/values-mn/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-mn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-mn/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-mr/02-strings.xml b/AnkiDroid/src/main/res/values-mr/02-strings.xml index 9ec99ebc0c45..43667ef38eb8 100644 --- a/AnkiDroid/src/main/res/values-mr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-mr/02-strings.xml @@ -122,6 +122,7 @@ Punarkramita kārḍa… %1$s(Pāsūna \"%2$s\") Kāhīhī na kēlēlyā kōṇatyāhī kārḍānnā haṭavilē jā\'īla. Ṭīpakaḍē urvarita kārḍa nasalyāsa tō gamāvalā jā\'īla. Āpalyālā khātrī āhē kī āpaṇa surū ṭhēvū icchitā? + Timebox reached %1$d Madhyē abhyāsa kēlēlā kārḍa %2$s %1$d Madhyē abhyāsa kēlēlā kārḍa %2$s diff --git a/AnkiDroid/src/main/res/values-mr/10-preferences.xml b/AnkiDroid/src/main/res/values-mr/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-mr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-mr/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ms/02-strings.xml b/AnkiDroid/src/main/res/values-ms/02-strings.xml index 0332e092b51b..87ab217fdcb8 100644 --- a/AnkiDroid/src/main/res/values-ms/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ms/02-strings.xml @@ -122,6 +122,7 @@ Penyusunan semula Kad… %1$s (daripada \"%2$s\") Apa kad dipetakan untuk tiada apa-apa akan dihapuskan. Jika nota tidak mempunyai baki kad, ia akan hilang. Adakah anda pasti anda mahu untuk terus? + Timebox reached %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ms/10-preferences.xml b/AnkiDroid/src/main/res/values-ms/10-preferences.xml index 7b996a20f21f..551487b4e6ec 100644 --- a/AnkiDroid/src/main/res/values-ms/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ms/10-preferences.xml @@ -290,6 +290,8 @@ minit yang lalu.
In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-my/02-strings.xml b/AnkiDroid/src/main/res/values-my/02-strings.xml index 48819525f3d2..0646af3ed7b7 100644 --- a/AnkiDroid/src/main/res/values-my/02-strings.xml +++ b/AnkiDroid/src/main/res/values-my/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-my/10-preferences.xml b/AnkiDroid/src/main/res/values-my/10-preferences.xml index e34753ec51e1..8e61726abb23 100644 --- a/AnkiDroid/src/main/res/values-my/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-my/10-preferences.xml @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-nl/02-strings.xml b/AnkiDroid/src/main/res/values-nl/02-strings.xml index b74410af6dd1..0450b37cf81d 100644 --- a/AnkiDroid/src/main/res/values-nl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-nl/02-strings.xml @@ -122,6 +122,7 @@ Kaarten herschikken... %1$s (van “%2$s”) De kaarten toegewezen aan niets zullen worden verwijderd. Als een memo resterende kaarten heeft, zullen zij verloren gaan. Weet u zeker dat u wilt doorgaan? + Timebox reached %1$d kaart geoefend in %2$s %1$d kaarten geoefend in %2$s diff --git a/AnkiDroid/src/main/res/values-nl/10-preferences.xml b/AnkiDroid/src/main/res/values-nl/10-preferences.xml index fc16ffda5b38..5ce5eb2cb313 100644 --- a/AnkiDroid/src/main/res/values-nl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-nl/10-preferences.xml @@ -291,6 +291,8 @@ Zet in de memobewerker alle instanties van <br> om in nieuwe lijnen bij het bewerken van een kaart. Eenvoudig getypte antwoordopmaak Herstelt het verschijnen van \'􏿾\' in getypte antwoordresultaten + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-nn/02-strings.xml b/AnkiDroid/src/main/res/values-nn/02-strings.xml index 9a112f8b55fc..b11ee77c46b6 100644 --- a/AnkiDroid/src/main/res/values-nn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-nn/02-strings.xml @@ -122,6 +122,7 @@ Stokker om kortene… %1$s (fra “%2$s”) Alle kort som ikke er tilordnet noe vil slettes. Hvis et notat ikke har kort igjen, vil det gå tapt. Er du sikker du vil fortsette? + Timebox reached %1$d kort studert i %2$s %1$d kort studert i %2$s diff --git a/AnkiDroid/src/main/res/values-nn/10-preferences.xml b/AnkiDroid/src/main/res/values-nn/10-preferences.xml index 1b8c4ca57d66..1a0f78dc97d1 100644 --- a/AnkiDroid/src/main/res/values-nn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-nn/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-no/02-strings.xml b/AnkiDroid/src/main/res/values-no/02-strings.xml index 9a112f8b55fc..b11ee77c46b6 100644 --- a/AnkiDroid/src/main/res/values-no/02-strings.xml +++ b/AnkiDroid/src/main/res/values-no/02-strings.xml @@ -122,6 +122,7 @@ Stokker om kortene… %1$s (fra “%2$s”) Alle kort som ikke er tilordnet noe vil slettes. Hvis et notat ikke har kort igjen, vil det gå tapt. Er du sikker du vil fortsette? + Timebox reached %1$d kort studert i %2$s %1$d kort studert i %2$s diff --git a/AnkiDroid/src/main/res/values-no/10-preferences.xml b/AnkiDroid/src/main/res/values-no/10-preferences.xml index 1b8c4ca57d66..1a0f78dc97d1 100644 --- a/AnkiDroid/src/main/res/values-no/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-no/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-or/02-strings.xml b/AnkiDroid/src/main/res/values-or/02-strings.xml index 4a2b97d70a6b..8813fd4b0278 100644 --- a/AnkiDroid/src/main/res/values-or/02-strings.xml +++ b/AnkiDroid/src/main/res/values-or/02-strings.xml @@ -122,6 +122,7 @@ ପତ୍ର ପୁନଃବ୍ୟବସ୍ଥିତ ହଉଛି %1$s (“%2$s” ରୁ) ବିନା କୌଣସି ଜିନିଷକୁ ମ୍ୟାପ୍ ହୋଇଥିବା ପତ୍ର ବିଲୋପ ହେଇଯିବ। ଯଦି କୌଣସି ନୋଟ୍ ର ଅବଶିଷ୍ଟ ପତ୍ର ନାହିଁ, ତେବେ ଏହା ନଷ୍ଟ ହୋଇଯିବ। ଆପଣ ଜାରି ରଖିବାକୁ ଚାହୁଁଛନ୍ତି କି? + Timebox reached %1$d ପତ୍ର %2$s ସମୟରେ ଅଧ୍ୟୟନ କଲେ %1$d ଟି ପତ୍ର %2$s ସମୟରେ ଅଧ୍ୟୟନ କଲେ @@ -347,5 +348,5 @@ ଇମେଲ୍ ଠିକଣା ଆବଶ୍ୟକ ପାସୱାର୍ଡ ଆବଶ୍ୟକ - Press back again to exit + ପ୍ରସ୍ଥାନ କରିବା ପାଇଁ ପୁନର୍ବାର ଦବାନ୍ତୁ diff --git a/AnkiDroid/src/main/res/values-or/10-preferences.xml b/AnkiDroid/src/main/res/values-or/10-preferences.xml index 331df1538f40..0c3c5cbcd5e0 100644 --- a/AnkiDroid/src/main/res/values-or/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-or/10-preferences.xml @@ -151,8 +151,8 @@ XXX s ପରବର୍ତ୍ତୀ ପ୍ରଶ୍ନ ଦେଖାଇବାର ସମୟ ଆସିଗଲା ଭାଷା ବାଛନ୍ତୁ - Disable card hardware render - Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. + କାର୍ଡ ହାର୍ଡୱେର ପ୍ରସ୍ତୁତକର୍ତ୍ତା କୁ ଅକ୍ଷମ କରନ୍ତୁ + ହାର୍ଡୱେୟାର୍ ପ୍ରସ୍ତୁତକର୍ତ୍ତା ଦ୍ରୁତ ଅଟେ କିନ୍ତୁ ବିଶେଷ କରି ଆଣ୍ଡ୍ରଏଡ୍ 8/8.1 ରେ ସମସ୍ୟା ହୋଇପାରେ। ଯଦି ଆପଣ କାର୍ଡ୍ ସମୀକ୍ଷା ଉପଭୋକ୍ତା ଇଣ୍ଟରଫେସର କିଛି ଅଂଶ ଦେଖିପାରୁ ନାହାନ୍ତି, ତେବେ ଏହି ସେଟିଂ ଚେଷ୍ଟା କରନ୍ତୁ। ସୁରକ୍ଷିତ ପ୍ରଦର୍ଶନ ମୋଡ୍ ସମସ୍ତ ଆନିମେସନ୍ ଅକ୍ଷମ କରନ୍ତୁ ଏବଂ କାର୍ଡ ଅଙ୍କନ ପାଇଁ ସୁରକ୍ଷିତ ପଦ୍ଧତି ବ୍ୟବହାର କରନ୍ତୁ | ଇ-ଇଙ୍କ ଉପକରଣଗୁଡ଼ିକ ଏହା ଆବଶ୍ୟକ କରିପାରନ୍ତି | ଗୋଟିଏ-କ୍ଷେତ୍ର ସଂପାଦନା ମୋଡ୍ କୁ ଅକ୍ଷମ କରନ୍ତୁ @@ -291,6 +291,8 @@ ନୋଟ୍ ସମ୍ପାଦକରେ, କାର୍ଡ ସମ୍ପାଦନ କରିବା ସମୟରେ <br> କୁ ନୂତନ ଲାଇନରେ ରୂପାନ୍ତର କରନ୍ତୁ। ସରଳ ଟାଇପ୍ ହୋଇଥିବା ଉତ୍ତର ଫର୍ମାଟିଂ ଟାଇପ୍ ହୋଇଥିବା ଉତ୍ତର ଫଳାଫଳଗୁଡିକରେ ଦେଖାଯାଉଥିବା ‘�’ ଠିକ୍ କରେ | + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer ପରିବର୍ତ୍ତନ ଲଗ୍ ଖୋଲନ୍ତୁ diff --git a/AnkiDroid/src/main/res/values-pa/02-strings.xml b/AnkiDroid/src/main/res/values-pa/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-pa/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pa/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-pa/10-preferences.xml b/AnkiDroid/src/main/res/values-pa/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-pa/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pa/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-pl/02-strings.xml b/AnkiDroid/src/main/res/values-pl/02-strings.xml index ba25d7379d7f..21202750fc4c 100644 --- a/AnkiDroid/src/main/res/values-pl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pl/02-strings.xml @@ -122,6 +122,7 @@ Zmiana kolejności kart... %1$s (z “%2$s”) Wszystkie nieprzypisane karty zostaną usunięte. Jeśli notatka nie zawiera już kart, również zostanie usunięta. Czy na pewno chcesz kontynuować? + Timebox reached %1$d karta przerobiona w %2$s %1$d karty przerobione w %2$s diff --git a/AnkiDroid/src/main/res/values-pl/10-preferences.xml b/AnkiDroid/src/main/res/values-pl/10-preferences.xml index eca1624a4c39..4d1d35f3ff3b 100644 --- a/AnkiDroid/src/main/res/values-pl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pl/10-preferences.xml @@ -295,6 +295,8 @@ W Edytorze Notatek zmień wszelkie przykłady <br> na nowy wiersz kiedy karta jest edytowana. Proste formatowanie wpisywanej odpowiedzi Naprawia ‘􏿾’ pojawiające się we wpisywanych odpowiedziach + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Otwórz listę zmian w programie 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 da4a0414408a..d0e4d9d04f9e 100644 --- a/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pt-rBR/02-strings.xml @@ -122,6 +122,7 @@ Reordenando cards... %1$s (de \'%2$s\') Quaisquer cards não-mapeados serão removidos. Se não houverem cards para uma nota, ela será perdida. Tem certeza de que deseja continuar? + Timebox reached %1$d card estudado em %2$s %1$d cards estudados em %2$s 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 b3ca097f3a77..161c0e1c92f4 100644 --- a/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pt-rBR/10-preferences.xml @@ -291,6 +291,8 @@ No Editor de Notas, converta as ocorrências de <br> para uma quebra de linha ao editar um cartão. Formatação de resposta a ser digitada Corrige \'􏿾\' que aparecer nas respostas digitadas + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog 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 118e5c138003..2f91e0870dee 100644 --- a/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pt-rPT/02-strings.xml @@ -122,6 +122,7 @@ A reordenar as fichas… %1$s (de \"%2$s\") Serão eliminadas quaisquer fichas mapeadas para nada. Se uma nota não tiver fichas restantes, esta estará perdida. Tem certeza que deseja continuar? + Timebox reached %1$d ficha estudada em %2$s %1$d fichas estudadas em %2$s 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 7b87eba7e994..5025275d838c 100644 --- a/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pt-rPT/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ro/02-strings.xml b/AnkiDroid/src/main/res/values-ro/02-strings.xml index 9fb49c231e54..08b15a8eb470 100644 --- a/AnkiDroid/src/main/res/values-ro/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ro/02-strings.xml @@ -122,6 +122,7 @@ Reordonare carduri... %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ro/10-preferences.xml b/AnkiDroid/src/main/res/values-ro/10-preferences.xml index 47e2114cbcc7..99ca084543ee 100644 --- a/AnkiDroid/src/main/res/values-ro/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ro/10-preferences.xml @@ -294,6 +294,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ru/02-strings.xml b/AnkiDroid/src/main/res/values-ru/02-strings.xml index e5b258a50b42..cb9575c017ff 100644 --- a/AnkiDroid/src/main/res/values-ru/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ru/02-strings.xml @@ -122,6 +122,7 @@ Карточки перемешиваются… %1$s (из \'%2$s\') Несопоставленные карты будут удалены. Записи, оставшиеся без карт, будут утеряны. Продолжить? + Timebox reached %1$d карточка изучена за %2$s %1$d карточки изучены за %2$s diff --git a/AnkiDroid/src/main/res/values-ru/10-preferences.xml b/AnkiDroid/src/main/res/values-ru/10-preferences.xml index 74c850be1d70..cd5d15cd683f 100644 --- a/AnkiDroid/src/main/res/values-ru/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ru/10-preferences.xml @@ -295,6 +295,8 @@ Заменять <br> в редакторе записей на перенос строки Простое форматирование ответа Исправляет «􏿾» при вводе ответа + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-sat/02-strings.xml b/AnkiDroid/src/main/res/values-sat/02-strings.xml index 5856eabe1d5d..0d3a706b4153 100644 --- a/AnkiDroid/src/main/res/values-sat/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sat/02-strings.xml @@ -122,6 +122,7 @@ ᱠᱟᱰ ᱨᱮᱠᱚᱰᱤᱝ… %1$s (“%2$s” ᱠᱷᱚᱱ) ᱡᱟᱷᱟᱭ ᱠᱟᱰ ᱵᱟᱭ ᱢᱮᱯ ᱠᱚᱜᱼᱟ ᱚᱱᱟ ᱠᱟᱲ ᱫᱚ ᱜᱮᱫ ᱜᱤᱰᱤᱜᱼᱟ ᱾ ᱡᱚᱫᱤ ᱠᱷᱟᱴᱚ ᱵᱤᱪᱟᱹᱨ ᱨᱮ ᱠᱟᱰ ᱵᱟᱝ ᱛᱟᱦᱮᱱᱟ ᱠᱷᱟᱱ ᱪᱮᱫ ᱢᱟᱱᱮ ᱵᱟᱝ ᱛᱟᱦᱮᱱᱟ ᱾ ᱢᱤᱫ ᱞᱮᱛᱟᱲ ᱥᱮᱱᱟᱢ ᱠᱟᱱᱟ? + Timebox reached 1$d ᱠᱟᱲ %2$s ᱨᱮ ᱯᱟᱲᱦᱟᱣ ᱠᱮᱜᱟᱢ 1$d ᱠᱟᱲᱥ %2$s ᱨᱮ ᱯᱟᱲᱦᱟᱣ ᱠᱮᱜᱟᱢ diff --git a/AnkiDroid/src/main/res/values-sat/10-preferences.xml b/AnkiDroid/src/main/res/values-sat/10-preferences.xml index 566f650963dd..0de8e90da11c 100644 --- a/AnkiDroid/src/main/res/values-sat/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sat/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-sk/02-strings.xml b/AnkiDroid/src/main/res/values-sk/02-strings.xml index c2a55dbbf2fd..1df21d2a0c2b 100644 --- a/AnkiDroid/src/main/res/values-sk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sk/02-strings.xml @@ -122,6 +122,7 @@ Zmena poradia kartičiek… %1$s (z “%2$s”) Akékoľvek kartičky, ktoré nie sú na nič mapované, budú zmazané. Ak poznámka nemá žiadne zostávajúce kartičky, bude stratená. Ste si istý, že chcete pokračovať? + Timebox reached %1$d preštudovaná kartička za %2$s %1$d preštudované kartičky za %2$s diff --git a/AnkiDroid/src/main/res/values-sk/10-preferences.xml b/AnkiDroid/src/main/res/values-sk/10-preferences.xml index edb0f3e0cc0d..e81ede843444 100644 --- a/AnkiDroid/src/main/res/values-sk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sk/10-preferences.xml @@ -296,6 +296,8 @@ Premeniť v editore poznámok každý prípad <br> do nového riadku pri editácii kartičky. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-sl/02-strings.xml b/AnkiDroid/src/main/res/values-sl/02-strings.xml index 0fabf4048761..d048f9dc6874 100644 --- a/AnkiDroid/src/main/res/values-sl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sl/02-strings.xml @@ -122,6 +122,7 @@ Prerazvrščanje kartic … %1$s (od \"%2$s\") Vse kartice, ki so preslikane v nič, bodo izbrisane. Če zapisek nima preostalih kartic, bo izgubljen. Ali res želite nadaljevati? + Timebox reached %1$d kartica preučena v %2$s %1$d kartici preučeni v %2$s diff --git a/AnkiDroid/src/main/res/values-sl/10-preferences.xml b/AnkiDroid/src/main/res/values-sl/10-preferences.xml index b99cbdbde8a0..5f390a72732c 100644 --- a/AnkiDroid/src/main/res/values-sl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sl/10-preferences.xml @@ -296,6 +296,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-sq/02-strings.xml b/AnkiDroid/src/main/res/values-sq/02-strings.xml index 7ce498b78f9b..4a22d5528eb0 100644 --- a/AnkiDroid/src/main/res/values-sq/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sq/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-sq/10-preferences.xml b/AnkiDroid/src/main/res/values-sq/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-sq/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sq/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-sr/02-strings.xml b/AnkiDroid/src/main/res/values-sr/02-strings.xml index 060d3a091098..7c161bbe3789 100644 --- a/AnkiDroid/src/main/res/values-sr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sr/02-strings.xml @@ -122,6 +122,7 @@ Промена редоследа картица… %1$s (од \'%2$s\') Немапиране карте ће бити избрисане. Напомене, које немају своје карте, биће изгубљене. Да продужим? + Timebox reached %1$d карта проучена у %2$s %1$d карата проучено у %2$s diff --git a/AnkiDroid/src/main/res/values-sr/10-preferences.xml b/AnkiDroid/src/main/res/values-sr/10-preferences.xml index cb1892fe12f0..c615d5f09853 100644 --- a/AnkiDroid/src/main/res/values-sr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sr/10-preferences.xml @@ -294,6 +294,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ss/02-strings.xml b/AnkiDroid/src/main/res/values-ss/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-ss/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ss/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ss/10-preferences.xml b/AnkiDroid/src/main/res/values-ss/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-ss/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ss/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-sv/02-strings.xml b/AnkiDroid/src/main/res/values-sv/02-strings.xml index 4728812582bd..2df3b70bef33 100644 --- a/AnkiDroid/src/main/res/values-sv/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sv/02-strings.xml @@ -122,6 +122,7 @@ Ändrar ordning på korten... %1$s (från \"%2$s\") Alla kort som inte mappar till något kommer att tas bort. Om en not inte har några kvarvarande kort, kommer den att tas bort. Är du säker på att du vill fortsätta? + Timebox reached Studera %1$d kort i %2$s Studera %1$d kort i %2$s diff --git a/AnkiDroid/src/main/res/values-sv/10-preferences.xml b/AnkiDroid/src/main/res/values-sv/10-preferences.xml index 440beff49e8a..b5f8b19c3953 100644 --- a/AnkiDroid/src/main/res/values-sv/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sv/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-sw/02-strings.xml b/AnkiDroid/src/main/res/values-sw/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-sw/02-strings.xml +++ b/AnkiDroid/src/main/res/values-sw/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-sw/10-preferences.xml b/AnkiDroid/src/main/res/values-sw/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-sw/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-sw/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ta/02-strings.xml b/AnkiDroid/src/main/res/values-ta/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-ta/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ta/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ta/10-preferences.xml b/AnkiDroid/src/main/res/values-ta/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-ta/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ta/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-te/02-strings.xml b/AnkiDroid/src/main/res/values-te/02-strings.xml index 15406803384e..649ac700c669 100644 --- a/AnkiDroid/src/main/res/values-te/02-strings.xml +++ b/AnkiDroid/src/main/res/values-te/02-strings.xml @@ -122,6 +122,7 @@ క్రమాన్ని క్రమం చేస్తుంది… %1$s (from “%2$s”) ఏదైనా మ్యాప్ చేసిన కార్డ్లు తొలగించబడతాయి. గమనికకు మిగిలిన కార్డులు లేకుంటే, అది కోల్పోతుంది. మీరు ఖచ్చితంగా కొనసాగించాలనుకుంటున్నారా? + Timebox reached %1$d కార్డ్ అధ్యయనం %2$s %1$d కార్డ్ అధ్యయనం %2$s diff --git a/AnkiDroid/src/main/res/values-te/10-preferences.xml b/AnkiDroid/src/main/res/values-te/10-preferences.xml index 7854ebb3b9a4..d5a7eee55973 100644 --- a/AnkiDroid/src/main/res/values-te/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-te/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-tg/02-strings.xml b/AnkiDroid/src/main/res/values-tg/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-tg/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tg/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-tg/10-preferences.xml b/AnkiDroid/src/main/res/values-tg/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-tg/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tg/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-tgl/02-strings.xml b/AnkiDroid/src/main/res/values-tgl/02-strings.xml index ba4f463bb5fc..6b985a4d20f9 100644 --- a/AnkiDroid/src/main/res/values-tgl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tgl/02-strings.xml @@ -122,6 +122,7 @@ Isinasaayos and pagkakasunod-sunod ng mga baraha… %1$s (mula “%2$s”) Ang anumang mga baraha na hindi naka-map ay tatanggalin. Kung ang isang paalala ay walang natitirang mga baraha, mawawala ito. Sigurado ka bang gusto mong magpatuloy? + Timebox reached %1$d napagaralang baraha sa %2$s %1$d napagaralang mga baraha sa %2$s diff --git a/AnkiDroid/src/main/res/values-tgl/10-preferences.xml b/AnkiDroid/src/main/res/values-tgl/10-preferences.xml index d83b843d6d9c..af011d8ad23b 100644 --- a/AnkiDroid/src/main/res/values-tgl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tgl/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-th/02-strings.xml b/AnkiDroid/src/main/res/values-th/02-strings.xml index 48819525f3d2..0646af3ed7b7 100644 --- a/AnkiDroid/src/main/res/values-th/02-strings.xml +++ b/AnkiDroid/src/main/res/values-th/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-th/10-preferences.xml b/AnkiDroid/src/main/res/values-th/10-preferences.xml index e34753ec51e1..8e61726abb23 100644 --- a/AnkiDroid/src/main/res/values-th/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-th/10-preferences.xml @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ti/02-strings.xml b/AnkiDroid/src/main/res/values-ti/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-ti/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ti/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ti/10-preferences.xml b/AnkiDroid/src/main/res/values-ti/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-ti/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ti/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-tn/02-strings.xml b/AnkiDroid/src/main/res/values-tn/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-tn/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tn/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-tn/10-preferences.xml b/AnkiDroid/src/main/res/values-tn/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-tn/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tn/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-tr/02-strings.xml b/AnkiDroid/src/main/res/values-tr/02-strings.xml index cd8927050859..be868ac9b928 100644 --- a/AnkiDroid/src/main/res/values-tr/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tr/02-strings.xml @@ -122,6 +122,7 @@ Kartların sırası değiştiriliyor... %1$s (\'%2$s\')\'dan/den Hiçbiriyle eşlemeyen herhangi bir kart silinecek. Geriye kalan kartları olmayan not varsa, kaybolur. Devam etmek istediğinizden emin misiniz? + Timebox reached %2$s %1$d kart çalışıldı %2$s %1$d kart çalışıldı diff --git a/AnkiDroid/src/main/res/values-tr/10-preferences.xml b/AnkiDroid/src/main/res/values-tr/10-preferences.xml index fa1768647ebb..cf298cde5ec7 100644 --- a/AnkiDroid/src/main/res/values-tr/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tr/10-preferences.xml @@ -291,6 +291,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ts/02-strings.xml b/AnkiDroid/src/main/res/values-ts/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-ts/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ts/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ts/10-preferences.xml b/AnkiDroid/src/main/res/values-ts/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-ts/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ts/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-tt/02-strings.xml b/AnkiDroid/src/main/res/values-tt/02-strings.xml index f0cc7556de17..387ce7242b9a 100644 --- a/AnkiDroid/src/main/res/values-tt/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tt/02-strings.xml @@ -122,6 +122,7 @@ Кәртләр чираты үзгәртелә... %1$s (\"%2$s\"дан) Бернәрсәгә дә бәйләнмәгән кәртләр сөртелер. Кәртсез калган язулар югалыр. Дәвам итәргә телисезме? + Timebox reached %2$s эчендә %1$d кәрт өйрәнелде @@ -210,10 +211,10 @@ After copying the text, tap anywhere on the flashcard to show the search icon - %s seconds + %s секунд - %s minutes + %s минут %s сәгатъ @@ -230,7 +231,7 @@ - %s seconds + %s секунд @@ -293,7 +294,7 @@ Invalid AnkiDroid JS API version. Contact developer %s, or view wiki AnkiDroid JS API update available. Contact developer %s, or view wiki View - (Error Code: %d) + (Хата коды: %d) Copied debug information to clipboard Error copying debug information to clipboard diff --git a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml index b1e52632a5fa..abf9ca3c8b50 100644 --- a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml @@ -160,7 +160,7 @@ Audio recording permission denied - Welcome to AnkiDroid! + AnkiDroid-ка рәхим итегез! AnkiDroid requires storage permission, which we use exclusively to store your AnkiDroid collection, flashcard media and backups. Our code is open-source, written by volunteers, and trusted by millions.\n\nIf you have any questions, please access our in-app manual or visit our support forums.\n\nThank you for trying AnkiDroid!\n—AnkiDroid Development Team Unknown error occurred displaying Advanced Editor @@ -206,7 +206,7 @@ Using AnkiDroid AnkiDroid Manual Anki Manual - AnkiDroid FAQ + AnkiDroid Сорау-Җавап Get Help Mailing List Reddit diff --git a/AnkiDroid/src/main/res/values-tt/04-network.xml b/AnkiDroid/src/main/res/values-tt/04-network.xml index fa65810a659f..0845b3242d50 100644 --- a/AnkiDroid/src/main/res/values-tt/04-network.xml +++ b/AnkiDroid/src/main/res/values-tt/04-network.xml @@ -62,7 +62,7 @@ Reset password Preferences - Options for XXX + XXX көйләүләрдә You must either upload this collection or download it from Ankiweb, as they can’t be merged. The collection on one side will be overwritten. Upload Йөкләп алу diff --git a/AnkiDroid/src/main/res/values-tt/06-statistics.xml b/AnkiDroid/src/main/res/values-tt/06-statistics.xml index 24927fe44369..b54584040505 100644 --- a/AnkiDroid/src/main/res/values-tt/06-statistics.xml +++ b/AnkiDroid/src/main/res/values-tt/06-statistics.xml @@ -64,13 +64,13 @@ 1 ел deck life collection - Time of day + Сәгать Атна көне % correct Reviews Answer type Again count: <b>%d</b> - (<b>%.1f%%</b> correct) + (<b>%.1f%%</b> дөрес) Learn: <b>%1$d</b>, review: <b>%2$d</b>, relearn: <b>%3$d</b>, filtered: <b>%4$d</b> Correct answers on mature cards: %1$d/%2$d (%3$.1f%%) No mature cards were studied today @@ -88,10 +88,10 @@ Total: <b>%1$d</b> cards "Average interval: " "Longest interval: " - <b>%1$.1f</b> years - <b>%1$.1f</b> months - <b>%1$.1f</b> days - <b>%1$.1f</b> hours + <b>%1$.1f</b> ел + <b>%1$.1f</b> ай + <b>%1$.1f</b> көн + <b>%1$.1f</b> cәгать Learning: <b>%1$.2f%%</b> correct (%2$d of %3$d) Young: <b>%1$.2f%%</b> correct (%2$d of %3$d) Mature: <b>%1$.2f%%</b> correct (%2$d of %3$d) diff --git a/AnkiDroid/src/main/res/values-tt/10-preferences.xml b/AnkiDroid/src/main/res/values-tt/10-preferences.xml index 356203ed9119..ede59e63d0d5 100644 --- a/AnkiDroid/src/main/res/values-tt/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-tt/10-preferences.xml @@ -42,7 +42,7 @@ Disabled Enabled - General settings + Төп көйләүләр Reviewing Non-deck-specific reviewer settings Behavior @@ -60,7 +60,7 @@ Performance Workarounds Plugins - About AnkiDroid + AnkiDroid турында Версия: Stroke width @@ -116,7 +116,7 @@ Тавышны киметү eReader Also use kanji/katakana, emoji/kao-moji buttons for scrolling - ‘%s’ Menu + ‘%s’ Меню Enables the ‘%s’ context menu globally Double scrolling Double the scroll gap with eReader @@ -125,7 +125,7 @@ Reads out question and answer if no sound file is included Fetch media on sync Automatically fetch missing media when syncing - AnkiWeb account + AnkiWeb хисап язмасы Not logged in %s Automatic synchronization @@ -133,7 +133,7 @@ minutes ago.
Display synchronization status Change the sync icon when changes can be uploaded - Day theme + Көн темасы Төнге режим Default font Default font applicability @@ -187,7 +187,7 @@ Disable screen timeout Chess notation support Draw chessboard from Forsyth–Edwards Notation. The notation must be enclosed in [fen][/fen] tags. - Enable AnkiDroid API + AnkiDroid API кабызу Let other apps connect to AnkiDroid and read / write data without your intervention App bar buttons Customize which actions appear in the app bar @@ -230,9 +230,9 @@ Reminders Steps (in minutes) Тәртипкә салу - New cards/day + Яңа кәртләр / көн Graduating interval - XXX day(s) + XXX көн Easy interval Starting ease Maximum reviews/day @@ -240,7 +240,7 @@ Related new cards are buried until the next day Bury related reviews Related reviews are buried until the next day - Use general settings + Төп көйләүләрне куллану Use general \'Automatic display answer\' settings instead of using the settings for this group. Easy bonus Hard interval @@ -249,10 +249,10 @@ New interval Minimum interval Leech threshold - XXX lapse(s) + XXX хата Leech action Maximal answer time - XXX seconds + XXX секунд Show answer timer Automatically play audio Replay question @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml b/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml index 83e27679a190..c796334f88c2 100644 --- a/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml +++ b/AnkiDroid/src/main/res/values-tt/16-multimedia-editor.xml @@ -115,7 +115,7 @@ Editing field Тәрҗемә - No connection + Бәйләнеш юк Do you want to crop this image? Crop image diff --git a/AnkiDroid/src/main/res/values-tt/18-standard-models.xml b/AnkiDroid/src/main/res/values-tt/18-standard-models.xml index fe1e1af408ec..8623b5a91515 100644 --- a/AnkiDroid/src/main/res/values-tt/18-standard-models.xml +++ b/AnkiDroid/src/main/res/values-tt/18-standard-models.xml @@ -26,7 +26,7 @@ Extra Add Reverse - Card %d + Кәрт: %d Basic Cloze diff --git a/AnkiDroid/src/main/res/values-uk/02-strings.xml b/AnkiDroid/src/main/res/values-uk/02-strings.xml index 41ceefc58ebd..1c7d48b104fc 100644 --- a/AnkiDroid/src/main/res/values-uk/02-strings.xml +++ b/AnkiDroid/src/main/res/values-uk/02-strings.xml @@ -122,6 +122,7 @@ Зміна порядку карт... %1$s (з\"%2$s\") Картки, які не прив\'язані, будуть видалені. Якщо запис не має жодної картки, то запис буде втрачено. Ви хочете продовжити? + Timebox reached %1$d картка переглянута за %2$s %1$d картки переглянуті за %2$s diff --git a/AnkiDroid/src/main/res/values-uk/10-preferences.xml b/AnkiDroid/src/main/res/values-uk/10-preferences.xml index 931c5f2ecb0c..c580ce87cb72 100644 --- a/AnkiDroid/src/main/res/values-uk/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-uk/10-preferences.xml @@ -296,6 +296,8 @@ Замінювати у Редакторі нотаток будь-які випадки <br> на нові рядки під час редагування картки. Просте форматування введених відповідей Виправляє «􏿾», що з\'являються у результатах введених відповідей + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Відкрити список змін diff --git a/AnkiDroid/src/main/res/values-ur/02-strings.xml b/AnkiDroid/src/main/res/values-ur/02-strings.xml index e4492af1c117..8c6fe26fca3f 100644 --- a/AnkiDroid/src/main/res/values-ur/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ur/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ur/10-preferences.xml b/AnkiDroid/src/main/res/values-ur/10-preferences.xml index 780655281985..dc7f0a952e29 100644 --- a/AnkiDroid/src/main/res/values-ur/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ur/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-uz/02-strings.xml b/AnkiDroid/src/main/res/values-uz/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-uz/02-strings.xml +++ b/AnkiDroid/src/main/res/values-uz/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-uz/10-preferences.xml b/AnkiDroid/src/main/res/values-uz/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-uz/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-uz/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-ve/02-strings.xml b/AnkiDroid/src/main/res/values-ve/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-ve/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ve/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-ve/10-preferences.xml b/AnkiDroid/src/main/res/values-ve/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-ve/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ve/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-vi/02-strings.xml b/AnkiDroid/src/main/res/values-vi/02-strings.xml index a96223054011..018d6a040035 100644 --- a/AnkiDroid/src/main/res/values-vi/02-strings.xml +++ b/AnkiDroid/src/main/res/values-vi/02-strings.xml @@ -122,6 +122,7 @@ Đang sắp sếp lại các thẻ... %1$s (từ \"%2$s\") Bất kỳ thẻ nào không được gán sẽ bị xoá. Nếu một ghi chú không có thẻ nào kèm theo, nó sẽ bị mất. Có chắc là bạn muốn tiếp tục? + Timebox reached Đã học %1$d thẻ trong %2$s diff --git a/AnkiDroid/src/main/res/values-vi/10-preferences.xml b/AnkiDroid/src/main/res/values-vi/10-preferences.xml index a1f92bc6b08a..81b30bd4c361 100644 --- a/AnkiDroid/src/main/res/values-vi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-vi/10-preferences.xml @@ -289,6 +289,8 @@ Trong Trình chỉnh sửa ghi chú, chuyển đổi thẻ <br> thành dòng mới khi chỉnh sửa thẻ. Định dạng câu trả lời đã nhập đơn giản Sửa lỗi ‘􏿾’ xuất hiện trong kết quả câu trả lời đã nhập + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-wo/02-strings.xml b/AnkiDroid/src/main/res/values-wo/02-strings.xml index 48819525f3d2..0646af3ed7b7 100644 --- a/AnkiDroid/src/main/res/values-wo/02-strings.xml +++ b/AnkiDroid/src/main/res/values-wo/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-wo/10-preferences.xml b/AnkiDroid/src/main/res/values-wo/10-preferences.xml index e34753ec51e1..8e61726abb23 100644 --- a/AnkiDroid/src/main/res/values-wo/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-wo/10-preferences.xml @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-xh/02-strings.xml b/AnkiDroid/src/main/res/values-xh/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-xh/02-strings.xml +++ b/AnkiDroid/src/main/res/values-xh/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-xh/10-preferences.xml b/AnkiDroid/src/main/res/values-xh/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-xh/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-xh/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-yue/02-strings.xml b/AnkiDroid/src/main/res/values-yue/02-strings.xml index 9aa4f18e52cb..030d95265248 100644 --- a/AnkiDroid/src/main/res/values-yue/02-strings.xml +++ b/AnkiDroid/src/main/res/values-yue/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-yue/10-preferences.xml b/AnkiDroid/src/main/res/values-yue/10-preferences.xml index e34753ec51e1..8e61726abb23 100644 --- a/AnkiDroid/src/main/res/values-yue/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-yue/10-preferences.xml @@ -290,6 +290,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog 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 9ea259a7be49..b0cef3cd78b3 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml @@ -122,6 +122,7 @@ 重新排列卡片中... %1$s (从“%2$s”) 在笔记类型中没有相应模板的卡片将被删除。如果该笔记没有生成其他的卡片,则该笔记将会丢失。你确定要继续吗? + Timebox reached 在%2$s内学习了%1$d张卡片 @@ -334,5 +335,5 @@ 电子邮件地址是必填项 密码是必填项 - Press back again to exit + 再按一次返回键以退出 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 438e880f03b5..9b453f501672 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml @@ -151,8 +151,8 @@ XXX 秒 显示下一个问题时间 选择语言 - Disable card hardware render - Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. + 禁用卡牌硬件渲染 + 硬件渲染速度较快,但可能有问题,特别是在 Android 8/8.1。如果您看不到部分卡片复习用户界面,请尝试此设置。 安全显示模式 禁用所有动画并使用更安全的方法绘制卡牌。使用墨水屏(E-ink)的设备可能需要此功能。 禁用单字段编辑模式 @@ -289,6 +289,8 @@ 在笔记编辑器中,编辑卡片时将 <br> 的任何实例转换为换行。 简单输入的答案格式 修复在输入的答案结果中出现的“􏿾” + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer 打开更新日志 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 c3b4b04d3514..27669395057e 100644 --- a/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml +++ b/AnkiDroid/src/main/res/values-zh-rTW/02-strings.xml @@ -122,6 +122,7 @@ 正在重新排列卡片... %1$s (來自 \"%2$s\") 空白卡片會被刪除,而筆記如果沒有在卡片上也會遺失。你確定要繼續嗎? + Timebox reached 使用 %2$s 學習共 %1$d 張卡片 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 3cca1d22cc29..0f2f01be56bd 100644 --- a/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zh-rTW/10-preferences.xml @@ -289,6 +289,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog diff --git a/AnkiDroid/src/main/res/values-zu/02-strings.xml b/AnkiDroid/src/main/res/values-zu/02-strings.xml index 1f3e0f61a620..a2153ab54a56 100644 --- a/AnkiDroid/src/main/res/values-zu/02-strings.xml +++ b/AnkiDroid/src/main/res/values-zu/02-strings.xml @@ -122,6 +122,7 @@ Reordering cards… %1$s (from “%2$s”) Any cards mapped to nothing will be deleted. If a note has no remaining cards, it will be lost. Are you sure you want to continue? + Timebox reached %1$d card studied in %2$s %1$d cards studied in %2$s diff --git a/AnkiDroid/src/main/res/values-zu/10-preferences.xml b/AnkiDroid/src/main/res/values-zu/10-preferences.xml index f39852e77b2b..ebe7317e58e5 100644 --- a/AnkiDroid/src/main/res/values-zu/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zu/10-preferences.xml @@ -292,6 +292,8 @@ In the Note Editor, convert any instances of <br> to newlines when editing a card. Simple typed answer formatting Fixes ‘􏿾’ appearing in typed answer results + Focus ‘type in answer’ + Automatically focus the ‘type in answer’ field in the reviewer Open Changelog From eb6363fd5257f9c24290a086ef17da94bdf0567b Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Thu, 8 Apr 2021 05:12:57 +0000 Subject: [PATCH 086/171] Bumped version to 2.15alpha40 @branch-specific --- AnkiDroid/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index c5348e753ab0..1b1b525b3930 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -50,8 +50,8 @@ 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=21500139 - versionName="2.15alpha39" + versionCode=21500140 + versionName="2.15alpha40" minSdkVersion 21 //noinspection OldTargetApi - also performed in api/build.fradle targetSdkVersion 29 // change .travis.yml platform download at same time From 54b39d2bbd9aba56ef1c84c9c6211b629e23b35c Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 8 Apr 2021 13:22:47 +0100 Subject: [PATCH 087/171] chore: Add CheckResult annotation --- AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java index 14f0f2175c26..42db4c5702de 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CollectionHelper.java @@ -21,6 +21,7 @@ import android.os.Environment; import android.text.format.Formatter; +import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -240,6 +241,7 @@ public static boolean isCurrentAnkiDroidDirAccessible(Context context) { * @return the folder path */ @SuppressWarnings("deprecation") // TODO Tracked in https://github.com/ankidroid/Anki-Android/issues/5304 + @CheckResult public static String getDefaultAnkiDroidDirectory() { return new File(Environment.getExternalStorageDirectory(), "AnkiDroid").getAbsolutePath(); } From 5542ad6ea5f212c04ae8dd93e19626832c25b11d Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 8 Apr 2021 15:25:44 +0100 Subject: [PATCH 088/171] chore: Fix usage of Throwable.printStackTrace We should be using Timber instead --- .../main/java/com/ichi2/anki/AbstractFlashcardViewer.java | 4 ++-- AnkiDroid/src/main/java/com/ichi2/anki/BackupManager.java | 2 +- AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java | 2 +- .../java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java | 2 +- .../java/com/ichi2/anki/provider/CardContentProvider.java | 2 +- .../main/java/com/ichi2/anki/servicelayer/NoteService.java | 2 +- .../main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java | 2 +- AnkiDroid/src/main/java/com/ichi2/async/Connection.java | 2 +- .../src/main/java/com/ichi2/libanki/AnkiPackageExporter.java | 4 ++-- AnkiDroid/src/main/java/com/ichi2/libanki/Utils.java | 5 ++--- 10 files changed, 13 insertions(+), 14 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index 35544dc01bd9..e95893d011ce 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -984,7 +984,7 @@ protected void onCollectionLoaded(Collection col) { String data = Utils.convertStreamToString(getAssets().open("card_template.html")); mCardTemplate = new CardTemplate(data); } catch (IOException e) { - e.printStackTrace(); + Timber.w(e); } // Initialize text-to-speech. This is an asynchronous operation. @@ -3634,7 +3634,7 @@ private boolean filterUrl(String url) { try { startActivityWithoutAnimation(intent); } catch (ActivityNotFoundException e) { - e.printStackTrace(); // Don't crash if the intent is not handled + Timber.w(e); // Don't crash if the intent is not handled } return true; } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/BackupManager.java b/AnkiDroid/src/main/java/com/ichi2/anki/BackupManager.java index 3941e2737947..e6be022807d5 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/BackupManager.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/BackupManager.java @@ -186,7 +186,7 @@ public void run() { } Timber.i("Backup created succesfully"); } catch (IOException e) { - e.printStackTrace(); + Timber.w(e); } } }; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java index 20b2c9190041..365175ad3f55 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java @@ -1317,7 +1317,7 @@ private void showChangeDeckDialog() { try { arrayAdapter.add(deck.getString("name")); } catch (JSONException e) { - e.printStackTrace(); + Timber.w(e); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java index ea94d5c170ac..44685b86894a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DatabaseErrorDialog.java @@ -76,7 +76,7 @@ public MaterialDialog onCreateDialog(Bundle savedInstanceState) { try { sqliteInstalled = Runtime.getRuntime().exec("sqlite3 --version").waitFor() == 0; } catch (IOException | InterruptedException e) { - e.printStackTrace(); + Timber.w(e); } switch (mType) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java b/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java index fc889989d623..5f840e6e16b0 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java @@ -356,7 +356,7 @@ public Cursor query(@NonNull Uri uri, String[] projection, String selection, Str } } } catch (NumberFormatException nfe) { - nfe.printStackTrace(); + Timber.w(nfe); } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/NoteService.java b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/NoteService.java index c3e31be65442..734fb92d5f3b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/NoteService.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/servicelayer/NoteService.java @@ -67,7 +67,7 @@ public static MultimediaEditableNote createEmptyNote(JSONObject model) { } } catch (JSONException e) { // TODO Auto-generated catch block - e.printStackTrace(); + Timber.w(e); } return null; } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java b/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java index b2eddf12c621..00b872f86c85 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java @@ -172,7 +172,7 @@ protected void onPostExecute(String html) { try { mWebView.loadData(URLEncoder.encode(html, "UTF-8").replaceAll("\\+", " "), "text/html; charset=utf-8", "utf-8"); } catch (UnsupportedEncodingException e) { - e.printStackTrace(); + Timber.w(e); } mProgressBar.setVisibility(View.GONE); int backgroundColor = Themes.getColorFromAttr(mWebView.getContext(), android.R.attr.colorBackground); diff --git a/AnkiDroid/src/main/java/com/ichi2/async/Connection.java b/AnkiDroid/src/main/java/com/ichi2/async/Connection.java index 1fdfdfc5de1a..e614fe090d78 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/Connection.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/Connection.java @@ -103,7 +103,7 @@ private static Connection launchConnectionTask(TaskListener listener, Payload da sInstance.get(); } } catch (Exception e) { - e.printStackTrace(); + Timber.w(e); } sInstance = new Connection(); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java b/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java index c58e8f542c86..2d09d5191d9e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java @@ -495,7 +495,7 @@ private JSONObject _exportMedia(ZipFile z, File[] files, ValidateFiles validateF media.put(Integer.toString(c), file.getName()); c++; } catch (JSONException e) { - e.printStackTrace(); + Timber.w(e); } } return media; @@ -608,7 +608,7 @@ public void close() { try { mZos.close(); } catch (IOException e) { - e.printStackTrace(); + Timber.w(e); } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.java index b8ddfecc1a8f..06f4784a4232 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.java @@ -568,7 +568,6 @@ public static String checksum(String data) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { Timber.e(e, "Utils.checksum :: UnsupportedEncodingException"); - e.printStackTrace(); } BigInteger biginteger = new BigInteger(1, digest); result = biginteger.toString(16); @@ -679,7 +678,7 @@ public static String convertStreamToString(InputStream is) { rd.close(); contentOfMyInputStream = sb.toString(); } catch (Exception e) { - e.printStackTrace(); + Timber.w(e); } return contentOfMyInputStream; @@ -803,7 +802,7 @@ public static void writeToFile(InputStream source, String destination) throws IO try { Thread.sleep(200); } catch (InterruptedException e1) { - e1.printStackTrace(); + Timber.w(e1); } } } From 89a7679839f55fa4748155ded6e73b33dc8fc545 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 8 Apr 2021 15:28:31 +0100 Subject: [PATCH 089/171] Add lint rule: PrintStackTraceUsage Speed up code review by ensuring that e.printStackTrace() isn't used Also includes AnkiDroid's first "Auto-Fix" for lint --- .../com/ichi2/anki/lint/IssueRegistry.java | 2 + .../anki/lint/rules/PrintStackTraceUsage.java | 92 +++++++++++++++++++ .../com/ichi2/anki/lint/utils/Constants.java | 16 ++++ .../lint/rules/PrintStackTraceUsageTest.java | 79 ++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 lint-rules/src/main/java/com/ichi2/anki/lint/rules/PrintStackTraceUsage.java create mode 100644 lint-rules/src/test/java/com/ichi2/anki/lint/rules/PrintStackTraceUsageTest.java diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java index fea1c70546f8..7bb0b1dc83c7 100644 --- a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java @@ -10,6 +10,7 @@ import com.ichi2.anki.lint.rules.DuplicateCrowdInStrings; import com.ichi2.anki.lint.rules.DuplicateTextInPreferencesXml; import com.ichi2.anki.lint.rules.InconsistentAnnotationUsage; +import com.ichi2.anki.lint.rules.PrintStackTraceUsage; import org.jetbrains.annotations.NotNull; @@ -29,6 +30,7 @@ public List getIssues() { issues.add(InconsistentAnnotationUsage.ISSUE); issues.add(DuplicateTextInPreferencesXml.ISSUE); issues.add(DuplicateCrowdInStrings.ISSUE); + issues.add(PrintStackTraceUsage.ISSUE); return issues; } diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/rules/PrintStackTraceUsage.java b/lint-rules/src/main/java/com/ichi2/anki/lint/rules/PrintStackTraceUsage.java new file mode 100644 index 000000000000..b0d3f450e142 --- /dev/null +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/rules/PrintStackTraceUsage.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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.lint.rules; + +import com.android.tools.lint.client.api.JavaEvaluator; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Implementation; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.JavaContext; +import com.android.tools.lint.detector.api.LintFix; +import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.SourceCodeScanner; +import com.google.common.annotations.VisibleForTesting; +import com.ichi2.anki.lint.utils.Constants; +import com.intellij.psi.PsiMethod; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.UCallExpression; + +import java.util.ArrayList; +import java.util.List; + +public class PrintStackTraceUsage extends Detector implements SourceCodeScanner { + + @VisibleForTesting + static final String ID = "PrintStackTraceUsage"; + @VisibleForTesting + static final String DESCRIPTION = "Use Timber to log exceptions (typically Timber.w if non-fatal)"; + private static final String EXPLANATION = "AnkiDroid exclusively uses Timber for logging exceptions. See: https://github.com/ankidroid/Anki-Android/wiki/Code-style#logging"; + private static final Implementation implementation = new Implementation(PrintStackTraceUsage.class, Scope.JAVA_FILE_SCOPE); + public static final Issue ISSUE = Issue.create( + ID, + DESCRIPTION, + EXPLANATION, + Constants.ANKI_CODE_STYLE_CATEGORY, + Constants.ANKI_CODE_STYLE_PRIORITY, + Constants.ANKI_CODE_STYLE_SEVERITY, + implementation + ); + + @Nullable + @Override + public List getApplicableMethodNames() { + List forbiddenMethods = new ArrayList<>(); + forbiddenMethods.add("printStackTrace"); + return forbiddenMethods; + } + + + @Override + public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) { + super.visitMethodCall(context, node, method); + JavaEvaluator evaluator = context.getEvaluator(); + + // if we have arguments, we're not writing to stdout, so it's an OK call + boolean hasArguments = node.getValueArgumentCount() != 0; + if (hasArguments || !evaluator.isMemberInSubClassOf(method, "java.lang.Throwable", false)) { + return; + } + + LintFix fix = LintFix.create() + .replace() + .select(node.asSourceString()) + // We don't need a semicolon here + .with("Timber.w(" + node.getReceiver().asSourceString() + ")") + .autoFix() + .build(); + + context.report( + ISSUE, + context.getCallLocation(node, true, true), + DESCRIPTION, + fix + ); + } + +} diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/utils/Constants.java b/lint-rules/src/main/java/com/ichi2/anki/lint/utils/Constants.java index 82c9f5d5fcea..a5e2e1d98984 100644 --- a/lint-rules/src/main/java/com/ichi2/anki/lint/utils/Constants.java +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/utils/Constants.java @@ -43,4 +43,20 @@ public class Constants { */ public static final Severity ANKI_CROWDIN_SEVERITY = Severity.FATAL; + /** + * A special {@link Category} which groups the Lint issues related to Code Style as a + * sub category for {@link Category#COMPLIANCE}. + */ + public static final Category ANKI_CODE_STYLE_CATEGORY = create(Category.COMPLIANCE, "CodeStyle", 10); + + /** + * The priority for the Lint issues used by rules related to Code Style. + */ + public static final int ANKI_CODE_STYLE_PRIORITY = 10; + + /** + * The severity for the Lint issues used by rules related to Code Style. + */ + public static final Severity ANKI_CODE_STYLE_SEVERITY = Severity.FATAL; + } diff --git a/lint-rules/src/test/java/com/ichi2/anki/lint/rules/PrintStackTraceUsageTest.java b/lint-rules/src/test/java/com/ichi2/anki/lint/rules/PrintStackTraceUsageTest.java new file mode 100644 index 000000000000..d75b46fea866 --- /dev/null +++ b/lint-rules/src/test/java/com/ichi2/anki/lint/rules/PrintStackTraceUsageTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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.lint.rules; + +import org.intellij.lang.annotations.Language; +import org.junit.Test; + +import static com.android.tools.lint.checks.infrastructure.TestFile.JavaTestFile.create; +import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint; +import static org.junit.Assert.assertTrue; + +public class PrintStackTraceUsageTest { + + @Language("JAVA") + private static final String printStackTraceUsage = + "import java.io.IOException; \n" + + "public class Test { \n" + + " public Test() { \n" + + " try { \n" + + " } catch (IOException ex) { \n" + + " ex.printStackTrace(); \n" + + " } \n" + + " } \n" + + "}"; + + @Language("JAVA") + private static final String printStackTraceWithMethodArgument = + "import java.io.IOException; \n" + + "import java.io.PrintWriter; \n" + + "public class Test { \n" + + " public Test() { \n" + + " try { \n" + + " } catch (IOException ex) { \n" + + " ex.printStackTrace(new PrintWriter(sw)); \n" + + " } \n" + + " } \n" + + "}"; + + + + @Test + public void showsErrorForUsageWithNoParam() { + lint() + .allowMissingSdk() + .files(create(printStackTraceUsage)) + .issues(PrintStackTraceUsage.ISSUE) + .run() + .expectErrorCount(1) + .check(output -> { + assertTrue(output.contains(PrintStackTraceUsage.ID)); + }); + } + + @Test + public void noErrorIfParamUsage() { + // .check() is not required for the code to execute + // If we have a parameter, we're not writing to stdout, so it's OK + lint() + .allowMissingSdk() + .files(create(printStackTraceWithMethodArgument)) + .issues(PrintStackTraceUsage.ISSUE) + .run() + .expectErrorCount(0); + } +} From 1688ee6a297fbc9e12101906433eb8a3ff883b55 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Mon, 5 Apr 2021 18:13:26 +0200 Subject: [PATCH 090/171] Created ExtendedFragmentFactory - A factory class that enables extending another factory. - will be very useful when designing modular FragmentFactory that can extend/chain to one another. --- .../ichi2/utils/ExtendedFragmentFactory.java | 83 +++++++++++++++++++ .../utils/ExtendedFragmentFactoryTest.java | 78 +++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 AnkiDroid/src/main/java/com/ichi2/utils/ExtendedFragmentFactory.java create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/ExtendedFragmentFactoryTest.java diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/ExtendedFragmentFactory.java b/AnkiDroid/src/main/java/com/ichi2/utils/ExtendedFragmentFactory.java new file mode 100644 index 000000000000..e71e337e94f1 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/utils/ExtendedFragmentFactory.java @@ -0,0 +1,83 @@ +/* + Copyright (c) 2021 Tarek Mohamed Abdalla + + 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.utils; + +import com.ichi2.libanki.Collection; + +import androidx.annotation.CallSuper; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentFactory; +import androidx.fragment.app.FragmentManager; + +/** + * A factory that enable extending another {@link FragmentFactory}. + * + * This should be useful if you want to add extra instantiations without overriding the instantiations in an old factory + */ +public abstract class ExtendedFragmentFactory extends FragmentFactory { + + @Nullable + private FragmentFactory mBaseFactory; + + + /** + * Create an extended factory from a base factory + */ + public ExtendedFragmentFactory(@NonNull FragmentFactory baseFactory) { + mBaseFactory = baseFactory; + } + + + /** + * Create a factory with no base, you can assign a base factory later using {@link #setBaseFactory(FragmentFactory)} + */ + public ExtendedFragmentFactory() {} + + + /** + * Typically you want to return the result of a super call as the last result, so if the passed class couldn't be + * instantiated by the extending factory, the base factory should instantiate it. + */ + @NonNull + @CallSuper + @Override + public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { + return mBaseFactory != null ? mBaseFactory.instantiate(classLoader, className) : super.instantiate(classLoader, className); + } + + + /** + * Sets a base factory to be used as a fallback + */ + public void setBaseFactory(@Nullable FragmentFactory baseFactory) { + this.mBaseFactory = baseFactory; + } + + + /** + * Attaches the factory to an activity by setting the current activity fragment factory as the base factory + * and updating the activity with the extended factory + */ + public F attachToActivity(@NonNull AppCompatActivity activity) { + final FragmentManager fm = activity.getSupportFragmentManager(); + mBaseFactory = fm.getFragmentFactory(); + fm.setFragmentFactory(this); + return (F) this; + } +} diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/ExtendedFragmentFactoryTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/ExtendedFragmentFactoryTest.java new file mode 100644 index 000000000000..e4b189e183ee --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/ExtendedFragmentFactoryTest.java @@ -0,0 +1,78 @@ +/* + Copyright (c) 2021 Tarek Mohamed Abdalla + + 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.utils; + +import org.junit.Test; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentFactory; +import androidx.fragment.app.FragmentManager; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ExtendedFragmentFactoryTest { + + + private static final ClassLoader fakeClassLoader = mock(ClassLoader.class); + private static final String fakeClassName = "FAKE CLASS NAME"; + + static class TestFragmentFactoryTest extends ExtendedFragmentFactory { + public TestFragmentFactoryTest() { + } + public TestFragmentFactoryTest(@NonNull FragmentFactory baseFactory) { + super(baseFactory); + } + } + + + @Test + public void willCallBaseFactory() { + final FragmentFactory baseFF = mock(FragmentFactory.class); + final ExtendedFragmentFactory testFF = new TestFragmentFactoryTest(baseFF); + + testFF.instantiate(fakeClassLoader, fakeClassName); + + verify(baseFF, times(1)).instantiate(fakeClassLoader, fakeClassName); + } + + + @Test + public void testAttachToActivity() { + final AppCompatActivity activity = mock(AppCompatActivity.class); + final FragmentManager fragmentManager = mock(FragmentManager.class); + final FragmentFactory baseFactory = mock(FragmentFactory.class); + + when(activity.getSupportFragmentManager()).thenReturn(fragmentManager); + when(fragmentManager.getFragmentFactory()).thenReturn(baseFactory); + + final ExtendedFragmentFactory testFF = new TestFragmentFactoryTest(); + + final ExtendedFragmentFactory result = testFF.attachToActivity(activity); + + assertEquals(testFF, result); + + verify(fragmentManager, times(1)).setFragmentFactory(testFF); + + testFF.instantiate(fakeClassLoader, fakeClassName); + + verify(baseFactory, times(1)).instantiate(fakeClassLoader, fakeClassName); + } +} \ No newline at end of file From 7d20f7118c86a5e2215942e4661ab64de25fc639 Mon Sep 17 00:00:00 2001 From: Tanmay Chakraborty <75254780+tanmayChakrawarty@users.noreply.github.com> Date: Fri, 9 Apr 2021 02:25:35 +0530 Subject: [PATCH 091/171] use official icons for community menu entries (#8519) * improved the icons of community menu * deleted unused xml files * added the licence in each xml file --- .../com/ichi2/anki/dialogs/HelpDialog.java | 8 ++--- AnkiDroid/src/main/res/drawable/discord.xml | 32 +++++++++++++++++++ AnkiDroid/src/main/res/drawable/facebook.xml | 32 +++++++++++++++++++ .../main/res/drawable/ic_link_black_24dp.xml | 10 ------ .../drawable/ic_mail_outline_black_24dp.xml | 10 ------ .../res/drawable/ic_message_black_24dp.xml | 10 ------ AnkiDroid/src/main/res/drawable/reddit.xml | 32 +++++++++++++++++++ AnkiDroid/src/main/res/drawable/twitter.xml | 32 +++++++++++++++++++ 8 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 AnkiDroid/src/main/res/drawable/discord.xml create mode 100644 AnkiDroid/src/main/res/drawable/facebook.xml delete mode 100644 AnkiDroid/src/main/res/drawable/ic_link_black_24dp.xml delete mode 100644 AnkiDroid/src/main/res/drawable/ic_mail_outline_black_24dp.xml delete mode 100644 AnkiDroid/src/main/res/drawable/ic_message_black_24dp.xml create mode 100644 AnkiDroid/src/main/res/drawable/reddit.xml create mode 100644 AnkiDroid/src/main/res/drawable/twitter.xml diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java index 6c25c1f37c94..0a73906c0ec6 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java @@ -73,11 +73,11 @@ public static DialogFragment createInstance(Context context) { ), new ItemHeader(R.string.help_title_community, R.drawable.ic_people_black_24dp, UsageAnalytics.Actions.OPENED_COMMUNITY, new LinkItem(R.string.help_item_anki_forums, R.drawable.ic_forum_black_24dp, UsageAnalytics.Actions.OPENED_ANKI_FORUMS, R.string.link_anki_forum), - new LinkItem(R.string.help_item_reddit, R.drawable.ic_mail_outline_black_24dp, UsageAnalytics.Actions.OPENED_REDDIT, R.string.link_reddit), + new LinkItem(R.string.help_item_reddit, R.drawable.reddit, UsageAnalytics.Actions.OPENED_REDDIT, R.string.link_reddit), new LinkItem(R.string.help_item_mailing_list, R.drawable.ic_email_black_24dp, UsageAnalytics.Actions.OPENED_MAILING_LIST, R.string.link_forum), - new LinkItem(R.string.help_item_discord, R.drawable.ic_message_black_24dp, UsageAnalytics.Actions.OPENED_DISCORD, R.string.link_discord), - new LinkItem(R.string.help_item_facebook, R.drawable.ic_link_black_24dp, UsageAnalytics.Actions.OPENED_FACEBOOK, R.string.link_facebook), - new LinkItem(R.string.help_item_twitter, R.drawable.ic_link_black_24dp, UsageAnalytics.Actions.OPENED_TWITTER, R.string.link_twitter) + new LinkItem(R.string.help_item_discord, R.drawable.discord, UsageAnalytics.Actions.OPENED_DISCORD, R.string.link_discord), + new LinkItem(R.string.help_item_facebook, R.drawable.facebook, UsageAnalytics.Actions.OPENED_FACEBOOK, R.string.link_facebook), + new LinkItem(R.string.help_item_twitter, R.drawable.twitter, UsageAnalytics.Actions.OPENED_TWITTER, R.string.link_twitter) ) }; diff --git a/AnkiDroid/src/main/res/drawable/discord.xml b/AnkiDroid/src/main/res/drawable/discord.xml new file mode 100644 index 000000000000..9c6cbda00dd6 --- /dev/null +++ b/AnkiDroid/src/main/res/drawable/discord.xml @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file diff --git a/AnkiDroid/src/main/res/drawable/facebook.xml b/AnkiDroid/src/main/res/drawable/facebook.xml new file mode 100644 index 000000000000..7dedaa4119c6 --- /dev/null +++ b/AnkiDroid/src/main/res/drawable/facebook.xml @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file diff --git a/AnkiDroid/src/main/res/drawable/ic_link_black_24dp.xml b/AnkiDroid/src/main/res/drawable/ic_link_black_24dp.xml deleted file mode 100644 index e4cc1f840529..000000000000 --- a/AnkiDroid/src/main/res/drawable/ic_link_black_24dp.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/AnkiDroid/src/main/res/drawable/ic_mail_outline_black_24dp.xml b/AnkiDroid/src/main/res/drawable/ic_mail_outline_black_24dp.xml deleted file mode 100644 index 61089b338088..000000000000 --- a/AnkiDroid/src/main/res/drawable/ic_mail_outline_black_24dp.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/AnkiDroid/src/main/res/drawable/ic_message_black_24dp.xml b/AnkiDroid/src/main/res/drawable/ic_message_black_24dp.xml deleted file mode 100644 index cc68d8ea858b..000000000000 --- a/AnkiDroid/src/main/res/drawable/ic_message_black_24dp.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/AnkiDroid/src/main/res/drawable/reddit.xml b/AnkiDroid/src/main/res/drawable/reddit.xml new file mode 100644 index 000000000000..4054f69f2e10 --- /dev/null +++ b/AnkiDroid/src/main/res/drawable/reddit.xml @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file diff --git a/AnkiDroid/src/main/res/drawable/twitter.xml b/AnkiDroid/src/main/res/drawable/twitter.xml new file mode 100644 index 000000000000..a88740dfd200 --- /dev/null +++ b/AnkiDroid/src/main/res/drawable/twitter.xml @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file From a299058a22c327cd14091b7d95a2e7b5123cd43c Mon Sep 17 00:00:00 2001 From: Piyush Goel <46752548+Arnold2381@users.noreply.github.com> Date: Fri, 9 Apr 2021 18:41:54 +0530 Subject: [PATCH 092/171] use 'm' prefix for non-public, non-static fields #8387 (#8521) * Changed variables names according to the new lint rule. ("Lint: Enforce 'm' prefix rule for fields") --- .../java/com/ichi2/anki/AnkiDroidApp.java | 8 +- .../main/java/com/ichi2/anki/DeckOptions.java | 8 +- .../main/java/com/ichi2/anki/DeckPicker.java | 54 ++++++------ .../com/ichi2/anki/FilteredDeckOptions.java | 5 +- .../java/com/ichi2/anki/ModelBrowser.java | 12 +-- .../com/ichi2/anki/dialogs/HelpDialog.java | 12 +-- .../com/ichi2/anki/dialogs/IntegerDialog.java | 6 +- .../anki/dialogs/LocaleSelectionDialog.java | 12 +-- .../activity/LoadPronounciationActivity.java | 22 ++--- .../multimediacard/glosbe/json/Meaning.java | 12 +-- .../multimediacard/glosbe/json/Phrase.java | 12 +-- .../com/ichi2/anki/noteeditor/FieldState.java | 22 ++--- .../anki/widgets/DeckDropDownAdapter.java | 26 +++--- .../main/java/com/ichi2/async/Connection.java | 8 +- .../ichi2/libanki/importer/NoteImporter.java | 14 +-- .../ichi2/libanki/importer/TextImporter.java | 70 +++++++-------- .../libanki/importer/python/CsvDialect.java | 3 + .../libanki/importer/python/CsvReader.java | 3 + .../importer/python/CsvReaderIterator.java | 3 + .../libanki/importer/python/CsvSniffer.java | 3 + .../libanki/sync/CountingFileRequestBody.java | 20 ++--- .../com/ichi2/ui/ConfirmationPreference.java | 12 +-- .../ichi2/utils/DatabaseChangeDecorator.java | 86 +++++++++---------- .../main/java/com/ichi2/utils/DiffEngine.java | 4 +- .../main/java/com/ichi2/utils/JSONArray.java | 32 +++---- .../java/com/ichi2/utils/JSONException.java | 8 +- 26 files changed, 243 insertions(+), 234 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index f5715492571f..0a92afb03aff 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -183,7 +183,7 @@ public class AnkiDroidApp extends Application { public static final int CHECK_PREFERENCES_AT_VERSION = 20500225; /** Our ACRA configurations, initialized during onCreate() */ - private CoreConfigurationBuilder acraCoreConfigBuilder; + private CoreConfigurationBuilder mAcraCoreConfigBuilder; /** An exception if the WebView subsystem fails to load */ @Nullable @@ -224,7 +224,7 @@ public static boolean isAcraEnbled(Context context, boolean defaultValue) { * @return ConfigurationBuilder for the current ACRA config */ public CoreConfigurationBuilder getAcraCoreConfigBuilder() { - return acraCoreConfigBuilder; + return mAcraCoreConfigBuilder; } @@ -233,7 +233,7 @@ public CoreConfigurationBuilder getAcraCoreConfigBuilder() { * @param acraCoreConfigBuilder the full ACRA config to initialize ACRA with */ private void setAcraConfigBuilder(CoreConfigurationBuilder acraCoreConfigBuilder) { - this.acraCoreConfigBuilder = acraCoreConfigBuilder; + this.mAcraCoreConfigBuilder = acraCoreConfigBuilder; ACRA.init(this, acraCoreConfigBuilder); ACRA.getErrorReporter().putCustomData("WEBVIEW_VER_NAME", fetchWebViewInformation().get("WEBVIEW_VER_NAME")); ACRA.getErrorReporter().putCustomData("WEBVIEW_VER_CODE", fetchWebViewInformation().get("WEBVIEW_VER_CODE")); @@ -270,7 +270,7 @@ public void onCreate() { SharedPreferences preferences = getSharedPrefs(this); // Setup logging and crash reporting - acraCoreConfigBuilder = new CoreConfigurationBuilder(this); + mAcraCoreConfigBuilder = new CoreConfigurationBuilder(this); if (BuildConfig.DEBUG) { // Enable verbose error logging and do method tracing to put the Class name as log tag Timber.plant(new DebugTree()); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java index 324ff9ad2a88..a500965c7a64 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckOptions.java @@ -93,7 +93,7 @@ public class DeckPreferenceHack implements SharedPreferences { private final Map mValues = new HashMap<>(30); // At most as many as in cacheValues private final Map mSummaries = new HashMap<>(); private MaterialDialog mProgressDialog; - private final List listeners = new LinkedList<>(); + private final List mListeners = new LinkedList<>(); private DeckPreferenceHack() { @@ -456,7 +456,7 @@ public boolean commit() { updateSummaries(); // and update any listeners - for (OnSharedPreferenceChangeListener listener : listeners) { + for (OnSharedPreferenceChangeListener listener : mListeners) { listener.onSharedPreferenceChanged(DeckPreferenceHack.this, null); } @@ -591,13 +591,13 @@ public String getString(String key, String defValue) { @Override public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { - listeners.add(listener); + mListeners.add(listener); } @Override public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { - listeners.remove(listener); + mListeners.remove(listener); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 6656d8dcc6ea..13c7dd29b5d5 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -1397,11 +1397,11 @@ private UndoTaskListener undoTaskListener(boolean isReview) { return new UndoTaskListener(isReview, this); } private static class UndoTaskListener extends TaskListenerWithContext { - private final boolean isReview; + private final boolean mIsreview; public UndoTaskListener(boolean isReview, DeckPicker deckPicker) { super(deckPicker); - this.isReview = isReview; + this.mIsreview = isReview; } @@ -1421,7 +1421,7 @@ public void actualOnPreExecute(@NonNull DeckPicker deckPicker) { public void actualOnPostExecute(@NonNull DeckPicker deckPicker, BooleanGetter voi) { deckPicker.hideProgressBar(); Timber.i("Undo completed"); - if (isReview) { + if (mIsreview) { Timber.i("Review undone - opening reviewer."); deckPicker.openReviewer(); } @@ -1732,10 +1732,10 @@ public void sync(Connection.ConflictResolution syncConflictResolution) { private final Connection.TaskListener mSyncListener = new Connection.CancellableTaskListener() { - private String currentMessage; - private long countUp; - private long countDown; - private boolean dialogDisplayFailure = false; + private String mCurrentMessage; + private long mCountUp; + private long mCountDown; + private boolean mDialogDisplayFailure = false; @Override public void onDisconnected() { @@ -1745,19 +1745,19 @@ public void onDisconnected() { @Override public void onCancelled() { showSyncLogMessage(R.string.sync_cancelled, ""); - if (!dialogDisplayFailure) { + if (!mDialogDisplayFailure) { mProgressDialog.dismiss(); // update deck list in case sync was cancelled during media sync and main sync was actually successful updateDeckList(); } // reset our display failure fate, just in case it is re-used - dialogDisplayFailure = false; + mDialogDisplayFailure = false; } @Override public void onPreExecute() { - countUp = 0; - countDown = 0; + mCountUp = 0; + mCountDown = 0; final long syncStartTime = getCol().getTime().intTimeMS(); if (mProgressDialog == null || !mProgressDialog.isShowing()) { @@ -1765,12 +1765,12 @@ public void onPreExecute() { mProgressDialog = StyledProgressDialog .show(DeckPicker.this, getResources().getString(R.string.sync_title), getResources().getString(R.string.sync_title) + "\n" - + getResources().getString(R.string.sync_up_down_size, countUp, countDown), + + getResources().getString(R.string.sync_up_down_size, mCountUp, mCountDown), false); } catch (WindowManager.BadTokenException e) { // If we could not show the progress dialog to start even, bail out - user will get a message Timber.w(e, "Unable to display Sync progress dialog, Activity not valid?"); - dialogDisplayFailure = true; + mDialogDisplayFailure = true; Connection.cancel(); return; } @@ -1827,24 +1827,24 @@ public void onProgressUpdate(Object... values) { } else if (values[0] instanceof Integer) { int id = (Integer) values[0]; if (id != 0) { - currentMessage = res.getString(id); + mCurrentMessage = res.getString(id); } if (values.length >= 3) { - countUp = (Long) values[1]; - countDown = (Long) values[2]; + mCountUp = (Long) values[1]; + mCountDown = (Long) values[2]; } } else if (values[0] instanceof String) { - currentMessage = (String) values[0]; + mCurrentMessage = (String) values[0]; if (values.length >= 3) { - countUp = (Long) values[1]; - countDown = (Long) values[2]; + mCountUp = (Long) values[1]; + mCountDown = (Long) values[2]; } } if (mProgressDialog != null && mProgressDialog.isShowing()) { // mProgressDialog.setTitle((String) values[0]); - mProgressDialog.setContent(currentMessage + "\n" + mProgressDialog.setContent(mCurrentMessage + "\n" + res - .getString(R.string.sync_up_down_size, countUp / 1024, countDown / 1024)); + .getString(R.string.sync_up_down_size, mCountUp / 1024, mCountDown / 1024)); } } @@ -2698,13 +2698,13 @@ private DeleteDeckListener deleteDeckListener(long did) { return new DeleteDeckListener(did, this); } private static class DeleteDeckListener extends TaskListenerWithContext{ - private final long did; + private final long mDid; // Flag to indicate if the deck being deleted is the current deck. - private boolean removingCurrent; + private boolean mRemovingCurrent; public DeleteDeckListener(long did, DeckPicker deckPicker) { super(deckPicker); - this.did = did; + this.mDid = did; } @@ -2712,8 +2712,8 @@ public DeleteDeckListener(long did, DeckPicker deckPicker) { public void actualOnPreExecute(@NonNull DeckPicker deckPicker) { deckPicker.mProgressDialog = StyledProgressDialog.show(deckPicker, null, deckPicker.getResources().getString(R.string.delete_deck), false); - if (did == deckPicker.getCol().getDecks().current().optLong("id")) { - removingCurrent = true; + if (mDid == deckPicker.getCol().getDecks().current().optLong("id")) { + mRemovingCurrent = true; } } @@ -2723,7 +2723,7 @@ public void actualOnPostExecute(@NonNull DeckPicker deckPicker, @Nullable int[] // In fragmented mode, if the deleted deck was the current deck, we need to reload // the study options fragment with a valid deck and re-center the deck list to the // new current deck. Otherwise we just update the list normally. - if (deckPicker.mFragmented && removingCurrent) { + if (deckPicker.mFragmented && mRemovingCurrent) { deckPicker.updateDeckList(); deckPicker.openStudyOptions(false); } else { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/FilteredDeckOptions.java b/AnkiDroid/src/main/java/com/ichi2/anki/FilteredDeckOptions.java index 5f77bba4c7f6..fe5120b95d4a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/FilteredDeckOptions.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/FilteredDeckOptions.java @@ -52,7 +52,6 @@ import timber.log.Timber; import static com.ichi2.anim.ActivityTransitionAnimation.Direction.FADE; -import static com.ichi2.libanki.Consts.DECK_STD; /** * Preferences for the current deck. @@ -67,7 +66,7 @@ public class FilteredDeckOptions extends AppCompatPreferenceActivity implements private BroadcastReceiver mUnmountReceiver = null; // TODO: not anymore used in libanki? - private final String[] dynExamples = new String[] { null, + private final String[] mDynExamples = new String[] { null, "{'search'=\"is:new\", 'resched'=False, 'steps'=\"1\", 'order'=5}", "{'search'=\"added:1\", 'resched'=False, 'steps'=\"1\", 'order'=5}", "{'search'=\"rated:1:1\", 'order'=4}", @@ -150,7 +149,7 @@ public boolean commit() { } else if ("preset".equals(entry.getKey())) { int i = Integer.parseInt((String) entry.getValue()); if (i > 0) { - JSONObject presetValues = new JSONObject(dynExamples[i]); + JSONObject presetValues = new JSONObject(mDynExamples[i]); JSONArray ar = presetValues.names(); for (String name: ar.stringIterable()) { if ("steps".equals(name)) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java index 3297933743f0..2f2a5caf8c68 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java @@ -557,20 +557,20 @@ private void showToast(CharSequence text) { * along with the name. */ public static class DisplayPair { - private final String name; - private final int count; + private final String mName; + private final int mCount; public DisplayPair(String name, int count) { - this.name = name; - this.count = count; + this.mName = name; + this.mCount = count; } public String getName() { - return name; + return mName; } public int getCount() { - return count; + return mCount; } @Override diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java index 0a73906c0ec6..bb4a7ce77198 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/HelpDialog.java @@ -244,9 +244,9 @@ public interface ActivityConsumer extends Serializable { private static class ExceptionReportItem extends Item implements Parcelable { private static Long lastClickStamp; - final long currentTimestamp = SystemClock.uptimeMillis(); - final int minIntervalMS = 60000; - final String exceptionMessage = "Exception report sent by user manually"; + final long mCurrentTimestamp = SystemClock.uptimeMillis(); + final int mMinIntervalMS = 60000; + final String mExceptionMessage = "Exception report sent by user manually"; public ExceptionReportItem(@StringRes int titleRes, @DrawableRes int iconRes, String analyticsRes) { super(titleRes, iconRes, analyticsRes); @@ -278,12 +278,12 @@ protected void onClicked(AnkiActivity activity) { } private void sendReport(AnkiActivity activity) { - if (lastClickStamp == null || currentTimestamp - lastClickStamp > minIntervalMS) { + if (lastClickStamp == null || mCurrentTimestamp - lastClickStamp > mMinIntervalMS) { AnkiDroidApp.deleteACRALimiterData(activity); AnkiDroidApp.sendExceptionReport( - new UserSubmittedException(exceptionMessage), + new UserSubmittedException(mExceptionMessage), "AnkiDroidApp.HelpDialog"); - lastClickStamp = currentTimestamp; + lastClickStamp = mCurrentTimestamp; } else { UIUtils.showThemedToast(activity, activity.getString(R.string.help_dialog_exception_report_debounce), true); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.java index 86742dc72d63..96853bc0016d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/IntegerDialog.java @@ -14,10 +14,10 @@ public class IntegerDialog extends AnalyticsDialogFragment { - private Consumer consumer; + private Consumer mConsumer; public void setCallbackRunnable(Consumer consumer) { - this.consumer = consumer; + this.mConsumer = consumer; } public void setArgs(String title, String prompt, int digits) { @@ -43,7 +43,7 @@ public void setArgs(String title, String prompt, int digits, @Nullable String co .inputType(InputType.TYPE_CLASS_NUMBER) .inputRange(1, getArguments().getInt("digits")) .input(getArguments().getString("prompt"), "", - (dialog, text) -> consumer.consume(Integer.parseInt(text.toString()))); + (dialog, text) -> mConsumer.consume(Integer.parseInt(text.toString()))); //builder.content's argument is marked as @NotNull //We can't use "" as that creates padding, and want to respect the contract, so only set if not null String content = getArguments().getString("content"); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java index de6edc4d43db..601a1e607f6b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java @@ -54,7 +54,7 @@ /** Locale selection dialog. Note: this must be dismissed onDestroy if not called from an activity implementing LocaleSelectionDialogHandler */ public class LocaleSelectionDialog extends AnalyticsDialogFragment { - private LocaleSelectionDialogHandler dialogHandler; + private LocaleSelectionDialogHandler mDialogHandler; public interface LocaleSelectionDialogHandler { void onSelectedLocale(@NonNull Locale selectedLocale); @@ -70,7 +70,7 @@ public interface LocaleSelectionDialogHandler { @NonNull public static LocaleSelectionDialog newInstance(@NonNull LocaleSelectionDialogHandler handler) { LocaleSelectionDialog t = new LocaleSelectionDialog(); - t.dialogHandler = handler; + t.mDialogHandler = handler; Bundle args = new Bundle(); t.setArguments(args); @@ -90,11 +90,11 @@ public void onAttach(@NonNull Context context) { super.onAttach(context); if (context instanceof Activity) { Activity activity = (Activity) context; - if (dialogHandler == null) { + if (mDialogHandler == null) { if (!(context instanceof LocaleSelectionDialogHandler)) { throw new IllegalArgumentException("Calling activity must implement LocaleSelectionDialogHandler"); } - this.dialogHandler = (LocaleSelectionDialogHandler) context; + this.mDialogHandler = (LocaleSelectionDialogHandler) context; } activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } @@ -118,7 +118,7 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { MaterialDialog.Builder builder = new MaterialDialog.Builder(activity) .negativeText(getString(R.string.dialog_cancel)) .customView(tagsDialogView, false) - .onNegative((dialog, which) -> dialogHandler.onLocaleSelectionCancelled()); + .onNegative((dialog, which) -> mDialogHandler.onLocaleSelectionCancelled()); Dialog mDialog = builder.build(); @@ -141,7 +141,7 @@ private void setupRecyclerView(@NonNull Activity activity, @NonNull View tagsDia recyclerView.addOnItemTouchListener(new RecyclerSingleTouchAdapter(activity, (view, position) -> { Locale l = adapter.getLocaleAtPosition(position); - LocaleSelectionDialog.this.dialogHandler.onSelectedLocale(l); + LocaleSelectionDialog.this.mDialogHandler.onSelectedLocale(l); })); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java index 8a7fdf29b4b6..913fff8846b7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java @@ -68,7 +68,7 @@ public class LoadPronounciationActivity extends Activity implements OnCancelList private String mTranslationAddress; @SuppressWarnings("deprecation") // tracked in github as #5020 - private android.app.ProgressDialog progressDialog = null; + private android.app.ProgressDialog mProgressDialog = null; private String mPronunciationAddress; @@ -158,7 +158,7 @@ protected void onLoadPronunciation(View v) { mPostTranslation.execute(); } catch (Exception e) { Timber.w(e); - progressDialog.dismiss(); + mProgressDialog.dismiss(); showToast(gtxt(R.string.multimedia_editor_something_wrong)); } } @@ -169,10 +169,10 @@ private void showProgressDialog(String message) { dismissCarefullyProgressDialog(); - progressDialog = android.app.ProgressDialog.show(this, gtxt(R.string.multimedia_editor_progress_wait_title), message, true, + mProgressDialog = android.app.ProgressDialog.show(this, gtxt(R.string.multimedia_editor_progress_wait_title), message, true, false); - progressDialog.setCancelable(true); - progressDialog.setOnCancelListener(this); + mProgressDialog.setCancelable(true); + mProgressDialog.setOnCancelListener(this); } /** @@ -291,7 +291,7 @@ protected void processPostFinished(BackgroundPost post, String result) { mPostPronunciation.execute(); } catch (Exception e) { Timber.w(e); - progressDialog.dismiss(); + mProgressDialog.dismiss(); showToast(gtxt(R.string.multimedia_editor_something_wrong)); } @@ -320,7 +320,7 @@ protected void processPostFinished(BackgroundPost post, String result) { mDownloadMp3Task.execute(); } catch (Exception e) { Timber.w(e); - progressDialog.dismiss(); + mProgressDialog.dismiss(); showToast(gtxt(R.string.multimedia_editor_something_wrong)); } } @@ -343,7 +343,7 @@ public void receiveMp3File(String result) { return; } - progressDialog.dismiss(); + mProgressDialog.dismiss(); showToast(gtxt(R.string.multimedia_editor_general_done)); Intent resultData = new Intent(); resultData.putExtra(EXTRA_PRONUNCIATION_FILE_PATH, result); @@ -367,7 +367,7 @@ private void failNoPronunciation() { private void stop(String string) { - progressDialog.dismiss(); + mProgressDialog.dismiss(); showToast(string); } @@ -429,8 +429,8 @@ public void onCancel(DialogInterface dialog) { private void dismissCarefullyProgressDialog() { try { - if ((progressDialog != null) && progressDialog.isShowing()) { - progressDialog.dismiss(); + if ((mProgressDialog != null) && mProgressDialog.isShowing()) { + mProgressDialog.dismiss(); } } catch (Exception e) { Timber.w(e); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Meaning.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Meaning.java index 3a57dd7eaa80..86de0f436fd7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Meaning.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Meaning.java @@ -23,26 +23,26 @@ * This is one of the classes, automatically generated to transform json replies from glosbe.com */ public class Meaning { - private String language; - private String text; + private String mLanguage; + private String mText; public String getLanguage() { - return this.language; + return this.mLanguage; } public void setLanguage(String language) { - this.language = language; + this.mLanguage = language; } public String getText() { - return this.text; + return this.mText; } public void setText(String text) { - this.text = text; + this.mText = text; } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Phrase.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Phrase.java index 83bfff5b0da6..0b4257c84eb2 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Phrase.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Phrase.java @@ -23,26 +23,26 @@ * This is one of the classes, automatically generated to transform json replies from glosbe.com */ public class Phrase { - private String language; - private String text; + private String mLanguage; + private String mText; public String getLanguage() { - return this.language; + return this.mLanguage; } public void setLanguage(String l) { - this.language = l; + this.mLanguage = l; } public String getText() { - return this.text; + return this.mText; } public void setText(String text) { - this.text = text; + this.mText = text; } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.java b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.java index 7f22d92a8886..b113a4f07d90 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/noteeditor/FieldState.java @@ -82,7 +82,7 @@ public List loadFieldEditLines(FieldChangeType type) { JSONArray flds = mEditor.getCurrentFields(); for (int fldIdx = 0; fldIdx < flds.length(); fldIdx++) { if (flds.getJSONObject(fldIdx).getBoolean("sticky")) { - fieldEditLines.get(fldIdx).setContent(currentFieldStrings[fldIdx], type.replaceNewlines); + fieldEditLines.get(fldIdx).setContent(currentFieldStrings[fldIdx], type.mReplaceNewlines); } } } @@ -91,7 +91,7 @@ public List loadFieldEditLines(FieldChangeType type) { String[] currentFieldStrings = mEditor.getCurrentFieldStrings(); for (int i = 0; i < Math.min(currentFieldStrings.length, fieldEditLines.size()); i++) { - fieldEditLines.get(i).setContent(currentFieldStrings[i], type.replaceNewlines); + fieldEditLines.get(i).setContent(currentFieldStrings[i], type.mReplaceNewlines); } } @@ -123,7 +123,7 @@ protected List createFields(FieldChangeType type) { FieldEditLine edit_line_view = new FieldEditLine(mEditor); editLines.add(edit_line_view); edit_line_view.setName(fields[i][0]); - edit_line_view.setContent(fields[i][1], type.replaceNewlines); + edit_line_view.setContent(fields[i][1], type.mReplaceNewlines); edit_line_view.setOrd(i); } return editLines; @@ -133,8 +133,8 @@ protected List createFields(FieldChangeType type) { private String[][] getFields(FieldChangeType type) { if (type.mType == Type.REFRESH_WITH_MAP) { String[][] items = mEditor.getFieldsFromSelectedNote(); - Map> fMapNew = Models.fieldMap(type.newModel); - return FieldState.fromFieldMap(mEditor, items, fMapNew, type.modelChangeFieldMap); + Map> fMapNew = Models.fieldMap(type.mNewModel); + return FieldState.fromFieldMap(mEditor, items, fMapNew, type.mModelChangeFieldMap); } return mEditor.getFieldsFromSelectedNote(); } @@ -212,19 +212,19 @@ public void setInstanceState(Bundle savedInstanceState) { public static class FieldChangeType { private final Type mType; - private Map modelChangeFieldMap; - private Model newModel; - private final boolean replaceNewlines; + private Map mModelChangeFieldMap; + private Model mNewModel; + private final boolean mReplaceNewlines; public FieldChangeType(Type type, boolean replaceNewlines) { this.mType = type; - this.replaceNewlines = replaceNewlines; + this.mReplaceNewlines = replaceNewlines; } public static FieldChangeType refreshWithMap(Model newModel, Map modelChangeFieldMap, boolean replaceNewlines) { FieldChangeType typeClass = new FieldChangeType(Type.REFRESH_WITH_MAP, replaceNewlines); - typeClass.newModel = newModel; - typeClass.modelChangeFieldMap = modelChangeFieldMap; + typeClass.mNewModel = newModel; + typeClass.mModelChangeFieldMap = modelChangeFieldMap; return typeClass; } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckDropDownAdapter.java b/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckDropDownAdapter.java index f2008eb12dc3..e4441b0d83c4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckDropDownAdapter.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/widgets/DeckDropDownAdapter.java @@ -21,12 +21,12 @@ public interface SubtitleListener { String getSubtitleText(); } - private final Context context; - private final ArrayList decks; + private final Context mContext; + private final ArrayList mDecks; public DeckDropDownAdapter(Context context, ArrayList decks) { - this.context = context; - this.decks = decks; + this.mContext = context; + this.mDecks = decks; } static class DeckDropDownViewHolder { @@ -37,7 +37,7 @@ static class DeckDropDownViewHolder { @Override public int getCount() { - return decks.size() + 1; + return mDecks.size() + 1; } @@ -46,7 +46,7 @@ public Object getItem(int position) { if (position == 0) { return null; } else { - return decks.get(position + 1); + return mDecks.get(position + 1); } } @@ -63,7 +63,7 @@ public View getView(int position, View convertView, ViewGroup parent) { TextView deckNameView; TextView deckCountsView; if (convertView == null) { - convertView = LayoutInflater.from(context).inflate(R.layout.dropdown_deck_selected_item, parent, false); + convertView = LayoutInflater.from(mContext).inflate(R.layout.dropdown_deck_selected_item, parent, false); deckNameView = convertView.findViewById(R.id.dropdown_deck_name); deckCountsView = convertView.findViewById(R.id.dropdown_deck_counts); viewHolder = new DeckDropDownViewHolder(); @@ -76,13 +76,13 @@ public View getView(int position, View convertView, ViewGroup parent) { deckCountsView = viewHolder.deckCountsView; } if (position == 0) { - deckNameView.setText(context.getResources().getString(R.string.card_browser_all_decks)); + deckNameView.setText(mContext.getResources().getString(R.string.card_browser_all_decks)); } else { - Deck deck = decks.get(position - 1); + Deck deck = mDecks.get(position - 1); String deckName = deck.getString("name"); deckNameView.setText(deckName); } - deckCountsView.setText(((SubtitleListener) context).getSubtitleText()); + deckCountsView.setText(((SubtitleListener) mContext).getSubtitleText()); return convertView; } @@ -91,16 +91,16 @@ public View getView(int position, View convertView, ViewGroup parent) { public View getDropDownView(int position, View convertView, ViewGroup parent) { TextView deckNameView; if (convertView == null) { - convertView = LayoutInflater.from(context).inflate(R.layout.dropdown_deck_item, parent, false); + convertView = LayoutInflater.from(mContext).inflate(R.layout.dropdown_deck_item, parent, false); deckNameView = convertView.findViewById(R.id.dropdown_deck_name); convertView.setTag(deckNameView); } else { deckNameView = (TextView) convertView.getTag(); } if (position == 0) { - deckNameView.setText(context.getResources().getString(R.string.card_browser_all_decks)); + deckNameView.setText(mContext.getResources().getString(R.string.card_browser_all_decks)); } else { - Deck deck = decks.get(position - 1); + Deck deck = mDecks.get(position - 1); String deckName = deck.getString("name"); deckNameView.setText(deckName); } diff --git a/AnkiDroid/src/main/java/com/ichi2/async/Connection.java b/AnkiDroid/src/main/java/com/ichi2/async/Connection.java index e614fe090d78..b05313ba816b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/Connection.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/Connection.java @@ -185,13 +185,13 @@ protected void onProgressUpdate(Object... values) { public static Connection login(TaskListener listener, Payload data) { - data.taskType = LOGIN; + data.mTaskType = LOGIN; return launchConnectionTask(listener, data); } public static Connection sync(TaskListener listener, Payload data) { - data.taskType = SYNC; + data.mTaskType = SYNC; return launchConnectionTask(listener, data); } @@ -207,7 +207,7 @@ protected Payload doInBackground(Payload... params) { private Payload doOneInBackground(Payload data) { - switch (data.taskType) { + switch (data.mTaskType) { case LOGIN: return doInBackgroundLogin(data); @@ -613,7 +613,7 @@ public interface CancellableTaskListener extends TaskListener { } public static class Payload { - private int taskType; + private int mTaskType; @NonNull public Object[] data; public ConnectionResultType resultType; public Object[] result; diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/NoteImporter.java b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/NoteImporter.java index f54fef0569ed..685a32ce1077 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/NoteImporter.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/NoteImporter.java @@ -64,7 +64,7 @@ public class NoteImporter extends Importer { /** _nextID in python */ private long mNextId; - private ArrayList _ids; + private ArrayList mIds; private boolean mEmptyNotes; private int mUpdateCount; private List mTemplateParsed; @@ -169,7 +169,7 @@ public void importNotes(List notes) { List updateLog = new ArrayList<>(notes.size()); // PORT: Translations moved closer to their sources List _new = new ArrayList<>(); - _ids = new ArrayList<>(); + mIds = new ArrayList<>(); mEmptyNotes = false; int dupeCount = 0; List dupes = new ArrayList<>(notes.size()); @@ -243,9 +243,9 @@ public void importNotes(List notes) { addNew(_new); addUpdates(updates); // make sure to update sflds, etc - mCol.updateFieldCache(_ids); + mCol.updateFieldCache(mIds); // generate cards - if (!mCol.genCards(_ids, mModel).isEmpty()) { + if (!mCol.genCards(mIds, mModel).isEmpty()) { this.getLog().add(0, getString(R.string.note_importer_empty_cards_found)); } @@ -274,14 +274,14 @@ public void importNotes(List notes) { if (mEmptyNotes) { mLog.add(getString(R.string.note_importer_error_empty_notes)); } - mTotal = _ids.size(); + mTotal = mIds.size(); } @Nullable private Object[] newData(ForeignNote n) { long id = mNextId; mNextId++; - _ids.add(id); + mIds.add(id); if (!processFields(n)) { return null; } @@ -306,7 +306,7 @@ private void addNew(List rows) { private Object[] updateData(ForeignNote n, long id, String[] sflds) { - _ids.add(id); + mIds.add(id); if (!processFields(n, sflds)) { return null; } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/TextImporter.java b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/TextImporter.java index ca82dbb32a57..2d46d5924bff 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/TextImporter.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/TextImporter.java @@ -35,12 +35,12 @@ public class TextImporter extends NoteImporter { private boolean mNeedDelimiter = true; final String mPatterns = "\t|,;:"; - private FileObj fileobj; - private char delimiter; - private String[] tagsToAdd; + private FileObj mFileobj; + private char mDelimiter; + private String[] mTagstoadd; - private CsvDialect dialect; - private int numFields; + private CsvDialect mDialect; + private int mNumFields; private boolean mFirstLineWasTags; @@ -48,9 +48,9 @@ public class TextImporter extends NoteImporter { public TextImporter(Collection col, String file) { super(col, file); - fileobj = null; - delimiter = '\0'; - tagsToAdd = new String[0]; + mFileobj = null; + mDelimiter = '\0'; + mTagstoadd = new String[0]; } @@ -66,10 +66,10 @@ protected List foreignNotes() { // Note: This differs from libAnki as we don't have csv.reader Iterator data = getDataStream().iterator(); CsvReader reader; - if (delimiter != '\0') { - reader = CsvReader.fromDelimiter(data, delimiter); + if (mDelimiter != '\0') { + reader = CsvReader.fromDelimiter(data, mDelimiter); } else { - reader = CsvReader.fromDialect(data, dialect); + reader = CsvReader.fromDialect(data, mDialect); } try { for (List row : reader) { @@ -77,12 +77,12 @@ protected List foreignNotes() { continue; } List rowAsString = new ArrayList<>(row); - if (rowAsString.size() != numFields) { + if (rowAsString.size() != mNumFields) { if (rowAsString.size() > 0) { String formatted = getString(R.string.csv_importer_error_invalid_field_count, TextUtils.join(" ", rowAsString), rowAsString.size(), - numFields); + mNumFields); log.add(formatted); ignored += 1; @@ -96,7 +96,7 @@ protected List foreignNotes() { log.add(getString(R.string.csv_importer_error_exception, e)); } mLog = log; - fileobj.close(); + mFileobj.close(); return notes; } @@ -104,14 +104,14 @@ protected List foreignNotes() { @Override protected int fields() { open(); - return numFields; + return mNumFields; } private ForeignNote noteFromFields(List fields) { ForeignNote note = new ForeignNote(); note.mFields.addAll(fields); - note.mTags.addAll(Arrays.asList(tagsToAdd)); + note.mTags.addAll(Arrays.asList(mTagstoadd)); return note; } @@ -125,27 +125,27 @@ protected void open() { /** Read file into self.lines if not already there. */ private void cacheFile() { - if (fileobj == null) { + if (mFileobj == null) { openFile(); } } private void openFile() { - dialect = null; - fileobj = FileObj.open(mFile); + mDialect = null; + mFileobj = FileObj.open(mFile); String firstLine = getFirstFileLine().orElse(null); if (firstLine != null) { if (firstLine.startsWith("tags:")) { String tags = firstLine.substring("tags:".length()).trim(); - tagsToAdd = tags.split(" "); + mTagstoadd = tags.split(" "); this.mFirstLineWasTags = true; } updateDelimiter(); } - if (dialect == null && delimiter == '\0') { + if (mDialect == null && mDelimiter == '\0') { throw new RuntimeException("unknownFormat"); } } @@ -156,16 +156,16 @@ private void err() { } private void updateDelimiter() { - dialect = null; + mDialect = null; CsvSniffer sniffer = new CsvSniffer(); - if (delimiter == '\0') { + if (mDelimiter == '\0') { try { String join = getLinesFromFile(10); - dialect = sniffer.sniff(join, mPatterns.toCharArray()); + mDialect = sniffer.sniff(join, mPatterns.toCharArray()); } catch (Exception e) { Timber.w(e); try { - dialect = sniffer.sniff(getFirstFileLine().orElse(""), mPatterns.toCharArray()); + mDialect = sniffer.sniff(getFirstFileLine().orElse(""), mPatterns.toCharArray()); } catch (Exception ex) { Timber.w(ex); // pass @@ -176,9 +176,9 @@ private void updateDelimiter() { Iterator data = getDataStream().iterator(); CsvReader reader = null; - if (dialect != null) { + if (mDialect != null) { try { - reader = CsvReader.fromDialect(data, dialect); + reader = CsvReader.fromDialect(data, mDialect); } catch (Exception e) { Timber.w(e); err(); @@ -186,25 +186,25 @@ private void updateDelimiter() { } else { // PERF: This starts the file read twice - whereas we only need the first line String firstLine = getFirstFileLine().orElse(""); - if (delimiter == '\0') { + if (mDelimiter == '\0') { if (firstLine.contains("\t")) { - delimiter = '\t'; + mDelimiter = '\t'; } else if(firstLine.contains(";")) { - delimiter = ';'; + mDelimiter = ';'; } else if(firstLine.contains(",")) { - delimiter = ','; + mDelimiter = ','; } else { - delimiter = ' '; + mDelimiter = ' '; } } - reader = CsvReader.fromDelimiter(data, delimiter); + reader = CsvReader.fromDelimiter(data, mDelimiter); } try { while (true) { List row = reader.next(); if (row.size() > 0) { - numFields = row.size(); + mNumFields = row.size(); break; } } @@ -234,7 +234,7 @@ private String sub(String s) { private Stream getDataStream() { Stream data; try { - data = fileobj.readAsUtf8WithoutBOM(); + data = mFileobj.readAsUtf8WithoutBOM(); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvDialect.java b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvDialect.java index a2c6ff709bf1..9f9160419253 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvDialect.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvDialect.java @@ -22,6 +22,9 @@ package com.ichi2.libanki.importer.python; +import android.annotation.SuppressLint; + +@SuppressLint("FieldNamingPatternDetector") public class CsvDialect { /** Controls when quotes should be generated by the writer and recognised by the reader. It can take on any of the QUOTE_* constants (see section Module Contents) and defaults to QUOTE_MINIMAL. */ public final Quoting mQuoting = Quoting.QUOTE_MINIMAL; diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReader.java b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReader.java index eabea1fb4c94..45622d5c9554 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReader.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReader.java @@ -21,6 +21,8 @@ package com.ichi2.libanki.importer.python; +import android.annotation.SuppressLint; + import java.util.Iterator; import java.util.List; @@ -28,6 +30,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +@SuppressLint("FieldNamingPatternDetector") public class CsvReader implements Iterable> { public final CsvDialect dialect; diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReaderIterator.java b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReaderIterator.java index 41ccb8986833..a9b811221b73 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReaderIterator.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvReaderIterator.java @@ -21,6 +21,8 @@ package com.ichi2.libanki.importer.python; +import android.annotation.SuppressLint; + import com.ichi2.libanki.importer.CsvException; import java.util.ArrayList; @@ -34,6 +36,7 @@ import static com.ichi2.libanki.importer.python.CsvDialect.Quoting.*; import static com.ichi2.libanki.importer.python.CsvReaderIterator.State.*; +@SuppressLint("FieldNamingPatternDetector") public class CsvReaderIterator implements Iterator> { private final CsvReader reader; diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvSniffer.java b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvSniffer.java index 1e88bfd95048..84684fa8e2e8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvSniffer.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/importer/python/CsvSniffer.java @@ -22,6 +22,7 @@ package com.ichi2.libanki.importer.python; +import android.annotation.SuppressLint; import android.os.Build; import com.ichi2.libanki.importer.CsvException; @@ -38,9 +39,11 @@ import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +@SuppressLint("FieldNamingPatternDetector") @RequiresApi(Build.VERSION_CODES.O) // Regex group(str) public class CsvSniffer { + private final char[] preferred; diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/CountingFileRequestBody.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/CountingFileRequestBody.java index 4fea14fe8bf0..f05d93a3b386 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/CountingFileRequestBody.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/CountingFileRequestBody.java @@ -31,36 +31,36 @@ public class CountingFileRequestBody extends RequestBody { private static final int SEGMENT_SIZE = 8092; // okio.Segment.SIZE (internal, copy required) - private final File file; - private final ProgressListener listener; - private final String contentType; + private final File mFile; + private final ProgressListener mListener; + private final String mContentType; public CountingFileRequestBody(File file, String contentType, ProgressListener listener) { - this.file = file; - this.contentType = contentType; - this.listener = listener; + this.mFile = file; + this.mContentType = contentType; + this.mListener = listener; } @Override public long contentLength() { - return file.length(); + return mFile.length(); } @Override public MediaType contentType() { - return MediaType.parse(contentType); + return MediaType.parse(mContentType); } @Override public void writeTo(BufferedSink sink) throws IOException { Source source = null; try { - source = Okio.source(file); + source = Okio.source(mFile); long read; while ((read = source.read(sink.getBuffer(), SEGMENT_SIZE)) != -1) { sink.flush(); - this.listener.transferred(read); + this.mListener.transferred(read); } } finally { Util.closeQuietly(source); diff --git a/AnkiDroid/src/main/java/com/ichi2/ui/ConfirmationPreference.java b/AnkiDroid/src/main/java/com/ichi2/ui/ConfirmationPreference.java index 2225bd9b3f6d..ede0ca9dbf76 100644 --- a/AnkiDroid/src/main/java/com/ichi2/ui/ConfirmationPreference.java +++ b/AnkiDroid/src/main/java/com/ichi2/ui/ConfirmationPreference.java @@ -22,8 +22,8 @@ @SuppressWarnings("deprecation") // TODO Tracked in https://github.com/ankidroid/Anki-Android/issues/5019 public class ConfirmationPreference extends android.preference.DialogPreference { - private Runnable cancelHandler = () -> { /* do nothing by default */ }; - private Runnable okHandler = () -> { /* do nothing by default */ }; + private Runnable mCancelHandler = () -> { /* do nothing by default */ }; + private Runnable mOkHandler = () -> { /* do nothing by default */ }; public ConfirmationPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -31,21 +31,21 @@ public ConfirmationPreference(Context context, AttributeSet attrs) { public void setCancelHandler(Runnable cancelHandler) { - this.cancelHandler = cancelHandler; + this.mCancelHandler = cancelHandler; } public void setOkHandler(Runnable okHandler) { - this.okHandler = okHandler; + this.mOkHandler = okHandler; } @Override protected void onDialogClosed(boolean positiveResult) { if (positiveResult) { - okHandler.run(); + mOkHandler.run(); } else { - cancelHandler.run(); + mCancelHandler.run(); } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/DatabaseChangeDecorator.java b/AnkiDroid/src/main/java/com/ichi2/utils/DatabaseChangeDecorator.java index 988f2f86ab83..0b3e8bde42ea 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/DatabaseChangeDecorator.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/DatabaseChangeDecorator.java @@ -23,8 +23,6 @@ import android.os.CancellationSignal; import android.util.Pair; -import com.ichi2.libanki.DB; - import java.io.IOException; import java.util.List; import java.util.Locale; @@ -38,11 +36,11 @@ public class DatabaseChangeDecorator implements SupportSQLiteDatabase { private static final String[] MOD_SQLS = new String[] { "insert", "update", "delete" }; - private final SupportSQLiteDatabase wrapped; + private final SupportSQLiteDatabase mWrapped; public DatabaseChangeDecorator(SupportSQLiteDatabase wrapped) { - this.wrapped = wrapped; + this.mWrapped = wrapped; } private void markDataAsChanged() { @@ -76,211 +74,211 @@ private boolean startsWithIgnoreCase(String lowerHaystack, String upperHaystack, public SupportSQLiteStatement compileStatement(String sql) { - SupportSQLiteStatement supportSQLiteStatement = wrapped.compileStatement(sql); + SupportSQLiteStatement supportSQLiteStatement = mWrapped.compileStatement(sql); checkForChanges(sql); //technically a little hasty - as the statement hasn't been executed. return supportSQLiteStatement; } public void beginTransaction() { - wrapped.beginTransaction(); + mWrapped.beginTransaction(); } public void beginTransactionNonExclusive() { - wrapped.beginTransactionNonExclusive(); + mWrapped.beginTransactionNonExclusive(); } public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { - wrapped.beginTransactionWithListener(transactionListener); + mWrapped.beginTransactionWithListener(transactionListener); } public void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener) { - wrapped.beginTransactionWithListenerNonExclusive(transactionListener); + mWrapped.beginTransactionWithListenerNonExclusive(transactionListener); } public void endTransaction() { - wrapped.endTransaction(); + mWrapped.endTransaction(); } public void setTransactionSuccessful() { - wrapped.setTransactionSuccessful(); + mWrapped.setTransactionSuccessful(); } public boolean inTransaction() { - return wrapped.inTransaction(); + return mWrapped.inTransaction(); } public boolean isDbLockedByCurrentThread() { - return wrapped.isDbLockedByCurrentThread(); + return mWrapped.isDbLockedByCurrentThread(); } public boolean yieldIfContendedSafely() { - return wrapped.yieldIfContendedSafely(); + return mWrapped.yieldIfContendedSafely(); } public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { - return wrapped.yieldIfContendedSafely(sleepAfterYieldDelay); + return mWrapped.yieldIfContendedSafely(sleepAfterYieldDelay); } public int getVersion() { - return wrapped.getVersion(); + return mWrapped.getVersion(); } public void setVersion(int version) { - wrapped.setVersion(version); + mWrapped.setVersion(version); } public long getMaximumSize() { - return wrapped.getMaximumSize(); + return mWrapped.getMaximumSize(); } public long setMaximumSize(long numBytes) { - return wrapped.setMaximumSize(numBytes); + return mWrapped.setMaximumSize(numBytes); } public long getPageSize() { - return wrapped.getPageSize(); + return mWrapped.getPageSize(); } public void setPageSize(long numBytes) { - wrapped.setPageSize(numBytes); + mWrapped.setPageSize(numBytes); } public Cursor query(String query) { - return wrapped.query(query); + return mWrapped.query(query); } public Cursor query(String query, Object[] bindArgs) { - return wrapped.query(query, bindArgs); + return mWrapped.query(query, bindArgs); } public Cursor query(SupportSQLiteQuery query) { - return wrapped.query(query); + return mWrapped.query(query); } public Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal) { - return wrapped.query(query, cancellationSignal); + return mWrapped.query(query, cancellationSignal); } public long insert(String table, int conflictAlgorithm, ContentValues values) throws SQLException { - long insert = wrapped.insert(table, conflictAlgorithm, values); + long insert = mWrapped.insert(table, conflictAlgorithm, values); markDataAsChanged(); return insert; } public int delete(String table, String whereClause, Object[] whereArgs) { - int delete = wrapped.delete(table, whereClause, whereArgs); + int delete = mWrapped.delete(table, whereClause, whereArgs); markDataAsChanged(); return delete; } public int update(String table, int conflictAlgorithm, ContentValues values, String whereClause, Object[] whereArgs) { - int update = wrapped.update(table, conflictAlgorithm, values, whereClause, whereArgs); + int update = mWrapped.update(table, conflictAlgorithm, values, whereClause, whereArgs); markDataAsChanged(); return update; } public void execSQL(String sql) throws SQLException { - wrapped.execSQL(sql); + mWrapped.execSQL(sql); checkForChanges(sql); } public void execSQL(String sql, Object[] bindArgs) throws SQLException { - wrapped.execSQL(sql, bindArgs); + mWrapped.execSQL(sql, bindArgs); checkForChanges(sql); } public boolean isReadOnly() { - return wrapped.isReadOnly(); + return mWrapped.isReadOnly(); } public boolean isOpen() { - return wrapped.isOpen(); + return mWrapped.isOpen(); } public boolean needUpgrade(int newVersion) { - return wrapped.needUpgrade(newVersion); + return mWrapped.needUpgrade(newVersion); } public String getPath() { - return wrapped.getPath(); + return mWrapped.getPath(); } public void setLocale(Locale locale) { - wrapped.setLocale(locale); + mWrapped.setLocale(locale); } public void setMaxSqlCacheSize(int cacheSize) { - wrapped.setMaxSqlCacheSize(cacheSize); + mWrapped.setMaxSqlCacheSize(cacheSize); } public void setForeignKeyConstraintsEnabled(boolean enable) { - wrapped.setForeignKeyConstraintsEnabled(enable); + mWrapped.setForeignKeyConstraintsEnabled(enable); } public boolean enableWriteAheadLogging() { - return wrapped.enableWriteAheadLogging(); + return mWrapped.enableWriteAheadLogging(); } public void disableWriteAheadLogging() { - wrapped.disableWriteAheadLogging(); + mWrapped.disableWriteAheadLogging(); } public boolean isWriteAheadLoggingEnabled() { - return wrapped.isWriteAheadLoggingEnabled(); + return mWrapped.isWriteAheadLoggingEnabled(); } public List> getAttachedDbs() { - return wrapped.getAttachedDbs(); + return mWrapped.getAttachedDbs(); } public boolean isDatabaseIntegrityOk() { - return wrapped.isDatabaseIntegrityOk(); + return mWrapped.isDatabaseIntegrityOk(); } public void close() throws IOException { - wrapped.close(); + mWrapped.close(); } public SupportSQLiteDatabase getWrapped() { - return wrapped; + return mWrapped; } } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/DiffEngine.java b/AnkiDroid/src/main/java/com/ichi2/utils/DiffEngine.java index 172ed7ee35de..219ab6d96108 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/DiffEngine.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/DiffEngine.java @@ -29,7 +29,7 @@ */ public class DiffEngine { - private final DiffMatchPatch diffMatchPatch = new DiffMatchPatch(); + private final DiffMatchPatch mDiffMatchPatch = new DiffMatchPatch(); /** @@ -42,7 +42,7 @@ public class DiffEngine { public String[] diffedHtmlStrings(String typed, String correct) { StringBuilder prettyTyped = new StringBuilder(); StringBuilder prettyCorrect = new StringBuilder(); - for (DiffMatchPatch.Diff aDiff : diffMatchPatch.diffMain(typed, correct)) { + for (DiffMatchPatch.Diff aDiff : mDiffMatchPatch.diffMain(typed, correct)) { switch (aDiff.operation) { case INSERT: prettyTyped.append(wrapBad(aDiff.text)); diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/JSONArray.java b/AnkiDroid/src/main/java/com/ichi2/utils/JSONArray.java index a5f3654a513a..de38738e845f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/JSONArray.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/JSONArray.java @@ -308,17 +308,17 @@ public Iterable jsonArrayIterable() { } public Iterator jsonArrayIterator() { return new Iterator() { - private int index = 0; + private int mIndex = 0; @Override public boolean hasNext() { - return index < length(); + return mIndex < length(); } @Override public JSONArray next() { - JSONArray array = getJSONArray(index); - index++; + JSONArray array = getJSONArray(mIndex); + mIndex++; return array; } }; @@ -329,17 +329,17 @@ public Iterable jsonObjectIterable() { } public Iterator jsonObjectIterator() { return new Iterator() { - private int index = 0; + private int mIndex = 0; @Override public boolean hasNext() { - return index < length(); + return mIndex < length(); } @Override public JSONObject next() { - JSONObject object = getJSONObject(index); - index++; + JSONObject object = getJSONObject(mIndex); + mIndex++; return object; } }; @@ -350,17 +350,17 @@ public Iterable stringIterable() { } public Iterator stringIterator() { return new Iterator() { - private int index = 0; + private int mIndex = 0; @Override public boolean hasNext() { - return index < length(); + return mIndex < length(); } @Override public String next() { - String string = getString(index); - index++; + String string = getString(mIndex); + mIndex++; return string; } }; @@ -371,17 +371,17 @@ public Iterable longIterable() { } public Iterator longIterator() { return new Iterator() { - private int index = 0; + private int mIndex = 0; @Override public boolean hasNext() { - return index < length(); + return mIndex < length(); } @Override public Long next() { - Long long_ = getLong(index); - index++; + Long long_ = getLong(mIndex); + mIndex++; return long_; } }; diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/JSONException.java b/AnkiDroid/src/main/java/com/ichi2/utils/JSONException.java index 9e7c0c032a6c..9a9f63768889 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/JSONException.java +++ b/AnkiDroid/src/main/java/com/ichi2/utils/JSONException.java @@ -48,7 +48,7 @@ Similar to JSONException in meaning, but unchecked */ public class JSONException extends RuntimeException { - private JSONException exc = null; + private JSONException mExc = null; public JSONException(String s) { super(s); @@ -64,12 +64,12 @@ public JSONException(Throwable e) { public JSONException(JSONException e) { super(e); - exc = e; + mExc = e; } public JSONException asException() { - if (exc!=null) { - return exc; + if (mExc !=null) { + return mExc; } else { return new JSONException(toString()); } From fd7599c5d777998fd510120d26e8e4bf3c21f924 Mon Sep 17 00:00:00 2001 From: Mrudul Tora Date: Fri, 9 Apr 2021 18:48:47 +0530 Subject: [PATCH 093/171] Fail CI if Analytics string constants are changed (#8405) This is important because analytics comparisons are done via the string constant values persisted externally in the cloud analytics provider, and if you change the strings you can no longer compare versus previous analytics string usages * Fail CI if Analytics string constants are changed * Added javadoc to explain the important points * Elaborated messages if assertion fails Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> --- .../anki/analytics/AnalyticsConstant.java | 29 ++++ .../ichi2/anki/analytics/UsageAnalytics.java | 30 ++++ .../analytics/AnalyticsConstantsTest.java | 144 ++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnalyticsConstant.java create mode 100644 AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnalyticsConstant.java b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnalyticsConstant.java new file mode 100644 index 000000000000..79d2a1b09369 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/AnalyticsConstant.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2021 Mrudul Tora + 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.analytics; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This marker annotation is used for annotating string constants used in Analytics. + */ + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AnalyticsConstant { + +} \ No newline at end of file diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.java b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.java index c3fde9a6139c..360e90ed4865 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/analytics/UsageAnalytics.java @@ -366,38 +366,68 @@ public static class Category { /** * These Strings must not be changed as they are used for analytic comparisons between AnkiDroid versions. + * If a new string is added here then the respective changes must also be made in AnalyticsConstantsTest.java + * All the constant strings added here must be annotated with @AnalyticsConstant. */ public static class Actions { /* Analytics actions used in Help Dialog*/ + @AnalyticsConstant public static final String OPENED_HELPDIALOG = "Opened HelpDialogBox"; + @AnalyticsConstant public static final String OPENED_USING_ANKIDROID = "Opened Using AnkiDroid"; + @AnalyticsConstant public static final String OPENED_GET_HELP = "Opened Get Help"; + @AnalyticsConstant public static final String OPENED_SUPPORT_ANKIDROID = "Opened Support AnkiDroid"; + @AnalyticsConstant public static final String OPENED_COMMUNITY = "Opened Community"; + @AnalyticsConstant public static final String OPENED_ANKIDROID_MANUAL = "Opened AnkiDroid Manual"; + @AnalyticsConstant public static final String OPENED_ANKI_MANUAL = "Opened Anki Manual"; + @AnalyticsConstant public static final String OPENED_ANKIDROID_FAQ = "Opened AnkiDroid FAQ"; + @AnalyticsConstant public static final String OPENED_MAILING_LIST = "Opened Mailing List"; + @AnalyticsConstant public static final String OPENED_REPORT_BUG = "Opened Report a Bug"; + @AnalyticsConstant public static final String OPENED_DONATE = "Opened Donate"; + @AnalyticsConstant public static final String OPENED_TRANSLATE = "Opened Translate"; + @AnalyticsConstant public static final String OPENED_DEVELOP = "Opened Develop"; + @AnalyticsConstant public static final String OPENED_RATE = "Opened Rate"; + @AnalyticsConstant public static final String OPENED_OTHER = "Opened Other"; + @AnalyticsConstant public static final String OPENED_SEND_FEEDBACK = "Opened Send Feedback"; + @AnalyticsConstant public static final String OPENED_ANKI_FORUMS = "Opened Anki Forums"; + @AnalyticsConstant public static final String OPENED_REDDIT = "Opened Reddit"; + @AnalyticsConstant public static final String OPENED_DISCORD = "Opened Discord"; + @AnalyticsConstant public static final String OPENED_FACEBOOK = "Opened Facebook"; + @AnalyticsConstant public static final String OPENED_TWITTER = "Opened Twitter"; + @AnalyticsConstant public static final String EXCEPTION_REPORT = "Exception Report"; /* Analytics actions used in Lookup Dictionary */ + @AnalyticsConstant public static final String AEDICT = "aedict"; + @AnalyticsConstant public static final String LEO = "leo"; + @AnalyticsConstant public static final String COLORDICT = "colordict"; + @AnalyticsConstant public static final String FORA = "fora"; + @AnalyticsConstant public static final String NCIKU = "nciku"; + @AnalyticsConstant public static final String EIJIRO = "eijiro"; } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java new file mode 100644 index 000000000000..42aaab678d34 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java @@ -0,0 +1,144 @@ +/* + Copyright (c) 2021 Mrudul Tora + 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.analytics; + +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * This class contains two nested classes and is using the concept of Enclosed runner that internally works as a Suite. + * The first inner class is a Parameterized class and will run according to the size of test case, while the second + * inner class is non-parameterized and will run only once. + */ +@RunWith(Enclosed.class) +public class AnalyticsConstantsTest { + private static final List listOfConstantFields = new ArrayList<>(); + + + @RunWith(Parameterized.class) + public static class AnalyticsConstantsFieldValuesTest { + private final String analyticsString; + + + public AnalyticsConstantsFieldValuesTest(String analyticsString) { + this.analyticsString = analyticsString; + } + + + @Parameterized.Parameters + public static List addAnalyticsConstants() { + listOfConstantFields.add("Opened HelpDialogBox"); + listOfConstantFields.add("Opened Using AnkiDroid"); + listOfConstantFields.add("Opened Get Help"); + listOfConstantFields.add("Opened Support AnkiDroid"); + listOfConstantFields.add("Opened Community"); + listOfConstantFields.add("Opened AnkiDroid Manual"); + listOfConstantFields.add("Opened Anki Manual"); + listOfConstantFields.add("Opened AnkiDroid FAQ"); + listOfConstantFields.add("Opened Mailing List"); + listOfConstantFields.add("Opened Report a Bug"); + listOfConstantFields.add("Opened Donate"); + listOfConstantFields.add("Opened Translate"); + listOfConstantFields.add("Opened Develop"); + listOfConstantFields.add("Opened Rate"); + listOfConstantFields.add("Opened Other"); + listOfConstantFields.add("Opened Send Feedback"); + listOfConstantFields.add("Opened Anki Forums"); + listOfConstantFields.add("Opened Reddit"); + listOfConstantFields.add("Opened Discord"); + listOfConstantFields.add("Opened Facebook"); + listOfConstantFields.add("Opened Twitter"); + listOfConstantFields.add("Exception Report"); + listOfConstantFields.add("aedict"); + listOfConstantFields.add("leo"); + listOfConstantFields.add("colordict"); + listOfConstantFields.add("fora"); + listOfConstantFields.add("nciku"); + listOfConstantFields.add("eijiro"); + return listOfConstantFields; + } + + + /** + * The message here means that the string being checked cannot be found in Actions class. + * If encountered with this message, re-check the list present here and constants in Actions class, to resolve + * the test failure. + */ + @Test + public void checkAnalyticsString() { + assertEquals("Re-check if you renamed any string in the analytics string constants of Actions class or AnalyticsConstantsTest.listOfConstantFields. If so, revert them as those string constants must not change as they are compared in analytics.", + analyticsString, getStringFromReflection(analyticsString)); + } + + + public String getStringFromReflection(String analyticsStringToBeChecked) { + String reflectedString = null; + UsageAnalytics.Actions actions = new UsageAnalytics.Actions(); + Field[] field; + field = actions.getClass().getDeclaredFields(); + for (Field value : field) { + if (value.isAnnotationPresent(AnalyticsConstant.class)) { + try { + if (value.get(actions).equals(analyticsStringToBeChecked)) { + reflectedString = (String) value.get(actions); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(); + } + } + } + return reflectedString; + } + + } + + + public static class AnalyticsConstantsFieldLengthTest { + + @Test + public void fieldSizeEqualsListOfConstantFields() { + if (getFieldSize() > listOfConstantFields.size()) { + assertEquals("Add the newly added analytics constant to AnalyticsConstantsTest.listOfConstantFields. NOTE: Constants should not be renamed as we cannot compare these in analytics.", + listOfConstantFields.size(), getFieldSize()); + } else if (getFieldSize() < listOfConstantFields.size()) { + assertEquals("If a constant is removed, it should be removed from AnalyticsConstantsTest.listOfConstantFields. NOTE: Constants should not be renamed as we cannot compare these in analytics.", + listOfConstantFields.size(), getFieldSize()); + } else { + assertEquals(listOfConstantFields.size(), getFieldSize()); + } + + } + + + /** + * This method is used to get the size of fields in Actions Class. + * Because whenever a new constant is added in Actions Class but not added to the list present in this + * class (listOfConstantFields) the test must fail. + */ + public static long getFieldSize() { + return Arrays.stream(UsageAnalytics.Actions.class.getDeclaredFields()) + .filter(x -> x.isAnnotationPresent(AnalyticsConstant.class)) + .count(); + } + } +} From 8d311c93df2f97d3ef6a98c114d6860460deaff7 Mon Sep 17 00:00:00 2001 From: Ayaan Javed <80831951+AyaanJaved@users.noreply.github.com> Date: Fri, 9 Apr 2021 22:10:27 +0530 Subject: [PATCH 094/171] Unit Test for DeckNameComparator (#8393) * Created Unit Test for DeckNameComparator.java * Added 3rd Level Subdeck test Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> --- .../ichi2/utils/DeckNameComparatorTest.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/DeckNameComparatorTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/DeckNameComparatorTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/DeckNameComparatorTest.java new file mode 100644 index 000000000000..14f4531e8307 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/DeckNameComparatorTest.java @@ -0,0 +1,27 @@ +package com.ichi2.utils; + +import org.junit.Before; +import org.junit.Test; + +import static java.util.Arrays.sort; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class DeckNameComparatorTest { + private DeckNameComparator deckNameComparator; + + @Before + public void setUp() { + deckNameComparator = new DeckNameComparator(); + } + + //Testing DeckNameComparator by sorting an array of deck names. + @Test + public void sortDeckNames() { + String[] deckNames = new String[]{"AA", "ab", "BB", "aa", "aa::bb", "aa::ab", "aa::ab::Aa", "aa::ab::aB", "aa::ab:bB"}; + sort(deckNames, deckNameComparator); + + assertThat(deckNames, is(new String[]{"AA", "aa", "aa::ab", "aa::ab::Aa", "aa::ab::aB", "aa::ab:bB", "aa::bb", "ab", "BB"})); + } +} From 4d0ba4d174e860bcc112560d840d582c08bd4f0d Mon Sep 17 00:00:00 2001 From: Kartikey Saran <55613721+kartikeysaran@users.noreply.github.com> Date: Fri, 9 Apr 2021 22:36:31 +0530 Subject: [PATCH 095/171] Add dialog with reset to default button for collection directory (#8514) * Reset the directory path to default button Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> --- AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java index 072d8ff7c7b0..a97255fe9151 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java @@ -26,6 +26,7 @@ import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; @@ -308,7 +309,13 @@ private void initSubscreen(String action, PreferenceContext listener) { return true; } catch (StorageAccessException e) { Timber.e(e, "Could not initialize directory: %s", newPath); - Toast.makeText(getApplicationContext(), R.string.dialog_collection_path_not_dir, Toast.LENGTH_LONG).show(); + MaterialDialog materialDialog = new MaterialDialog.Builder(Preferences.this) + .title(R.string.dialog_collection_path_not_dir) + .positiveText(R.string.dialog_ok) + .negativeText(R.string.reset_custom_buttons) + .onPositive((dialog, which) -> dialog.dismiss()) + .onNegative((dialog, which) -> collectionPathPreference.setText(CollectionHelper.getDefaultAnkiDroidDirectory())) + .show(); return false; } }); From a776ef2a12c648f152f98029a881279d7e8930f2 Mon Sep 17 00:00:00 2001 From: Vaibhavi Lokegaonkar <53178543+Vaibhavi1707@users.noreply.github.com> Date: Fri, 9 Apr 2021 22:43:04 +0530 Subject: [PATCH 096/171] Added tests to the utility functions of the Tags class (#8511) * Added tests to the utility functions of the Tags class * Added copyright headers --- .../test/java/com/ichi2/libanki/TagsTest.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.java new file mode 100644 index 000000000000..ba403f67e188 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/TagsTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 Vaibhavi Lokegaonkar Github Username: Vaibhavi1707 + * + * 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.libanki; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import com.ichi2.anki.RobolectricTest; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertNotEquals; + +@RunWith(AndroidJUnit4.class) +public class TagsTest extends RobolectricTest { + + @Test + public void test_split() { + Collection col = getCol(); + Tags tags = new Tags(col); + + ArrayList tags_list1 = new ArrayList<>(); + tags_list1.add("Todo"); + tags_list1.add("todo"); + tags_list1.add("Needs revision"); + + ArrayList tags_list2 = new ArrayList<>(); + tags_list2.add("Todo"); + tags_list2.add("todo"); + tags_list2.add("Needs"); + tags_list2.add("Revision"); + + assertNotEquals(tags_list1, tags.split("Todo todo Needs Revision")); + assertEquals(tags_list2, tags.split("Todo todo Needs Revision")); + assertEquals(0, tags.split("").size()); + } + + @Test + public void test_in_list() { + Collection col = getCol(); + Tags tags = new Tags(col); + + ArrayList tags_list = new ArrayList<>(); + tags_list.add("Todo"); + tags_list.add("Needs revision"); + tags_list.add("Once more"); + tags_list.add("test1 content"); + + assertFalse(tags.inList("Done", tags_list)); + assertTrue(tags.inList("Needs revision", tags_list)); + assertTrue(tags.inList("once More", tags_list)); + assertFalse(tags.inList("test1Content", tags_list)); + assertFalse(tags.inList("", new ArrayList())); + } + + @Test + public void test_add_to_str() { + Collection col = getCol(); + Tags tags = new Tags(col); + + assertEquals(" Needs Revision Todo ", tags.addToStr("todo", "Todo todo Needs Revision")); + assertEquals(" Todo ", tags.addToStr("Todo", "")); + assertEquals(" Needs Revision Todo ", tags.addToStr("", "Todo todo Needs Revision")); + } +} From a9b58cfe3780b882e51fa29d10904c3b07fa4a44 Mon Sep 17 00:00:00 2001 From: Farjad Ilyas Date: Sat, 10 Apr 2021 02:30:30 +0500 Subject: [PATCH 097/171] Unit Tests for JSONObject --- .../java/com/ichi2/utils/JSONObjectTest.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java index 6c8f669cd3e7..beff462c2520 100644 --- a/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java @@ -16,9 +16,15 @@ package com.ichi2.utils; +import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.HashMap; +import java.util.Map; + +import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import static org.hamcrest.MatcherAssert.assertThat; @@ -26,9 +32,131 @@ @RunWith(AndroidJUnit4.class) public class JSONObjectTest { + + private final String emptyJson = "{}"; + private final String correctJsonBasic = "{key1:value1}"; + private final String correctJsonNested = "{key1:{key1a:value1a,key1b:value1b},key2:value2}"; + private final String correctJsonWithArray = "{key1:value1,key2:[{key2a:value2a},{key2b:value2b}],key3:value3}"; + private final String correctJsonNestedWithArray = "{key1:{key1a:value1a,key1b:value1b},key2:[{key2a:value2a},{key2b:value2b}],key3:value3}"; + private final String noOpeningBracket = "key1:value1}"; + private final String extraOpeningBracket = "{{key1: value1}"; + private final String noClosingBracket = "{key1:value1"; + private final String wrongKeyValueSeparator = "{key1:value1,key2 value2}"; + private final String duplicateKey = "{key1:value1,key1:value2}"; + + private JSONObject correctJsonObjectBasic; + private JSONObject correctJsonObjectNested; + private JSONObject correctJsonObjectWithArray; + private JSONObject correctJsonObjectNestedWithArray; + Map booleanMap; + + @Before + public void setUp() { + correctJsonObjectBasic = new JSONObject(correctJsonBasic); + correctJsonObjectNested = new JSONObject(correctJsonNested); + correctJsonObjectWithArray = new JSONObject(correctJsonWithArray); + correctJsonObjectNestedWithArray = new JSONObject(correctJsonNestedWithArray); + + booleanMap = new HashMap<>(); + for (int i = 0 ; i < 10 ; ++i) { + booleanMap.put("key" + i, i%2 == 0); + } + } + @Test public void objectNullIsNotNull() { //#6289 assertThat(JSONObject.NULL, notNullValue()); } + + + @Test + public void formatTest() { + // Correct formats + new JSONObject(correctJsonBasic); + new JSONObject(correctJsonNested); + new JSONObject(correctJsonWithArray); + new JSONObject(emptyJson); + + // Incorrect formats + Assert.assertThrows(JSONException.class, () -> new JSONObject(noOpeningBracket)); + Assert.assertThrows(JSONException.class, () -> new JSONObject(extraOpeningBracket)); + Assert.assertThrows(JSONException.class, () -> new JSONObject(noClosingBracket)); + Assert.assertThrows(JSONException.class, () -> new JSONObject(wrongKeyValueSeparator)); + Assert.assertThrows(JSONException.class, () -> new JSONObject(duplicateKey)); + } + + @Test + public void copyJsonTest() { + Assert.assertEquals(correctJsonObjectBasic.toString(), new JSONObject(correctJsonObjectBasic).toString()); + Assert.assertEquals(correctJsonObjectNested.toString(), new JSONObject(correctJsonObjectNested).toString()); + Assert.assertEquals(correctJsonObjectWithArray.toString(), new JSONObject(correctJsonObjectWithArray).toString()); + } + + @Test + public void objectToObjectTest() { + Assert.assertEquals(correctJsonObjectBasic.toString(), JSONObject.objectToObject(correctJsonObjectBasic).toString()); + Assert.assertEquals(correctJsonObjectNested.toString(), JSONObject.objectToObject(correctJsonObjectNested).toString()); + Assert.assertNotEquals(correctJsonObjectNested.toString(), JSONObject.objectToObject(correctJsonObjectWithArray).toString()); + } + + @Test + public void getTest() { + JSONObject correctJsonObjectBasicCopy = new JSONObject(correctJsonBasic); + correctJsonObjectBasicCopy.put("int-key", 2); + correctJsonObjectBasicCopy.put("int_key", 6); + correctJsonObjectBasicCopy.put("long_key", 2L); + correctJsonObjectBasicCopy.put("double_key", 2d); + correctJsonObjectBasicCopy.putOpt("boolean_key", (boolean) true); + correctJsonObjectBasicCopy.putOpt("object_key", correctJsonBasic); + + Assert.assertEquals(6, correctJsonObjectBasicCopy.getInt("int_key")); + Assert.assertEquals(2L, correctJsonObjectBasicCopy.getLong("long_key")); + Assert.assertEquals(2d, correctJsonObjectBasicCopy.getDouble("double_key"), 1e-10); + Assert.assertTrue(correctJsonObjectBasicCopy.getBoolean("boolean_key")); + Assert.assertEquals(correctJsonBasic, correctJsonObjectBasicCopy.get("object_key")); + + // Check that putOpt doesn't add pair when one is null + correctJsonObjectBasicCopy.putOpt("boolean_key_2", null); + Assert.assertFalse(correctJsonObjectBasicCopy.has("boolean_key_2")); + Assert.assertThrows(JSONException.class, () -> correctJsonObjectBasicCopy.get("boolean_key_2")); + } + + /** + * Wraps all the alphanumeric words in a string in quotes + */ + private static String removeQuotes(String string) { + return string.replaceAll("\"([a-zA-Z0-9]+)\"", "$1"); + } + + private static class JSONObjectSubType extends JSONObject { + /** + * Sample overridden function + */ + @NonNull + @Override + public String toString() { + return removeQuotes(super.toString()); + } + } + + @Test + public void deepCloneTest() { + JSONObjectSubType jsonObjectSubType = new JSONObjectSubType(); + + // Clone base JSONObject Type into JSONObjectSubType + correctJsonObjectNestedWithArray.deepClonedInto(jsonObjectSubType); + + // Test by passing result of base JSONObject's toString() to removeQuotes() + // This is already done in the JSONObjectSubType object + Assert.assertEquals(removeQuotes(correctJsonObjectNestedWithArray.toString()), jsonObjectSubType.toString()); + } + + @Test + public void fromMapTest() { + JSONObject fromMapJsonObject = JSONObject.fromMap(booleanMap); + for (int i = 0 ; i < 10 ; ++i) { + Assert.assertEquals(fromMapJsonObject.getBoolean("key" + i), i%2 == 0); + } + } } From 164200255997ac4750493118c24cec649191b630 Mon Sep 17 00:00:00 2001 From: Piyush Goel <46752548+Arnold2381@users.noreply.github.com> Date: Sat, 10 Apr 2021 18:23:08 +0530 Subject: [PATCH 098/171] m prefix non-public, non-static fields #8387 (#8532) * Changed variables names according to the new lint rule. ("Lint: Enforce 'm' prefix rule for fields") --- .../java/com/ichi2/anki/ModelBrowser.java | 16 +++--- .../main/java/com/ichi2/anki/MyAccount.java | 6 +- .../ichi2/anki/NavigationDrawerActivity.java | 10 ++-- .../main/java/com/ichi2/anki/Preferences.java | 18 +++--- .../main/java/com/ichi2/anki/Reviewer.java | 56 +++++++++---------- .../com/ichi2/anki/StudyOptionsFragment.java | 5 +- .../main/java/com/ichi2/anki/Whiteboard.java | 12 ++-- .../com/ichi2/anki/dialogs/ExportDialog.java | 6 +- .../anki/dialogs/RecursivePictureMenu.java | 11 ++-- .../activity/MultimediaEditFieldActivity.java | 14 ++--- .../activity/TranslationActivity.java | 16 +++--- .../multimediacard/glosbe/json/Response.java | 30 +++++----- .../anki/multimediacard/glosbe/json/Tuc.java | 24 ++++---- .../anki/reviewer/PeripheralCommand.java | 8 +-- .../com/ichi2/anki/stats/StatsMetaInfo.java | 12 ++-- .../java/com/ichi2/async/TaskManager.java | 27 +++------ .../ichi2/libanki/AnkiPackageExporter.java | 4 +- .../main/java/com/ichi2/libanki/Models.java | 20 +++---- .../main/java/com/ichi2/libanki/Sound.java | 8 +-- .../java/com/ichi2/libanki/StdModels.java | 12 ++-- .../libanki/sync/Tls12SocketFactory.java | 18 +++--- .../libanki/sync/UnifiedTrustManager.java | 20 +++---- .../com/ichi2/preferences/TimePreference.java | 24 ++++---- 23 files changed, 181 insertions(+), 196 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java index 2f2a5caf8c68..b34bd685bfb7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java @@ -75,7 +75,7 @@ public class ModelBrowser extends AnkiActivity { private ArrayList mModelIds; private ArrayList mModelDisplayList; - private Collection col; + private Collection mCol; private ActionBar mActionBar; //Dialogue used in renaming @@ -240,7 +240,7 @@ public void onDestroy() { @Override public void onCollectionLoaded(Collection col) { super.onCollectionLoaded(col); - this.col = col; + this.mCol = col; TaskManager.launchCollectionTask(new CollectionTask.CountModels(), loadingModelsHandler()); } @@ -384,17 +384,17 @@ private void addNewNoteType(String modelName, int position) { if (modelName.length() > 0) { int nbStdModels = StdModels.STD_MODELS.length; if (position < nbStdModels) { - model = StdModels.STD_MODELS[position].add(col); + model = StdModels.STD_MODELS[position].add(mCol); } else { //New model //Model that is being cloned Model oldModel = mModels.get(position - nbStdModels).deepClone(); - Model newModel = StdModels.BASIC_MODEL.add(col); + Model newModel = StdModels.BASIC_MODEL.add(mCol); oldModel.put("id", newModel.getLong("id")); model = oldModel; } model.put("name", modelName); - col.getModels().update(model); + mCol.getModels().update(model); fullRefresh(); } else { showToast(getResources().getString(R.string.toast_empty_name)); @@ -408,14 +408,14 @@ private void addNewNoteType(String modelName, int position) { private void deleteModelDialog() { if (mModelIds.size() > 1) { Runnable confirm = () -> { - col.modSchemaNoCheck(); + mCol.modSchemaNoCheck(); deleteModel(); dismissContextMenu(); }; Runnable cancel = this::dismissContextMenu; try { - col.modSchema(); + mCol.modSchema(); ConfirmationDialog d = new ConfirmationDialog(); d.setArgs(getResources().getString(R.string.model_delete_warning)); d.setConfirm(confirm); @@ -457,7 +457,7 @@ private void renameModelDialog() { .replaceAll("[\"\\n\\r]", ""); if (deckName.length() > 0) { model.put("name", deckName); - col.getModels().update(model); + mCol.getModels().update(model); mModels.get(mModelListPosition).put("name", deckName); mModelDisplayList.set(mModelListPosition, new DisplayPair(mModels.get(mModelListPosition).getString("name"), diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java b/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java index 4283fd4a912c..f2ca441663af 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/MyAccount.java @@ -130,7 +130,7 @@ public void attemptLogin() { if (!"".equalsIgnoreCase(username) && !"".equalsIgnoreCase(password)) { Timber.i("Attempting auto-login"); - Connection.login(loginListener, new Connection.Payload(new Object[]{username, password, + Connection.login(mLoginListener, new Connection.Payload(new Object[]{username, password, HostNumFactory.getInstance(this) })); } else { Timber.i("Auto-login cancelled - username/password missing"); @@ -168,7 +168,7 @@ private void login() { } if (!"".equalsIgnoreCase(username) && !"".equalsIgnoreCase(password)) { - Connection.login(loginListener, new Connection.Payload(new Object[]{username, password, + Connection.login(mLoginListener, new Connection.Payload(new Object[]{username, password, HostNumFactory.getInstance(this) })); } else { UIUtils.showSimpleSnackbar(this, R.string.invalid_username_password, true); @@ -245,7 +245,7 @@ private void initAllContentViews() { /** * Listeners */ - final Connection.TaskListener loginListener = new Connection.TaskListener() { + final Connection.TaskListener mLoginListener = new Connection.TaskListener() { @Override public void onProgressUpdate(Object... values) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java index d75c43be1b05..2f8431bf5151 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NavigationDrawerActivity.java @@ -70,7 +70,7 @@ public abstract class NavigationDrawerActivity extends AnkiActivity implements N /** * runnable that will be executed after the drawer has been closed. */ - private Runnable pendingRunnable; + private Runnable mPendingRunnable; // Navigation drawer initialisation protected void initNavigationDrawer(View mainView) { @@ -111,9 +111,9 @@ public void onDrawerClosed(View drawerView) { // If animations are disabled, this is executed before onNavigationItemSelected is called // PERF: May be able to reduce this delay new Handler().postDelayed(() -> { - if (pendingRunnable != null) { - new Handler().post(pendingRunnable); - pendingRunnable = null; + if (mPendingRunnable != null) { + new Handler().post(mPendingRunnable); + mPendingRunnable = null; } }, 100); @@ -293,7 +293,7 @@ public boolean onNavigationItemSelected(final MenuItem item) { * This runnable will be executed in onDrawerClosed(...) * to make the animation more fluid on older devices. */ - pendingRunnable = () -> { + mPendingRunnable = () -> { // Take action if a different item selected int itemId = item.getItemId(); if (itemId == R.id.nav_decks) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java index a97255fe9151..c560f7c3bbe6 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java @@ -32,7 +32,6 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.PackageManager; import android.database.Cursor; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; @@ -86,7 +85,6 @@ import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; import timber.log.Timber; import static com.ichi2.anim.ActivityTransitionAnimation.Direction.FADE; @@ -115,7 +113,7 @@ public class Preferences extends AppCompatPreferenceActivity implements Preferen "learnCutoff", "timeLimit", "useCurrent", "newSpread", "dayOffset", "schedVer"}; private static final int RESULT_LOAD_IMG = 111; - private android.preference.CheckBoxPreference backgroundImage; + private android.preference.CheckBoxPreference mBackgroundImage; private static long fileLength; @@ -229,18 +227,18 @@ private void initSubscreen(String action, PreferenceContext listener) { case "com.ichi2.anki.prefs.appearance": listener.addPreferencesFromResource(R.xml.preferences_appearance); screen = listener.getPreferenceScreen(); - backgroundImage = (android.preference.CheckBoxPreference) screen.findPreference("deckPickerBackground"); - backgroundImage.setOnPreferenceClickListener(preference -> { - if (backgroundImage.isChecked()) { + mBackgroundImage = (android.preference.CheckBoxPreference) screen.findPreference("deckPickerBackground"); + mBackgroundImage.setOnPreferenceClickListener(preference -> { + if (mBackgroundImage.isChecked()) { try { Intent galleryIntent = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(galleryIntent, RESULT_LOAD_IMG); - backgroundImage.setChecked(true); + mBackgroundImage.setChecked(true); } catch (Exception ex) { Timber.e("%s",ex.getLocalizedMessage()); } } else { - backgroundImage.setChecked(false); + mBackgroundImage.setChecked(false); String currentAnkiDroidDirectory = CollectionHelper.getCurrentAnkiDroidDirectory(this); File imgFile = new File(currentAnkiDroidDirectory, "DeckPickerBackground.png" ); if (imgFile.exists()) { @@ -486,12 +484,12 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { UIUtils.showThemedToast(this, getString(R.string.background_image_applied), false); } } else { - backgroundImage.setChecked(false); + mBackgroundImage.setChecked(false); UIUtils.showThemedToast(this, getString(R.string.image_max_size_allowed, 10), false); } } } else { - backgroundImage.setChecked(false); + mBackgroundImage.setChecked(false); UIUtils.showThemedToast(this, getString(R.string.no_image_selected), false); } } catch (OutOfMemoryError | Exception e) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index 8b4e61fb1455..4e4d9ed2d3e4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -63,8 +63,6 @@ import com.ichi2.anki.cardviewer.CardAppearance; import com.ichi2.anki.dialogs.ConfirmationDialog; -import com.ichi2.anki.multimediacard.AudioPlayer; -import com.ichi2.anki.multimediacard.AudioRecorder; import com.ichi2.anki.multimediacard.AudioView; import com.ichi2.anki.dialogs.RescheduleDialog; import com.ichi2.anki.reviewer.PeripheralKeymap; @@ -103,13 +101,13 @@ public class Reviewer extends AbstractFlashcardViewer { private boolean mPrefFullscreenReview = false; private static final int ADD_NOTE = 12; private static final int REQUEST_AUDIO_PERMISSION = 0; - private LinearLayout colorPalette; + private LinearLayout mColorPalette; // TODO: Consider extracting to ViewModel // Card counts - private SpannableString newCount; - private SpannableString lrnCount; - private SpannableString revCount; + private SpannableString mNewCount; + private SpannableString mLrnCount; + private SpannableString mRevCount; private TextView mTextBarNew; private TextView mTextBarLearn; @@ -118,7 +116,7 @@ public class Reviewer extends AbstractFlashcardViewer { private boolean mPrefHideDueCount; // ETA - private int eta; + private int mEta; private boolean mPrefShowETA; @@ -178,7 +176,7 @@ protected void onCreate(Bundle savedInstanceState) { selectDeckFromExtra(); } - colorPalette = findViewById(R.id.whiteboard_pen_color); + mColorPalette = findViewById(R.id.whiteboard_pen_color); startLoadingCollection(); } @@ -362,10 +360,10 @@ public boolean onOptionsItemSelected(MenuItem item) { showDeleteNoteDialog(); } else if (itemId == R.id.action_change_whiteboard_pen_color) { Timber.i("Reviewer:: Pen Color button pressed"); - if (colorPalette.getVisibility() == View.GONE) { - colorPalette.setVisibility(View.VISIBLE); + if (mColorPalette.getVisibility() == View.GONE) { + mColorPalette.setVisibility(View.VISIBLE); } else { - colorPalette.setVisibility(View.GONE); + mColorPalette.setVisibility(View.GONE); } } else if (itemId == R.id.action_save_whiteboard) { Timber.i("Reviewer:: Save whiteboard button pressed"); @@ -435,7 +433,7 @@ protected void toggleWhiteboard() { setWhiteboardEnabledState(mPrefWhiteboard); setWhiteboardVisibility(mPrefWhiteboard); if (!mPrefWhiteboard) { - colorPalette.setVisibility(View.GONE); + mColorPalette.setVisibility(View.GONE); } refreshActionBar(); } @@ -663,7 +661,7 @@ public boolean onCreateOptionsMenu(Menu menu) { whiteboardColorPaletteIcon.setAlpha(Themes.ALPHA_ICON_DISABLED_LIGHT); change_pen_color_icon.setEnabled(false); change_pen_color_icon.setIcon(whiteboardColorPaletteIcon); - colorPalette.setVisibility(View.GONE); + mColorPalette.setVisibility(View.GONE); } } else { toggle_whiteboard_icon.setTitle(R.string.enable_whiteboard); @@ -941,37 +939,37 @@ protected void updateScreenCounts() { if (actionBar != null) { if (mPrefShowETA) { - eta = mSched.eta(counts, false); - actionBar.setSubtitle(Utils.remainingTime(AnkiDroidApp.getInstance(), eta * 60)); + mEta = mSched.eta(counts, false); + actionBar.setSubtitle(Utils.remainingTime(AnkiDroidApp.getInstance(), mEta * 60)); } } - newCount = new SpannableString(String.valueOf(counts.getNew())); - lrnCount = new SpannableString(String.valueOf(counts.getLrn())); - revCount = new SpannableString(String.valueOf(counts.getRev())); + mNewCount = new SpannableString(String.valueOf(counts.getNew())); + mLrnCount = new SpannableString(String.valueOf(counts.getLrn())); + mRevCount = new SpannableString(String.valueOf(counts.getRev())); if (mPrefHideDueCount) { - revCount = new SpannableString("???"); + mRevCount = new SpannableString("???"); } switch (mSched.countIdx(mCurrentCard)) { case NEW: - newCount.setSpan(new UnderlineSpan(), 0, newCount.length(), 0); + mNewCount.setSpan(new UnderlineSpan(), 0, mNewCount.length(), 0); break; case LRN: - lrnCount.setSpan(new UnderlineSpan(), 0, lrnCount.length(), 0); + mLrnCount.setSpan(new UnderlineSpan(), 0, mLrnCount.length(), 0); break; case REV: - revCount.setSpan(new UnderlineSpan(), 0, revCount.length(), 0); + mRevCount.setSpan(new UnderlineSpan(), 0, mRevCount.length(), 0); break; default: Timber.w("Unknown card type %s", mSched.countIdx(mCurrentCard)); break; } - mTextBarNew.setText(newCount); - mTextBarLearn.setText(lrnCount); - mTextBarReview.setText(revCount); + mTextBarNew.setText(mNewCount); + mTextBarLearn.setText(mLrnCount); + mTextBarReview.setText(mRevCount); } @Override @@ -1438,25 +1436,25 @@ public class ReviewerJavaScriptFunction extends JavaScriptFunction { @JavascriptInterface @Override public String ankiGetNewCardCount() { - return newCount.toString(); + return mNewCount.toString(); } @JavascriptInterface @Override public String ankiGetLrnCardCount() { - return lrnCount.toString(); + return mLrnCount.toString(); } @JavascriptInterface @Override public String ankiGetRevCardCount() { - return revCount.toString(); + return mRevCount.toString(); } @JavascriptInterface @Override public int ankiGetETA() { - return eta; + return mEta; } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java index 16c1c1691141..cbe20b17a22b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java @@ -54,7 +54,6 @@ import com.ichi2.themes.StyledProgressDialog; import com.ichi2.utils.BooleanGetter; import com.ichi2.utils.HtmlUtils; -import com.ichi2.utils.UiUtil; import timber.log.Timber; @@ -301,7 +300,7 @@ void setFragmentContentView(View newView) { parent.addView(newView); } - private final TaskListener undoListener = new TaskListener() { + private final TaskListener mUndoListener = new TaskListener() { @Override public void onPreExecute() { @@ -320,7 +319,7 @@ public boolean onMenuItemClick(MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.action_undo) { Timber.i("StudyOptionsFragment:: Undo button pressed"); - TaskManager.launchCollectionTask(new CollectionTask.Undo(), undoListener); + TaskManager.launchCollectionTask(new CollectionTask.Undo(), mUndoListener); return true; } else if (itemId == R.id.action_deck_or_study_options) { Timber.i("StudyOptionsFragment:: Deck or study options button pressed"); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Whiteboard.java b/AnkiDroid/src/main/java/com/ichi2/anki/Whiteboard.java index 25b8c171da5e..9839e2295324 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Whiteboard.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Whiteboard.java @@ -85,7 +85,7 @@ public class Whiteboard extends View { private boolean mSecondFingerWithinTapTolerance; private boolean mCurrentlyDrawing = false; private boolean mUndoModeActive = false; - private final int foregroundColor; + private final int mForegroundColor; private final LinearLayout mColorPalette; @Nullable @@ -101,17 +101,17 @@ public Whiteboard(AbstractFlashcardViewer cardViewer, boolean inverted) { if (!inverted) { whitePenColorButton.setVisibility(View.GONE); blackPenColorButton.setOnClickListener(this::onClick); - foregroundColor = Color.BLACK; + mForegroundColor = Color.BLACK; } else { blackPenColorButton.setVisibility(View.GONE); whitePenColorButton.setOnClickListener(this::onClick); - foregroundColor = Color.WHITE; + mForegroundColor = Color.WHITE; } mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); - mPaint.setColor(foregroundColor); + mPaint.setColor(mForegroundColor); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); @@ -592,7 +592,7 @@ protected String saveWhiteboard(Time time) throws FileNotFoundException { File saveWhiteboardImageFile = new File(ankiDroidFolder, finalFileName); - if (foregroundColor != Color.BLACK) { + if (mForegroundColor != Color.BLACK) { canvas.drawColor(Color.BLACK); } else { canvas.drawColor(Color.WHITE); @@ -606,7 +606,7 @@ protected String saveWhiteboard(Time time) throws FileNotFoundException { @VisibleForTesting @CheckResult protected int getForegroundColor() { - return foregroundColor; + return mForegroundColor; } public interface OnPaintColorChangeListener { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportDialog.java index f903c4db67ce..728e4daa9507 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/ExportDialog.java @@ -19,8 +19,8 @@ public interface ExportDialogListener { void dismissAllDialogFragments(); } - private final int INCLUDE_SCHED = 0; - private final int INCLUDE_MEDIA = 1; + private final static int INCLUDE_SCHED = 0; + private final static int INCLUDE_MEDIA = 1; private boolean mIncludeSched = false; private boolean mIncludeMedia = false; @@ -65,7 +65,7 @@ public MaterialDialog onCreateDialog(Bundle savedInstanceState) { checked = new Integer[]{}; } else { mIncludeSched = true; - checked = new Integer[]{ INCLUDE_SCHED }; + checked = new Integer[]{INCLUDE_SCHED}; } final String[] items = { res.getString(R.string.export_include_schedule), res.getString(R.string.export_include_media) }; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/RecursivePictureMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/RecursivePictureMenu.java index f44e301df60d..6ef005111ff1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/RecursivePictureMenu.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/RecursivePictureMenu.java @@ -17,7 +17,6 @@ package com.ichi2.anki.dialogs; import android.app.Dialog; -import android.content.Context; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcel; @@ -147,12 +146,12 @@ public abstract static class Item implements Parcelable { private final int mText; @DrawableRes private final int mIcon; - private final String analyticsId; + private final String mAnalyticsId; public Item(@StringRes int titleString, @DrawableRes int iconDrawable, String analyticsId) { this.mText = titleString; this.mIcon = iconDrawable; - this.analyticsId = analyticsId; + this.mAnalyticsId = analyticsId; } public List getChildren() { @@ -162,7 +161,7 @@ public List getChildren() { protected Item(Parcel in) { mText = in.readInt(); mIcon = in.readInt(); - analyticsId = in.readString(); + mAnalyticsId = in.readString(); } @StringRes @@ -179,13 +178,13 @@ public int describeContents() { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mText); dest.writeInt(mIcon); - dest.writeString(analyticsId); + dest.writeString(mAnalyticsId); } protected abstract void onClicked(AnkiActivity activity); public final void sendAnalytics() { - UsageAnalytics.sendAnalyticsEvent(UsageAnalytics.Category.LINK_CLICKED, analyticsId); + UsageAnalytics.sendAnalyticsEvent(UsageAnalytics.Category.LINK_CLICKED, mAnalyticsId); } /* This method calls onClicked method to handle click event in a suitable manner and diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java index 81f888c51d48..86bba6047870 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java @@ -434,8 +434,8 @@ protected void onSaveInstanceState(Bundle outState) { /** Intermediate class to hold state for the onRequestPermissionsResult callback */ private final static class ChangeUIRequest { - private final IField newField; - private final int state; + private final IField mNewField; + private final int mState; private boolean mRequiresPermissionCheck = true; /** Initial request when activity is created */ @@ -446,12 +446,12 @@ private final static class ChangeUIRequest { public static final int EXTERNAL_FIELD_CHANGE = 2; private ChangeUIRequest(IField field, int state) { - this.newField = field; - this.state = state; + this.mNewField = field; + this.mState = state; } private IField getField() { - return newField; + return mNewField; } private static ChangeUIRequest init(@NonNull IField field) { @@ -475,7 +475,7 @@ private void markAsPermissionRequested() { } private int getState() { - return state; + return mState; } } @@ -534,7 +534,7 @@ private static void onPostUICreation(ChangeUIRequest request, MultimediaEditFiel private static void onRequiredPermissionDenied(ChangeUIRequest request, MultimediaEditFieldActivity activity) { Timber.d("onRequiredPermissionDenied. State: %d", request.getState()); - switch (request.state) { + switch (request.mState) { case ChangeUIRequest.ACTIVITY_LOAD: activity.finishCancel(); break; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java index ca55c3186828..a30edb64d95d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java @@ -78,7 +78,7 @@ public class TranslationActivity extends FragmentActivity implements DialogInter private Spinner mSpinnerFrom; private Spinner mSpinnerTo; @SuppressWarnings("deprecation") // tracked in github as #5020 - private android.app.ProgressDialog progressDialog = null; + private android.app.ProgressDialog mProgressDialog = null; private String mWebServiceAddress; private ArrayList mPossibleTranslations; private String mLangCodeTo; @@ -193,7 +193,7 @@ protected String doInBackground(Void... params) { @Override protected void onPostExecute(String result) { - progressDialog.dismiss(); + mProgressDialog.dismiss(); mTranslation = result; showPickTranslationDialog(); } @@ -208,11 +208,11 @@ protected void translate() { return; } - progressDialog = android.app.ProgressDialog.show(this, getText(R.string.multimedia_editor_progress_wait_title), + mProgressDialog = android.app.ProgressDialog.show(this, getText(R.string.multimedia_editor_progress_wait_title), getText(R.string.multimedia_editor_trans_translating_online), true, false); - progressDialog.setCancelable(true); - progressDialog.setOnCancelListener(this); + mProgressDialog.setCancelable(true); + mProgressDialog.setOnCancelListener(this); mWebServiceAddress = computeAddress(); @@ -221,7 +221,7 @@ protected void translate() { mTranslationLoadPost.execute(); } catch (Exception e) { Timber.w(e); - progressDialog.dismiss(); + mProgressDialog.dismiss(); showToast(getText(R.string.multimedia_editor_something_wrong)); } } @@ -427,8 +427,8 @@ protected void onPause() { private void dismissCarefullyProgressDialog() { try { - if ((progressDialog != null) && progressDialog.isShowing()) { - progressDialog.dismiss(); + if ((mProgressDialog != null) && mProgressDialog.isShowing()) { + mProgressDialog.dismiss(); } } catch (Exception e) { Timber.w(e); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Response.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Response.java index 7464fd49bbda..997346d2e0ff 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Response.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Response.java @@ -26,59 +26,59 @@ * the root class, from which response starts. */ public class Response { - private String dest; - private String from; - private String phrase; - private String result; - private List tuc; + private String mDest; + private String mFrom; + private String mPhrase; + private String mResult; + private List mTuc; public String getDest() { - return this.dest; + return this.mDest; } public void setDest(String dest) { - this.dest = dest; + this.mDest = dest; } public String getFrom() { - return this.from; + return this.mFrom; } public void setFrom(String from) { - this.from = from; + this.mFrom = from; } public String getPhrase() { - return this.phrase; + return this.mPhrase; } public void setPhrase(String phrase) { - this.phrase = phrase; + this.mPhrase = phrase; } public String getResult() { - return this.result; + return this.mResult; } public void setResult(String result) { - this.result = result; + this.mResult = result; } public List getTuc() { - return this.tuc; + return this.mTuc; } public void setTuc(List tuc) { - this.tuc = tuc; + this.mTuc = tuc; } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Tuc.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Tuc.java index 5fdf47ffb6f4..a3be1508b621 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Tuc.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/glosbe/json/Tuc.java @@ -25,48 +25,48 @@ * This is one of the classes, automatically generated to transform json replies from glosbe.com */ public class Tuc { - private List authors; - private Number meaningId; - private List meanings; - private Phrase phrase; + private List mAuthors; + private Number mMeaningId; + private List mMeanings; + private Phrase mPhrase; public List getAuthors() { - return this.authors; + return this.mAuthors; } public void setAuthors(List authors) { - this.authors = authors; + this.mAuthors = authors; } public Number getMeaningId() { - return this.meaningId; + return this.mMeaningId; } public void setMeaningId(Number meaningId) { - this.meaningId = meaningId; + this.mMeaningId = meaningId; } public List getMeanings() { - return this.meanings; + return this.mMeanings; } public void setMeanings(List meanings) { - this.meanings = meanings; + this.mMeanings = meanings; } public Phrase getPhrase() { - return this.phrase; + return this.mPhrase; } public void setPhrase(Phrase phrase) { - this.phrase = phrase; + this.mPhrase = phrase; } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralCommand.java b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralCommand.java index 62f059a23b49..5559bddcfd31 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralCommand.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/PeripheralCommand.java @@ -38,7 +38,7 @@ public class PeripheralCommand { private final @ViewerCommandDef int mCommand; - private final ModifierKeys modifierKeys; + private final ModifierKeys mModifierKeys; private PeripheralCommand(int keyCode, @ViewerCommandDef int command, @NonNull CardSide side, ModifierKeys modifierKeys) { @@ -46,11 +46,11 @@ private PeripheralCommand(int keyCode, @ViewerCommandDef int command, @NonNull C this.mUnicodeCharacter = null; this.mCommand = command; this.mCardSide = side; - this.modifierKeys = modifierKeys; + this.mModifierKeys = modifierKeys; } private PeripheralCommand(@Nullable Character unicodeCharacter, @ViewerCommandDef int command, @NonNull CardSide side, ModifierKeys modifierKeys) { - this.modifierKeys = modifierKeys; + this.mModifierKeys = modifierKeys; this.mKeyCode = null; this.mUnicodeCharacter = unicodeCharacter; this.mCommand = command; @@ -148,7 +148,7 @@ public static List getDefaultCommands() { public boolean matchesModifier(KeyEvent event) { - return modifierKeys.matches(event); + return mModifierKeys.matches(event); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/stats/StatsMetaInfo.java b/AnkiDroid/src/main/java/com/ichi2/anki/stats/StatsMetaInfo.java index 0e6d3b87fada..5d31f11f218d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/stats/StatsMetaInfo.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/stats/StatsMetaInfo.java @@ -40,15 +40,15 @@ public class StatsMetaInfo { double[][] mSeriesList; - boolean statsCalculated; - boolean dataAvailable; + boolean mStatsCalculated; + boolean mDataAvailable; public boolean isStatsCalculated() { - return statsCalculated; + return mStatsCalculated; } public void setStatsCalculated(boolean statsCalculated) { - this.statsCalculated = statsCalculated; + this.mStatsCalculated = statsCalculated; } public double[][] getmSeriesList() { @@ -60,11 +60,11 @@ public void setmSeriesList(double[][] mSeriesList) { } public boolean isDataAvailable() { - return dataAvailable; + return mDataAvailable; } public void setDataAvailable(boolean dataAvailable) { - this.dataAvailable = dataAvailable; + this.mDataAvailable = dataAvailable; } public boolean ismDynamicAxis() { diff --git a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java index faf215e66d96..e3f7d6e88ace 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/TaskManager.java @@ -1,21 +1,10 @@ package com.ichi2.async; import android.content.res.Resources; -import android.os.AsyncTask; - -import com.ichi2.libanki.Collection; -import com.ichi2.utils.ThreadUtil; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import timber.log.Timber; public abstract class TaskManager { @NonNull private static TaskManager sTaskManager = new SingleTaskManager(); @@ -130,28 +119,28 @@ public static boolean waitForAllToFinish(Integer timeoutSeconds) { * Helper class for allowing inner function to publish progress of an AsyncTask. */ public static class ProgressCallback { - private final Resources res; - private final ProgressSender task; + private final Resources mRes; + private final ProgressSender mTask; protected ProgressCallback(ProgressSender task, Resources res) { - this.res = res; + this.mRes = res; if (res != null) { - this.task = task; + this.mTask = task; } else { - this.task = null; + this.mTask = null; } } public Resources getResources() { - return res; + return mRes; } public void publishProgress(Progress value) { - if (task != null) { - task.doProgress(value); + if (mTask != null) { + mTask.doProgress(value); } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java b/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java index 2d09d5191d9e..bd2f9077a22f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/AnkiPackageExporter.java @@ -16,6 +16,7 @@ package com.ichi2.libanki; +import android.annotation.SuppressLint; import android.content.Context; import android.database.sqlite.SQLiteDatabase; @@ -161,6 +162,7 @@ class AnkiExporter extends Exporter { String mMediaDir; // Actual capacity will be set when known, if media are imported. final ArrayList mMediaFiles = new ArrayList<>(0); + @SuppressLint("FieldNamingPatternDetector") boolean _v2sched; @@ -567,7 +569,7 @@ private enum ValidateFiles { * @author Tim */ class ZipFile { - private final int BUFFER_SIZE = 1024; + private static final int BUFFER_SIZE = 1024; private ZipArchiveOutputStream mZos; diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java index 9a39c1f51646..3c6a626f5e51 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Models.java @@ -533,18 +533,18 @@ public void remField(Model m, JSONObject field) throws ConfirmModSchemaException } static class TransformFieldDelete implements TransformFieldVisitor { - private final int idx; + private final int mIdx; public TransformFieldDelete(int _idx) { - idx = _idx; + mIdx = _idx; } @Override public String[] transform(String[] fields) { ArrayList fl = new ArrayList<>(Arrays.asList(fields)); - fl.remove(idx); + fl.remove(mIdx); return fl.toArray(new String[fl.size()]); } } @@ -585,22 +585,22 @@ public void moveField(Model m, JSONObject field, int idx) throws ConfirmModSchem } static class TransformFieldMove implements TransformFieldVisitor { - private final int idx; - private final int oldidx; + private final int mIdx; + private final int mOldidx; public TransformFieldMove(int _idx, int _oldidx) { - idx = _idx; - oldidx = _oldidx; + mIdx = _idx; + mOldidx = _oldidx; } @Override public String[] transform(String[] fields) { - String val = fields[oldidx]; + String val = fields[mOldidx]; ArrayList fl = new ArrayList<>(Arrays.asList(fields)); - fl.remove(oldidx); - fl.add(idx, val); + fl.remove(mOldidx); + fl.add(mIdx, val); return fl.toArray(new String[fl.size()]); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java index 513950dc8715..8dde3e4128b6 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Sound.java @@ -396,17 +396,17 @@ public void notifyConfigurationChanged(VideoView videoView) { /** #5414 - Ensures playing a single sound performs cleanup */ private final class SingleSoundCompletionListener implements OnCompletionListener { @Nullable - private final OnCompletionListener userCallback; + private final OnCompletionListener mUserCallback; public SingleSoundCompletionListener(@Nullable OnCompletionListener userCallback) { - this.userCallback = userCallback; + this.mUserCallback = userCallback; } @Override public void onCompletion(MediaPlayer mp) { Timber.d("Single Sound completed"); - if (userCallback != null) { - userCallback.onCompletion(mp); + if (mUserCallback != null) { + mUserCallback.onCompletion(mp); } else { releaseSound(); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java b/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java index 1ac28aad70d4..2ad2b41c698a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/StdModels.java @@ -10,19 +10,19 @@ public class StdModels { /** Essentially, the default name. As a resource, so that it can * be localized later. */ @StringRes - private final int defaultName; + private final int mDefaultName; /** * Funtion creating the standard model. Needs to be a funtion to take the local language into account. */ - private final CreateStdModels fun; + private final CreateStdModels mFun; interface CreateStdModels { Model create(Models mm, String name); } public StdModels(CreateStdModels fun, @StringRes int defaultName) { - this.fun = fun; - this.defaultName = defaultName; + this.mFun = fun; + this.mDefaultName = defaultName; } private Model _new(Models mm) { @@ -31,7 +31,7 @@ private Model _new(Models mm) { } private Model _new(Models mm, String name) { - return fun.create(mm, name); + return mFun.create(mm, name); } public Model add(Collection col, String name) { @@ -49,7 +49,7 @@ public Model add(Collection col) { } public String getDefaultName() { - return AnkiDroidApp.getAppResources().getString(defaultName); + return AnkiDroidApp.getAppResources().getString(mDefaultName); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Tls12SocketFactory.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Tls12SocketFactory.java index 2abe5b60fd44..06c659b0c780 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Tls12SocketFactory.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/Tls12SocketFactory.java @@ -52,7 +52,7 @@ public class Tls12SocketFactory extends SSLSocketFactory { private static final String[] TLS_V12_ONLY = {"TLSv1.2"}; - private final SSLSocketFactory delegate; + private final SSLSocketFactory mDelegate; public static OkHttpClient.Builder enableTls12OnPreLollipop(OkHttpClient.Builder client) { @@ -101,49 +101,49 @@ private static Certificate getUserTrustRootCertificate() throws CertificateExcep private Tls12SocketFactory(SSLSocketFactory base) { - this.delegate = base; + this.mDelegate = base; } @Override public String[] getDefaultCipherSuites() { - return delegate.getDefaultCipherSuites(); + return mDelegate.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); + return mDelegate.getSupportedCipherSuites(); } @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { - return patch(delegate.createSocket(s, host, port, autoClose)); + return patch(mDelegate.createSocket(s, host, port, autoClose)); } @Override public Socket createSocket(String host, int port) throws IOException { - return patch(delegate.createSocket(host, port)); + return patch(mDelegate.createSocket(host, port)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { - return patch(delegate.createSocket(host, port, localHost, localPort)); + return patch(mDelegate.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { - return patch(delegate.createSocket(host, port)); + return patch(mDelegate.createSocket(host, port)); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return patch(delegate.createSocket(address, port, localAddress, localPort)); + return patch(mDelegate.createSocket(address, port, localAddress, localPort)); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/UnifiedTrustManager.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/UnifiedTrustManager.java index 536590fcc274..c0a86ddd9681 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sync/UnifiedTrustManager.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sync/UnifiedTrustManager.java @@ -35,15 +35,15 @@ // Cached the accepted issuers. // Did not ignore NoSuchAlgorithmException class UnifiedTrustManager implements X509TrustManager { - private X509TrustManager defaultTrustManager; - private X509TrustManager localTrustManager; + private X509TrustManager mDefaultTrustManager; + private X509TrustManager mLocalTrustManager; private X509Certificate[] mAcceptedIssuers; public UnifiedTrustManager(KeyStore localKeyStore) throws KeyStoreException, NoSuchAlgorithmException { - this.defaultTrustManager = createTrustManager(null); - this.localTrustManager = createTrustManager(localKeyStore); - X509Certificate[] first = defaultTrustManager.getAcceptedIssuers(); - X509Certificate[] second = localTrustManager.getAcceptedIssuers(); + this.mDefaultTrustManager = createTrustManager(null); + this.mLocalTrustManager = createTrustManager(localKeyStore); + X509Certificate[] first = mDefaultTrustManager.getAcceptedIssuers(); + X509Certificate[] second = mLocalTrustManager.getAcceptedIssuers(); mAcceptedIssuers = Arrays.copyOf(first, first.length + second.length); System.arraycopy(second, 0, mAcceptedIssuers, first.length, second.length); } @@ -58,19 +58,19 @@ private X509TrustManager createTrustManager(KeyStore store) throws NoSuchAlgorit public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { - localTrustManager.checkServerTrusted(chain, authType); + mLocalTrustManager.checkServerTrusted(chain, authType); } catch (CertificateException ce) { Timber.w(ce); - defaultTrustManager.checkServerTrusted(chain, authType); + mDefaultTrustManager.checkServerTrusted(chain, authType); } } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { try { - localTrustManager.checkClientTrusted(chain, authType); + mLocalTrustManager.checkClientTrusted(chain, authType); } catch (CertificateException ce) { Timber.w(ce); - defaultTrustManager.checkClientTrusted(chain, authType); + mDefaultTrustManager.checkClientTrusted(chain, authType); } } @Override diff --git a/AnkiDroid/src/main/java/com/ichi2/preferences/TimePreference.java b/AnkiDroid/src/main/java/com/ichi2/preferences/TimePreference.java index e17c07de857b..af9d752f1063 100644 --- a/AnkiDroid/src/main/java/com/ichi2/preferences/TimePreference.java +++ b/AnkiDroid/src/main/java/com/ichi2/preferences/TimePreference.java @@ -12,9 +12,9 @@ public class TimePreference extends android.preference.DialogPreference { public static final String DEFAULT_VALUE = "00:00"; - private TimePicker timePicker; - private int hours; - private int minutes; + private TimePicker mTimepicker; + private int mHours; + private int mMinutes; public TimePreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -25,11 +25,11 @@ public TimePreference(Context context, AttributeSet attrs) { @Override protected View onCreateDialogView() { - timePicker = new TimePicker(getContext()); + mTimepicker = new TimePicker(getContext()); - timePicker.setIs24HourView(true); + mTimepicker.setIs24HourView(true); - return timePicker; + return mTimepicker; } @Override @@ -46,14 +46,14 @@ protected void onSetInitialValue(boolean restorePersistedValue, Object defaultVa time = defaultValue.toString(); } - hours = parseHours(time); - minutes = parseMinutes(time); + mHours = parseHours(time); + mMinutes = parseMinutes(time); } @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); - CompatHelper.getCompat().setTime(timePicker, hours, minutes); + CompatHelper.getCompat().setTime(mTimepicker, mHours, mMinutes); } @Override @@ -61,10 +61,10 @@ protected void onDialogClosed(boolean positiveResult) { super.onDialogClosed(positiveResult); if (positiveResult) { - hours = CompatHelper.getCompat().getHour(timePicker); - minutes = CompatHelper.getCompat().getMinute(timePicker); + mHours = CompatHelper.getCompat().getHour(mTimepicker); + mMinutes = CompatHelper.getCompat().getMinute(mTimepicker); - final String time = String.format("%1$02d:%2$02d", hours, minutes); + final String time = String.format("%1$02d:%2$02d", mHours, mMinutes); if (callChangeListener(time)) { persistString(time); From a5a68b0fbecc837980174ca35f9ebf364ecc54b8 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 10 Apr 2021 15:07:23 +0200 Subject: [PATCH 099/171] NF: @PluralRes on getToastResourceId --- AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java index 4e4d9ed2d3e4..894e91cae861 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Reviewer.java @@ -52,6 +52,7 @@ import androidx.annotation.MenuRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.PluralsRes; import androidx.annotation.VisibleForTesting; import androidx.appcompat.view.menu.MenuBuilder; import androidx.appcompat.app.ActionBar; @@ -127,13 +128,13 @@ public class Reviewer extends AbstractFlashcardViewer { private final ScheduleCollectionTaskListener mRescheduleCardHandler = new ScheduleCollectionTaskListener() { - protected int getToastResourceId() { + protected @PluralsRes int getToastResourceId() { return R.plurals.reschedule_cards_dialog_acknowledge; } }; private final ScheduleCollectionTaskListener mResetProgressCardHandler = new ScheduleCollectionTaskListener() { - protected int getToastResourceId() { + protected @PluralsRes int getToastResourceId() { return R.plurals.reset_cards_dialog_acknowledge; } }; @@ -144,7 +145,7 @@ protected int getToastResourceId() { /** We need to listen for and handle reschedules / resets very similarly */ abstract class ScheduleCollectionTaskListener extends NextCardHandler> { - abstract protected int getToastResourceId(); + abstract protected @PluralsRes int getToastResourceId(); @Override From 9b46eaf6caa77c65ae4b10d7f40e63182b9b9cd9 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 9 Apr 2021 09:14:27 +0200 Subject: [PATCH 100/171] NF: remove an unused variable with no side effect --- AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index e037950c5d69..27f3273e4f01 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -837,7 +837,6 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa boolean hasUnmarked = !originalUnmarked.isEmpty(); CardUtils.markAll(new ArrayList<>(notes), hasUnmarked); - Undoable markNoteMulti = new UndoMarkNoteMulti(originalMarked, originalUnmarked, hasUnmarked); // mark undo for all at once col.markUndo(new UndoMarkNoteMulti(originalMarked, originalUnmarked, hasUnmarked)); From 6db6907f87e19b5b152a12d125970171de80e8fb Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 9 Apr 2021 16:52:45 +0200 Subject: [PATCH 101/171] NF: remove useless variable --- .../src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java index e95893d011ce..c329e2a39078 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java @@ -618,8 +618,6 @@ public void onProgressUpdate(Card card) { protected void displayNext(Card nextCard) { - Resources res = getResources(); - if (mSched == null) { // TODO: proper testing for restored activity finishWithoutAnimation(); From 1d0ad0613b6961d290f0a83832a16dd7fdd62ca3 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 10 Apr 2021 14:50:58 +0200 Subject: [PATCH 102/171] NF: MultiColumnListAdapter's getCard return `CardCache` --- AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java index 365175ad3f55..8e3e7eda805e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java @@ -2338,7 +2338,7 @@ public int getCount() { @Override - public Object getItem(int position) { + public CardCache getItem(int position) { return getCards().get(position); } From 794a42073340c5d0f2a81c1990be560f7d8ed9eb Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 9 Apr 2021 09:22:39 +0200 Subject: [PATCH 103/171] NF: rename `Undoable` to `UndoAction` This should make clear that it represents the action that must be done to undo something. This is not something that can be undone --- .../java/com/ichi2/async/CollectionTask.java | 22 +++++++++---------- .../java/com/ichi2/libanki/Collection.java | 15 +++++-------- .../{Undoable.java => UndoAction.java} | 8 +++---- 3 files changed, 21 insertions(+), 24 deletions(-) rename AnkiDroid/src/main/java/com/ichi2/libanki/{Undoable.java => UndoAction.java} (86%) diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index 27f3273e4f01..365b84d3cc96 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -37,7 +37,7 @@ import com.ichi2.libanki.Media; import com.ichi2.libanki.Model; import com.ichi2.libanki.Models; -import com.ichi2.libanki.Undoable; +import com.ichi2.libanki.UndoAction; import com.ichi2.libanki.WrongId; import com.ichi2.libanki.sched.AbstractSched; import com.ichi2.libanki.AnkiPackageExporter; @@ -92,7 +92,7 @@ import static com.ichi2.async.TaskManager.setLatestInstance; import static com.ichi2.libanki.Card.deepCopyCardArray; -import static com.ichi2.libanki.Undoable.*; +import static com.ichi2.libanki.UndoAction.*; import static com.ichi2.utils.BooleanGetter.FALSE; import static com.ichi2.utils.BooleanGetter.TRUE; @@ -404,7 +404,7 @@ protected Void task(Collection col, ProgressSenderAndCancelListener collec - private static class UndoSuspendCard extends Undoable { + private static class UndoSuspendCard extends UndoAction { private final Card mSuspendedCard; @@ -422,7 +422,7 @@ public UndoSuspendCard(Card suspendedCard) { } - private static class UndoDeleteNote extends Undoable { + private static class UndoDeleteNote extends UndoAction { private final Note mNote; private final ArrayList mAllCs; private final @NonNull Card mCard; @@ -561,7 +561,7 @@ protected void actualTask(Collection col, Card card) { } - protected static class UndoSuspendCardMulti extends Undoable { + protected static class UndoSuspendCardMulti extends UndoAction { private final Card[] mCards; private final boolean[] mOriginalSuspended; @@ -608,7 +608,7 @@ public UndoSuspendCardMulti(Card[] cards, boolean[] originalSuspended, } - private static class UndoDeleteNoteMulti extends Undoable { + private static class UndoDeleteNoteMulti extends UndoAction { private final Note[] mNotesArr; private final List mAllCards; @@ -639,7 +639,7 @@ public UndoDeleteNoteMulti(Note[] notesArr, List allCards) { } - private static class UndoChangeDeckMulti extends Undoable { + private static class UndoChangeDeckMulti extends UndoAction { private final Card[] mCards; private final long[] mOriginalDids; @@ -667,7 +667,7 @@ public UndoChangeDeckMulti(Card[] cards, long[] originalDids) { } } - private static class UndoMarkNoteMulti extends Undoable { + private static class UndoMarkNoteMulti extends UndoAction { private final List mOriginalMarked; private final List mOriginalUnmarked; @@ -689,7 +689,7 @@ public UndoMarkNoteMulti(List originalMarked, List originalUnmarked, } - private static class UndoRepositionRescheduleResetCards extends Undoable { + private static class UndoRepositionRescheduleResetCards extends UndoAction { private final Card[] mCardsCopied; @@ -931,7 +931,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa card.flush(); } - Undoable changeDeckMulti = new UndoChangeDeckMulti(cards, originalDids); + UndoAction changeDeckMulti = new UndoChangeDeckMulti(cards, originalDids); // mark undo for all at once col.markUndo(changeDeckMulti); return null; @@ -951,7 +951,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa try { Timber.d("Saving undo information of type %s on %d cards", getClass(), cards.length); Card[] cards_copied = deepCopyCardArray(cards, collectionTask); - Undoable repositionRescheduleResetCards = new UndoRepositionRescheduleResetCards(mUndoNameId, cards_copied); + UndoAction repositionRescheduleResetCards = new UndoRepositionRescheduleResetCards(mUndoNameId, cards_copied); col.markUndo(repositionRescheduleResetCards); } catch (CancellationException ce) { Timber.i(ce, "Cancelled while handling type %s, skipping undo", mUndoNameId); diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java index 28d178b7c331..a5cf113851fa 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java @@ -49,7 +49,6 @@ import com.ichi2.upgrade.Upgrade; import com.ichi2.utils.DatabaseChangeDecorator; import com.ichi2.utils.FunctionalInterfaces; -import com.ichi2.utils.LanguageUtil; import com.ichi2.utils.VersionUtils; import com.ichi2.utils.JSONArray; @@ -80,14 +79,12 @@ import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.StringRes; import androidx.annotation.VisibleForTesting; import androidx.sqlite.db.SupportSQLiteDatabase; import androidx.sqlite.db.SupportSQLiteStatement; import timber.log.Timber; import static com.ichi2.async.CancelListener.isCancelled; -import static com.ichi2.libanki.Consts.DECK_DYN; // Anki maintains a cache of used tags so it can quickly present a list of tags // for autocomplete and in the browser. For efficiency, deletions are not @@ -126,7 +123,7 @@ public class Collection implements CollectionGetter { // END: SQL table columns // API 21: Use a ConcurrentLinkedDeque - private LinkedBlockingDeque mUndo; + private LinkedBlockingDeque mUndo; private final String mPath; private final DroidBackend mDroidBackend; @@ -1316,14 +1313,14 @@ public void clearUndo() { /** Undo menu item name, or "" if undo unavailable. */ @VisibleForTesting - public @Nullable Undoable undoType() { + public @Nullable UndoAction undoType() { if (mUndo.size() > 0) { return mUndo.getLast(); } return null; } public String undoName(Resources res) { - Undoable type = undoType(); + UndoAction type = undoType(); if (type != null) { return type.name(res); } @@ -1336,12 +1333,12 @@ public boolean undoAvailable() { } public @Nullable Card undo() { - Undoable lastUndo = mUndo.removeLast(); + UndoAction lastUndo = mUndo.removeLast(); Timber.d("undo() of type %s", lastUndo.getClass()); return lastUndo.undo(this); } - public void markUndo(@NonNull Undoable undo) { + public void markUndo(@NonNull UndoAction undo) { Timber.d("markUndo() of type %s", undo.getClass()); mUndo.add(undo); while (mUndo.size() > UNDO_SIZE_MAX) { @@ -1350,7 +1347,7 @@ public void markUndo(@NonNull Undoable undo) { } @VisibleForTesting - public static class UndoReview extends Undoable { + public static class UndoReview extends UndoAction { private final boolean mWasLeech; @NonNull private final Card mClonedCard; public UndoReview(boolean wasLeech, @NonNull Card clonedCard) { diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Undoable.java b/AnkiDroid/src/main/java/com/ichi2/libanki/UndoAction.java similarity index 86% rename from AnkiDroid/src/main/java/com/ichi2/libanki/Undoable.java rename to AnkiDroid/src/main/java/com/ichi2/libanki/UndoAction.java index 81e1d077cf7c..0ce6b714395d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Undoable.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/UndoAction.java @@ -12,13 +12,13 @@ import androidx.annotation.StringRes; import timber.log.Timber; -public abstract class Undoable { +public abstract class UndoAction { @StringRes public final int mUndoNameId; /** * For all descendants, we assume that a card/note/object passed as argument is never going to be changed again. * It's the caller reponsability to clone the object if necessary.*/ - public Undoable(@StringRes int undoNameId) { + public UndoAction(@StringRes int undoNameId) { mUndoNameId = undoNameId; } @@ -35,10 +35,10 @@ public String name(Resources res) { * Returned positive integers are card id. Those ids is the card that was discarded and that may be sent back to the reviewer.*/ public abstract @Nullable Card undo(@NonNull Collection col); - public static @NonNull Undoable revertToProvidedState (@StringRes int undoNameId, Card card){ + public static @NonNull UndoAction revertToProvidedState (@StringRes int undoNameId, Card card){ Note note = card.note(); List cards = note.cards(); - return new Undoable(undoNameId) { + return new UndoAction(undoNameId) { public @Nullable Card undo(@NonNull Collection col) { Timber.i("Undo: %d", undoNameId); From 44b5fb2a9f9789663b33e0dd1d41d0a00b89b437 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Sun, 4 Apr 2021 22:20:10 +0200 Subject: [PATCH 104/171] NF: use require activity and arguments. + [SharedPreferences] Changed commit to apply, apply is asynchronous version of commit that doesn't block. refer to: https://developer.android.com/reference/android/content/SharedPreferences.Editor#apply() --- .../ichi2/anki/dialogs/CustomStudyDialog.java | 76 +++++++++---------- .../anki/dialogs/CustomStudyDialogTest.java | 3 +- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java index ed694479f6d0..af05a10708bb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java @@ -15,6 +15,7 @@ ****************************************************************************************/ package com.ichi2.anki.dialogs; +import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Intent; import android.content.SharedPreferences; @@ -108,10 +109,10 @@ public static CustomStudyDialog newInstance(int id, long did, boolean jumpToRevi @Override public Dialog onCreateDialog(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final int dialogId = getArguments().getInt("id"); + final int dialogId = requireArguments().getInt("id"); if (dialogId < 100) { // Select the specified deck - CollectionHelper.getInstance().getCol(getActivity()).getDecks().select(getArguments().getLong("did")); + CollectionHelper.getInstance().getCol(requireActivity()).getDecks().select(requireArguments().getLong("did")); return buildContextMenu(dialogId); } else { return buildInputDialog(dialogId); @@ -120,31 +121,30 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { /** * Build a context menu for custom study - * @param id - * @return + * @param id the id type of the dialog */ private MaterialDialog buildContextMenu(int id) { int[] listIds = getListIds(id); - final boolean jumpToReviewer = getArguments ().getBoolean("jumpToReviewer"); - return new MaterialDialog.Builder(this.getActivity()) + final boolean jumpToReviewer = requireArguments().getBoolean("jumpToReviewer"); + return new MaterialDialog.Builder(this.requireActivity()) .title(R.string.custom_study) .cancelable(true) .itemsIds(listIds) .items(ContextMenuHelper.getValuesFromKeys(getKeyValueMap(), listIds)) .itemsCallback((materialDialog, view, which, charSequence) -> { - AnkiActivity activity = getAnkiActivity(); + AnkiActivity activity = requireAnkiActivity(); switch (view.getId()) { case DECK_OPTIONS: { // User asked to permanently change the deck options Intent i = new Intent(activity, DeckOptions.class); - i.putExtra("did", getArguments().getLong("did")); - getActivity().startActivity(i); + i.putExtra("did", requireArguments().getLong("did")); + requireActivity().startActivity(i); break; } case MORE_OPTIONS: { // User asked to see all custom study options CustomStudyDialog d = CustomStudyDialog.newInstance(CONTEXT_MENU_STANDARD, - getArguments().getLong("did"), jumpToReviewer); + requireArguments().getLong("did"), jumpToReviewer); activity.showDialogFragment(d); break; } @@ -154,7 +154,7 @@ private MaterialDialog buildContextMenu(int id) { * number, it is necessary to collect a list of tags. This case handles the creation * of that Dialog. */ - long currentDeck = getArguments().getLong("did"); + long currentDeck = requireArguments().getLong("did"); TagsDialog dialogFragment = TagsDialog.newInstance( TagsDialog.DialogType.CUSTOM_STUDY_TAGS, new ArrayList<>(), new ArrayList<>(activity.getCol().getTags().byDeck(currentDeck, true))); @@ -164,8 +164,8 @@ private MaterialDialog buildContextMenu(int id) { default: { // User asked for a standard custom study option CustomStudyDialog d = CustomStudyDialog.newInstance(view.getId(), - getArguments().getLong("did"), jumpToReviewer); - getAnkiActivity().showDialogFragment(d); + requireArguments().getLong("did"), jumpToReviewer); + requireAnkiActivity().showDialogFragment(d); } } }).build(); @@ -173,8 +173,7 @@ private MaterialDialog buildContextMenu(int id) { /** * Build an input dialog that is used to get a parameter related to custom study from the user - * @param dialogId - * @return + * @param dialogId the id type of the dialog */ private MaterialDialog buildInputDialog(final int dialogId) { /* @@ -182,9 +181,9 @@ private MaterialDialog buildInputDialog(final int dialogId) { TODO: hint line for the number of cards available, and having the pre-filled text selected by default) */ // Input dialogs - Resources res = getActivity().getResources(); // Show input dialog for an individual custom study dialog - View v = getActivity().getLayoutInflater().inflate(R.layout.styled_custom_study_details_dialog, null); + @SuppressLint("InflateParams") + View v = requireActivity().getLayoutInflater().inflate(R.layout.styled_custom_study_details_dialog, null); TextView textView1 = v.findViewById(R.id.custom_study_details_text1); TextView textView2 = v.findViewById(R.id.custom_study_details_text2); final EditText mEditText = v.findViewById(R.id.custom_study_details_edittext2); @@ -199,16 +198,16 @@ private MaterialDialog buildInputDialog(final int dialogId) { mEditText.setInputType(EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_FLAG_SIGNED); } // deck id - final long did = getArguments().getLong("did"); + final long did = requireArguments().getLong("did"); // Whether or not to jump straight to the reviewer - final boolean jumpToReviewer = getArguments ().getBoolean("jumpToReviewer"); + final boolean jumpToReviewer = requireArguments().getBoolean("jumpToReviewer"); // Set builder parameters - MaterialDialog.Builder builder = new MaterialDialog.Builder(getActivity()) + MaterialDialog.Builder builder = new MaterialDialog.Builder(requireActivity()) .customView(v, true) .positiveText(R.string.dialog_ok) .negativeText(R.string.dialog_cancel) .onPositive((dialog, which) -> { - Collection col = CollectionHelper.getInstance().getCol(getActivity()); + Collection col = CollectionHelper.getInstance().getCol(requireActivity()); // Get the value selected by user int n; try { @@ -222,7 +221,7 @@ private MaterialDialog buildInputDialog(final int dialogId) { // Set behavior when clicking OK button switch (dialogId) { case CUSTOM_STUDY_NEW: { - AnkiDroidApp.getSharedPrefs(getActivity()).edit().putInt("extendNew", n).commit(); + AnkiDroidApp.getSharedPrefs(requireActivity()).edit().putInt("extendNew", n).apply(); Deck deck = col.getDecks().get(did); deck.put("extendNew", n); col.getDecks().save(deck); @@ -231,7 +230,7 @@ private MaterialDialog buildInputDialog(final int dialogId) { break; } case CUSTOM_STUDY_REV: { - AnkiDroidApp.getSharedPrefs(getActivity()).edit().putInt("extendRev", n).commit(); + AnkiDroidApp.getSharedPrefs(requireActivity()).edit().putInt("extendRev", n).apply(); Deck deck = col.getDecks().get(did); deck.put("extendRev", n); col.getDecks().save(deck); @@ -265,7 +264,7 @@ private MaterialDialog buildInputDialog(final int dialogId) { break; } }) - .onNegative((dialog, which) -> getAnkiActivity().dismissAllDialogFragments()); + .onNegative((dialog, which) -> requireAnkiActivity().dismissAllDialogFragments()); final MaterialDialog dialog = builder.build(); mEditText.addTextChangedListener(new TextWatcher() { @Override @@ -346,7 +345,7 @@ public void onSelectedTags(List selectedTags, int option) { * @return the ids of which values to show */ private int[] getListIds(int dialogId) { - Collection col = getAnkiActivity().getCol(); + Collection col = requireAnkiActivity().getCol(); switch (dialogId) { case CONTEXT_MENU_STANDARD: // Standard context menu @@ -387,8 +386,8 @@ private int[] getListIds(int dialogId) { private String getText1() { Resources res = AnkiDroidApp.getAppResources(); - Collection col = CollectionHelper.getInstance().getCol(getActivity()); - switch (getArguments().getInt("id")) { + Collection col = CollectionHelper.getInstance().getCol(requireActivity()); + switch (requireArguments().getInt("id")) { case CUSTOM_STUDY_NEW: return res.getString(R.string.custom_study_new_total_new, col.getSched().totalNewForCurrentDeck()); case CUSTOM_STUDY_REV: @@ -400,7 +399,7 @@ private String getText1() { private String getText2() { Resources res = AnkiDroidApp.getAppResources(); - switch (getArguments().getInt("id")) { + switch (requireArguments().getInt("id")) { case CUSTOM_STUDY_NEW: return res.getString(R.string.custom_study_new_extend); case CUSTOM_STUDY_REV: @@ -419,8 +418,8 @@ private String getText2() { } private String getDefaultValue() { - SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(getActivity()); - switch (getArguments().getInt("id")) { + SharedPreferences prefs = AnkiDroidApp.getSharedPrefs(requireActivity()); + switch (requireArguments().getInt("id")) { case CUSTOM_STUDY_NEW: return Integer.toString(prefs.getInt("extendNew", 10)); case CUSTOM_STUDY_REV: @@ -446,9 +445,9 @@ private String getDefaultValue() { */ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean resched) { Deck dyn; - final AnkiActivity activity = getAnkiActivity(); + final AnkiActivity activity = requireAnkiActivity(); Collection col = CollectionHelper.getInstance().getCol(activity); - long did = getArguments().getLong("did"); + long did = requireArguments().getLong("did"); Decks decks = col.getDecks(); String deckToStudyName = decks.get(did).getString("name"); String customStudyDeck = getResources().getString(R.string.custom_study_deck_name); @@ -457,7 +456,7 @@ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean Timber.i("Found deck: '%s'", customStudyDeck); if (cur.isStd()) { Timber.w("Deck: '%s' was non-dynamic", customStudyDeck); - UIUtils.showThemedToast(getActivity(), getString(R.string.custom_study_deck_exists), true); + UIUtils.showThemedToast(requireActivity(), getString(R.string.custom_study_deck_exists), true); return; } else { Timber.i("Emptying dynamic deck '%s' for custom study", customStudyDeck); @@ -472,7 +471,7 @@ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean try { dyn = decks.get(decks.newDyn(customStudyDeck)); } catch (FilteredAncestor filteredAncestor) { - UIUtils.showThemedToast(getActivity(), getString(R.string.decks_rename_filtered_nosubdecks), true); + UIUtils.showThemedToast(requireActivity(), getString(R.string.decks_rename_filtered_nosubdecks), true); return; } } @@ -506,7 +505,7 @@ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean } private void onLimitsExtended(boolean jumpToReviewer) { - AnkiActivity activity = getAnkiActivity(); + AnkiActivity activity = requireAnkiActivity(); if (jumpToReviewer) { activity.startActivityForResultWithoutAnimation(new Intent(activity, Reviewer.class), AnkiActivity.REQUEST_REVIEW); } else { @@ -515,13 +514,14 @@ private void onLimitsExtended(boolean jumpToReviewer) { activity.dismissAllDialogFragments(); } - protected AnkiActivity getAnkiActivity() { - return (AnkiActivity) getActivity(); + @NonNull + protected AnkiActivity requireAnkiActivity() { + return (AnkiActivity) requireActivity(); } private CreateCustomStudySessionListener createCustomStudySessionListener(){ - return new CreateCustomStudySessionListener(getAnkiActivity()); + return new CreateCustomStudySessionListener(requireAnkiActivity()); } private static class CreateCustomStudySessionListener extends TaskListenerWithContext { public CreateCustomStudySessionListener(AnkiActivity activity) { diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java index 624c02b6d435..f3ccc956131f 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java @@ -129,8 +129,9 @@ public void setActivity(T ankiAct } + @NonNull @Override - protected AnkiActivity getAnkiActivity() { + protected AnkiActivity requireAnkiActivity() { return mAnkiActivity; } } From dc76c6adfc5bcbdf96905e3da46055f3915862e5 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Mon, 5 Apr 2021 20:35:55 +0200 Subject: [PATCH 105/171] NF: Move CustomStudyDialog to its own package --- AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java | 2 +- .../src/main/java/com/ichi2/anki/StudyOptionsActivity.java | 3 +-- .../src/main/java/com/ichi2/anki/StudyOptionsFragment.java | 2 +- .../java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java | 2 +- .../anki/dialogs/{ => customstudy}/CustomStudyDialog.java | 5 +++-- .../java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java | 5 ++--- 6 files changed, 9 insertions(+), 10 deletions(-) rename AnkiDroid/src/main/java/com/ichi2/anki/dialogs/{ => customstudy}/CustomStudyDialog.java (99%) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 13c7dd29b5d5..6203c99b7a32 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -89,7 +89,7 @@ import com.ichi2.anki.analytics.UsageAnalytics; import com.ichi2.anki.dialogs.AsyncDialogFragment; import com.ichi2.anki.dialogs.ConfirmationDialog; -import com.ichi2.anki.dialogs.CustomStudyDialog; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; import com.ichi2.anki.dialogs.DatabaseErrorDialog; import com.ichi2.anki.dialogs.DeckPickerAnalyticsOptInDialog; import com.ichi2.anki.dialogs.DeckPickerBackupNoSpaceLeftDialog; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java index fc86afe0e219..809ed9b7e637 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java @@ -21,9 +21,8 @@ import android.view.MenuItem; import android.view.View; -import com.ichi2.anim.ActivityTransitionAnimation; import com.ichi2.anki.StudyOptionsFragment.StudyOptionsListener; -import com.ichi2.anki.dialogs.CustomStudyDialog; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; import com.ichi2.widget.WidgetStatus; import timber.log.Timber; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java index cbe20b17a22b..00134d0f6a51 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java @@ -41,7 +41,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anim.ActivityTransitionAnimation; -import com.ichi2.anki.dialogs.CustomStudyDialog; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; import com.ichi2.async.CollectionTask; import com.ichi2.async.TaskListener; import com.ichi2.async.TaskManager; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java index b2d790cddd65..7b5f30484c59 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java @@ -17,7 +17,6 @@ import android.app.Dialog; import android.content.res.Resources; -import android.os.Build; import android.os.Bundle; import com.afollestad.materialdialogs.MaterialDialog; @@ -27,6 +26,7 @@ import com.ichi2.anki.R; import com.ichi2.anki.StudyOptionsFragment; import com.ichi2.anki.analytics.AnalyticsDialogFragment; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; import com.ichi2.libanki.Collection; import java.lang.annotation.Retention; diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java similarity index 99% rename from AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java rename to AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index af05a10708bb..85d77003e327 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -13,7 +13,7 @@ * You should have received a copy of the GNU General Public License along with * * this program. If not, see . * ****************************************************************************************/ -package com.ichi2.anki.dialogs; +package com.ichi2.anki.dialogs.customstudy; import android.annotation.SuppressLint; import android.app.Dialog; @@ -45,9 +45,10 @@ import com.ichi2.anki.StudyOptionsFragment; import com.ichi2.anki.UIUtils; import com.ichi2.anki.analytics.AnalyticsDialogFragment; +import com.ichi2.anki.dialogs.ContextMenuHelper; +import com.ichi2.anki.dialogs.TagsDialog; import com.ichi2.anki.exception.FilteredAncestor; import com.ichi2.async.CollectionTask; -import com.ichi2.async.TaskListener; import com.ichi2.async.TaskListenerWithContext; import com.ichi2.async.TaskManager; import com.ichi2.libanki.Collection; diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java index f3ccc956131f..6b88ccf2bc94 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java @@ -24,9 +24,9 @@ import com.ichi2.anki.CardTemplateBrowserAppearanceEditor; import com.ichi2.anki.R; import com.ichi2.anki.RobolectricTest; -import com.ichi2.anki.dialogs.CustomStudyDialog.CustomStudyListener; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener; import com.ichi2.libanki.Deck; -import com.ichi2.utils.JSONObject; import org.hamcrest.Matchers; import org.junit.Ignore; @@ -39,7 +39,6 @@ import androidx.lifecycle.Lifecycle; import androidx.test.ext.junit.runners.AndroidJUnit4; -import static com.ichi2.libanki.Consts.DECK_DYN; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsNull.notNullValue; From bd03e7995bd4dbd10cf4e4c9e9157e660fd9ec9b Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Mon, 5 Apr 2021 21:35:01 +0200 Subject: [PATCH 106/171] NF: extract interface + CreateCustomStudySessionListener is now using an interface instead of relying on the AnkiActivity and assuming it does implement CustomStudyListener --- .../CreateCustomStudySessionListener.java | 35 +++++++++++++++++++ .../customstudy/CustomStudyDialog.java | 25 ++----------- 2 files changed, 37 insertions(+), 23 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CreateCustomStudySessionListener.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CreateCustomStudySessionListener.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CreateCustomStudySessionListener.java new file mode 100644 index 000000000000..3f2eba120773 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CreateCustomStudySessionListener.java @@ -0,0 +1,35 @@ +package com.ichi2.anki.dialogs.customstudy; + +import com.ichi2.anki.StudyOptionsFragment; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener; +import com.ichi2.async.TaskListenerWithContext; + +import androidx.annotation.NonNull; + +import static com.ichi2.anki.dialogs.customstudy.CreateCustomStudySessionListener.*; + +class CreateCustomStudySessionListener extends TaskListenerWithContext { + + public interface Callback { + void hideProgressBar(); + void onCreateCustomStudySession(); + void showProgressBar(); + } + + public CreateCustomStudySessionListener(Callback callback) { + super(callback); + } + + + @Override + public void actualOnPreExecute(@NonNull Callback callback) { + callback.showProgressBar(); + } + + + @Override + public void actualOnPostExecute(@NonNull Callback callback, StudyOptionsFragment.DeckStudyData v) { + callback.hideProgressBar(); + callback.onCreateCustomStudySession(); + } +} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index 85d77003e327..38dc8f73bc8b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -42,14 +42,12 @@ import com.ichi2.anki.DeckOptions; import com.ichi2.anki.R; import com.ichi2.anki.Reviewer; -import com.ichi2.anki.StudyOptionsFragment; import com.ichi2.anki.UIUtils; import com.ichi2.anki.analytics.AnalyticsDialogFragment; import com.ichi2.anki.dialogs.ContextMenuHelper; import com.ichi2.anki.dialogs.TagsDialog; import com.ichi2.anki.exception.FilteredAncestor; import com.ichi2.async.CollectionTask; -import com.ichi2.async.TaskListenerWithContext; import com.ichi2.async.TaskManager; import com.ichi2.libanki.Collection; import com.ichi2.libanki.Consts; @@ -84,8 +82,7 @@ public class CustomStudyDialog extends AnalyticsDialogFragment implements private static final int DECK_OPTIONS = 107; private static final int MORE_OPTIONS = 108; - public interface CustomStudyListener { - void onCreateCustomStudySession(); + public interface CustomStudyListener extends CreateCustomStudySessionListener.Callback { void onExtendStudyLimits(); } @@ -522,24 +519,6 @@ protected AnkiActivity requireAnkiActivity() { private CreateCustomStudySessionListener createCustomStudySessionListener(){ - return new CreateCustomStudySessionListener(requireAnkiActivity()); - } - private static class CreateCustomStudySessionListener extends TaskListenerWithContext { - public CreateCustomStudySessionListener(AnkiActivity activity) { - super(activity); - } - - - @Override - public void actualOnPreExecute(@NonNull AnkiActivity activity) { - activity.showProgressBar(); - } - - - @Override - public void actualOnPostExecute(@NonNull AnkiActivity activity, StudyOptionsFragment.DeckStudyData v) { - activity.hideProgressBar(); - ((CustomStudyListener) activity).onCreateCustomStudySession(); - } + return new CreateCustomStudySessionListener((CustomStudyListener) requireAnkiActivity()); } } From 5fa5dd2f20fad5296ec4ae78802729b2374c3e89 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Sat, 10 Apr 2021 16:49:32 +0200 Subject: [PATCH 107/171] NF: extract collection to a member field --- .../customstudy/CustomStudyDialog.java | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index 38dc8f73bc8b..ded1f53f75d9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -17,6 +17,7 @@ import android.annotation.SuppressLint; import android.app.Dialog; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; @@ -38,7 +39,6 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anki.AnkiActivity; import com.ichi2.anki.AnkiDroidApp; -import com.ichi2.anki.CollectionHelper; import com.ichi2.anki.DeckOptions; import com.ichi2.anki.R; import com.ichi2.anki.Reviewer; @@ -103,6 +103,18 @@ public static CustomStudyDialog newInstance(int id, long did, boolean jumpToRevi return f; } + + private Collection mCollection; + + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + AnkiActivity ankiActivity = requireAnkiActivity(); + mCollection = ankiActivity.getCol(); + } + + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { @@ -110,7 +122,7 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { final int dialogId = requireArguments().getInt("id"); if (dialogId < 100) { // Select the specified deck - CollectionHelper.getInstance().getCol(requireActivity()).getDecks().select(requireArguments().getLong("did")); + mCollection.getDecks().select(requireArguments().getLong("did")); return buildContextMenu(dialogId); } else { return buildInputDialog(dialogId); @@ -155,7 +167,7 @@ private MaterialDialog buildContextMenu(int id) { long currentDeck = requireArguments().getLong("did"); TagsDialog dialogFragment = TagsDialog.newInstance( TagsDialog.DialogType.CUSTOM_STUDY_TAGS, new ArrayList<>(), - new ArrayList<>(activity.getCol().getTags().byDeck(currentDeck, true))); + new ArrayList<>(mCollection.getTags().byDeck(currentDeck, true))); activity.showDialogFragment(dialogFragment); break; } @@ -205,7 +217,6 @@ private MaterialDialog buildInputDialog(final int dialogId) { .positiveText(R.string.dialog_ok) .negativeText(R.string.dialog_cancel) .onPositive((dialog, which) -> { - Collection col = CollectionHelper.getInstance().getCol(requireActivity()); // Get the value selected by user int n; try { @@ -220,19 +231,19 @@ private MaterialDialog buildInputDialog(final int dialogId) { switch (dialogId) { case CUSTOM_STUDY_NEW: { AnkiDroidApp.getSharedPrefs(requireActivity()).edit().putInt("extendNew", n).apply(); - Deck deck = col.getDecks().get(did); + Deck deck = mCollection.getDecks().get(did); deck.put("extendNew", n); - col.getDecks().save(deck); - col.getSched().extendLimits(n, 0); + mCollection.getDecks().save(deck); + mCollection.getSched().extendLimits(n, 0); onLimitsExtended(jumpToReviewer); break; } case CUSTOM_STUDY_REV: { AnkiDroidApp.getSharedPrefs(requireActivity()).edit().putInt("extendRev", n).apply(); - Deck deck = col.getDecks().get(did); + Deck deck = mCollection.getDecks().get(did); deck.put("extendRev", n); - col.getDecks().save(deck); - col.getSched().extendLimits(0, n); + mCollection.getDecks().save(deck); + mCollection.getSched().extendLimits(0, n); onLimitsExtended(jumpToReviewer); break; } @@ -343,7 +354,6 @@ public void onSelectedTags(List selectedTags, int option) { * @return the ids of which values to show */ private int[] getListIds(int dialogId) { - Collection col = requireAnkiActivity().getCol(); switch (dialogId) { case CONTEXT_MENU_STANDARD: // Standard context menu @@ -355,17 +365,17 @@ private int[] getListIds(int dialogId) { dialogOptions.add(CUSTOM_STUDY_RANDOM); dialogOptions.add(CUSTOM_STUDY_PREVIEW); dialogOptions.add(CUSTOM_STUDY_TAGS); - if (col.getSched().totalNewForCurrentDeck() == 0) { + if (mCollection.getSched().totalNewForCurrentDeck() == 0) { // If no new cards we wont show CUSTOM_STUDY_NEW dialogOptions.remove(Integer.valueOf(CUSTOM_STUDY_NEW)); } return ContextMenuHelper.integerListToArray(dialogOptions); case CONTEXT_MENU_LIMITS: // Special custom study options to show when the daily study limit has been reached - if (col.getSched().newDue() && col.getSched().revDue()) { + if (mCollection.getSched().newDue() && mCollection.getSched().revDue()) { return new int[] {CUSTOM_STUDY_NEW, CUSTOM_STUDY_REV, DECK_OPTIONS, MORE_OPTIONS}; } else { - if (col.getSched().newDue()) { + if (mCollection.getSched().newDue()) { return new int[]{CUSTOM_STUDY_NEW, DECK_OPTIONS, MORE_OPTIONS}; } else { return new int[]{CUSTOM_STUDY_REV, DECK_OPTIONS, MORE_OPTIONS}; @@ -384,12 +394,11 @@ private int[] getListIds(int dialogId) { private String getText1() { Resources res = AnkiDroidApp.getAppResources(); - Collection col = CollectionHelper.getInstance().getCol(requireActivity()); switch (requireArguments().getInt("id")) { case CUSTOM_STUDY_NEW: - return res.getString(R.string.custom_study_new_total_new, col.getSched().totalNewForCurrentDeck()); + return res.getString(R.string.custom_study_new_total_new, mCollection.getSched().totalNewForCurrentDeck()); case CUSTOM_STUDY_REV: - return res.getString(R.string.custom_study_rev_total_rev, col.getSched().totalRevForCurrentDeck()); + return res.getString(R.string.custom_study_rev_total_rev, mCollection.getSched().totalRevForCurrentDeck()); default: return ""; } @@ -444,9 +453,8 @@ private String getDefaultValue() { private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean resched) { Deck dyn; final AnkiActivity activity = requireAnkiActivity(); - Collection col = CollectionHelper.getInstance().getCol(activity); long did = requireArguments().getLong("did"); - Decks decks = col.getDecks(); + Decks decks = mCollection.getDecks(); String deckToStudyName = decks.get(did).getString("name"); String customStudyDeck = getResources().getString(R.string.custom_study_deck_name); Deck cur = decks.byName(customStudyDeck); @@ -459,7 +467,7 @@ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean } else { Timber.i("Emptying dynamic deck '%s' for custom study", customStudyDeck); // safe to empty - col.getSched().emptyDyn(cur.getLong("id")); + mCollection.getSched().emptyDyn(cur.getLong("id")); // reuse; don't delete as it may have children dyn = cur; decks.select(cur.getLong("id")); From 13b3c8fc2730a136c3a0964b36eb56afd6e13b9b Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Sat, 10 Apr 2021 16:50:17 +0200 Subject: [PATCH 108/171] NF: extract CustomStudyListener to a member field NF: add showDialogFragment to CustomStudyListener NF: add dismissAllDialogFragments to CustomStudyListener NF: add startActivityForResultWithoutAnimation to CustomStudyListener --- .../customstudy/CustomStudyDialog.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index ded1f53f75d9..65eadeba7085 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -24,6 +24,7 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import androidx.fragment.app.DialogFragment; import timber.log.Timber; import android.text.Editable; @@ -84,6 +85,9 @@ public class CustomStudyDialog extends AnalyticsDialogFragment implements public interface CustomStudyListener extends CreateCustomStudySessionListener.Callback { void onExtendStudyLimits(); + void showDialogFragment(DialogFragment newFragment); + void dismissAllDialogFragments(); + void startActivityForResultWithoutAnimation(Intent intent, int requestCode); } /** @@ -104,6 +108,7 @@ public static CustomStudyDialog newInstance(int id, long did, boolean jumpToRevi } + private CustomStudyListener mCustomStudyListener; private Collection mCollection; @@ -111,6 +116,7 @@ public static CustomStudyDialog newInstance(int id, long did, boolean jumpToRevi public void onAttach(@NonNull Context context) { super.onAttach(context); AnkiActivity ankiActivity = requireAnkiActivity(); + mCustomStudyListener = (CustomStudyListener) ankiActivity; mCollection = ankiActivity.getCol(); } @@ -155,7 +161,7 @@ private MaterialDialog buildContextMenu(int id) { // User asked to see all custom study options CustomStudyDialog d = CustomStudyDialog.newInstance(CONTEXT_MENU_STANDARD, requireArguments().getLong("did"), jumpToReviewer); - activity.showDialogFragment(d); + mCustomStudyListener.showDialogFragment(d); break; } case CUSTOM_STUDY_TAGS: { @@ -168,14 +174,14 @@ private MaterialDialog buildContextMenu(int id) { TagsDialog dialogFragment = TagsDialog.newInstance( TagsDialog.DialogType.CUSTOM_STUDY_TAGS, new ArrayList<>(), new ArrayList<>(mCollection.getTags().byDeck(currentDeck, true))); - activity.showDialogFragment(dialogFragment); + mCustomStudyListener.showDialogFragment(dialogFragment); break; } default: { // User asked for a standard custom study option CustomStudyDialog d = CustomStudyDialog.newInstance(view.getId(), requireArguments().getLong("did"), jumpToReviewer); - requireAnkiActivity().showDialogFragment(d); + mCustomStudyListener.showDialogFragment(d); } } }).build(); @@ -251,7 +257,7 @@ private MaterialDialog buildInputDialog(final int dialogId) { JSONArray ar = new JSONArray(); ar.put(0, 1); createCustomStudySession(ar, new Object[] {String.format(Locale.US, - "rated:%d:1", n), Consts.DYN_MAX_SIZE, Consts.DYN_RANDOM}, false); + "rated:%d:1", n), Consts.DYN_MAX_SIZE, Consts.DYN_RANDOM}, false); break; } case CUSTOM_STUDY_AHEAD: { @@ -273,7 +279,7 @@ private MaterialDialog buildInputDialog(final int dialogId) { break; } }) - .onNegative((dialog, which) -> requireAnkiActivity().dismissAllDialogFragments()); + .onNegative((dialog, which) -> mCustomStudyListener.dismissAllDialogFragments()); final MaterialDialog dialog = builder.build(); mEditText.addTextChangedListener(new TextWatcher() { @Override @@ -507,17 +513,17 @@ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean TaskManager.launchCollectionTask(new CollectionTask.RebuildCram(), createCustomStudySessionListener()); // Hide the dialogs - activity.dismissAllDialogFragments(); + mCustomStudyListener.dismissAllDialogFragments(); } private void onLimitsExtended(boolean jumpToReviewer) { AnkiActivity activity = requireAnkiActivity(); if (jumpToReviewer) { - activity.startActivityForResultWithoutAnimation(new Intent(activity, Reviewer.class), AnkiActivity.REQUEST_REVIEW); + mCustomStudyListener.startActivityForResultWithoutAnimation(new Intent(activity, Reviewer.class), AnkiActivity.REQUEST_REVIEW); } else { - ((CustomStudyListener) activity).onExtendStudyLimits(); + mCustomStudyListener.onExtendStudyLimits(); } - activity.dismissAllDialogFragments(); + mCustomStudyListener.dismissAllDialogFragments(); } @NonNull @@ -527,6 +533,6 @@ protected AnkiActivity requireAnkiActivity() { private CreateCustomStudySessionListener createCustomStudySessionListener(){ - return new CreateCustomStudySessionListener((CustomStudyListener) requireAnkiActivity()); + return new CreateCustomStudySessionListener(mCustomStudyListener); } } From 018f6f9f43d4c2162ee9b642ae0d14425b370cb0 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Mon, 5 Apr 2021 23:02:32 +0200 Subject: [PATCH 109/171] NF: remove requireAnkiActivity --- .../dialogs/customstudy/CustomStudyDialog.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index 65eadeba7085..88938ab68ba3 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -115,7 +115,7 @@ public static CustomStudyDialog newInstance(int id, long did, boolean jumpToRevi @Override public void onAttach(@NonNull Context context) { super.onAttach(context); - AnkiActivity ankiActivity = requireAnkiActivity(); + AnkiActivity ankiActivity = (AnkiActivity) requireActivity(); mCustomStudyListener = (CustomStudyListener) ankiActivity; mCollection = ankiActivity.getCol(); } @@ -148,11 +148,10 @@ private MaterialDialog buildContextMenu(int id) { .itemsIds(listIds) .items(ContextMenuHelper.getValuesFromKeys(getKeyValueMap(), listIds)) .itemsCallback((materialDialog, view, which, charSequence) -> { - AnkiActivity activity = requireAnkiActivity(); switch (view.getId()) { case DECK_OPTIONS: { // User asked to permanently change the deck options - Intent i = new Intent(activity, DeckOptions.class); + Intent i = new Intent(requireContext(), DeckOptions.class); i.putExtra("did", requireArguments().getLong("did")); requireActivity().startActivity(i); break; @@ -458,7 +457,6 @@ private String getDefaultValue() { */ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean resched) { Deck dyn; - final AnkiActivity activity = requireAnkiActivity(); long did = requireArguments().getLong("did"); Decks decks = mCollection.getDecks(); String deckToStudyName = decks.get(did).getString("name"); @@ -517,21 +515,14 @@ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean } private void onLimitsExtended(boolean jumpToReviewer) { - AnkiActivity activity = requireAnkiActivity(); if (jumpToReviewer) { - mCustomStudyListener.startActivityForResultWithoutAnimation(new Intent(activity, Reviewer.class), AnkiActivity.REQUEST_REVIEW); + mCustomStudyListener.startActivityForResultWithoutAnimation(new Intent(requireContext(), Reviewer.class), AnkiActivity.REQUEST_REVIEW); } else { mCustomStudyListener.onExtendStudyLimits(); } mCustomStudyListener.dismissAllDialogFragments(); } - @NonNull - protected AnkiActivity requireAnkiActivity() { - return (AnkiActivity) requireActivity(); - } - - private CreateCustomStudySessionListener createCustomStudySessionListener(){ return new CreateCustomStudySessionListener(mCustomStudyListener); } From bcdba8d634a525318391b2afb964bbe30a9ef7e9 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Mon, 5 Apr 2021 23:04:19 +0200 Subject: [PATCH 110/171] NF: rename `ignored` to `e` since the exception is used for logging, the linter is throwing an error --- .../ichi2/anki/dialogs/customstudy/CustomStudyDialog.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index 88938ab68ba3..4319ca559f12 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -226,8 +226,8 @@ private MaterialDialog buildInputDialog(final int dialogId) { int n; try { n = Integer.parseInt(mEditText.getText().toString()); - } catch (Exception ignored) { - Timber.w(ignored); + } catch (Exception e) { + Timber.w(e); // This should never happen because we disable positive button for non-parsable inputs return; } @@ -296,8 +296,8 @@ public void afterTextChanged(Editable editable) { try { Integer.parseInt(mEditText.getText().toString()); dialog.getActionButton(DialogAction.POSITIVE).setEnabled(true); - } catch (Exception ignored) { - Timber.w(ignored); + } catch (Exception e) { + Timber.w(e); dialog.getActionButton(DialogAction.POSITIVE).setEnabled(false); } } From 2911cbde25c9906685d9ac68da2e2bfd28d50280 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Mon, 5 Apr 2021 23:05:24 +0200 Subject: [PATCH 111/171] NF: useless generic --- .../com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index 4319ca559f12..7dbcc7a84c0a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -362,7 +362,7 @@ private int[] getListIds(int dialogId) { switch (dialogId) { case CONTEXT_MENU_STANDARD: // Standard context menu - ArrayList dialogOptions = new ArrayList(); + ArrayList dialogOptions = new ArrayList<>(); dialogOptions.add(CUSTOM_STUDY_NEW); dialogOptions.add(CUSTOM_STUDY_REV); dialogOptions.add(CUSTOM_STUDY_FORGOT); From db2294f265d9bc1eb30cf1b527fa08b4677c13c0 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Tue, 6 Apr 2021 00:24:14 +0200 Subject: [PATCH 112/171] NF: use FragmentFactory for CustomStudyDialog --- .../main/java/com/ichi2/anki/DeckPicker.java | 9 +- .../com/ichi2/anki/StudyOptionsActivity.java | 3 + .../com/ichi2/anki/StudyOptionsFragment.java | 9 +- .../anki/dialogs/DeckPickerContextMenu.java | 10 +- .../customstudy/CustomStudyDialog.java | 63 ++++++----- .../customstudy/CustomStudyDialogFactory.java | 35 ++++++ .../anki/dialogs/CustomStudyDialogTest.java | 103 +++++++++--------- 7 files changed, 142 insertions(+), 90 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialogFactory.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 6203c99b7a32..4ed3d2e08132 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -102,6 +102,7 @@ import com.ichi2.anki.dialogs.ImportDialog; import com.ichi2.anki.dialogs.MediaCheckDialog; import com.ichi2.anki.dialogs.SyncErrorDialog; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialogFactory; import com.ichi2.anki.exception.ConfirmModSchemaException; import com.ichi2.anki.exception.DeckRenameException; import com.ichi2.anki.exception.FilteredAncestor; @@ -251,6 +252,8 @@ public class DeckPicker extends NavigationDrawerActivity implements private SearchView mToolbarSearchView; + private CustomStudyDialogFactory mCustomStudyDialogFactory; + // ---------------------------------------------------------------------------- // LISTENERS // ---------------------------------------------------------------------------- @@ -447,6 +450,8 @@ protected void onCreate(Bundle savedInstanceState) throws SQLException { return; } + mCustomStudyDialogFactory = new CustomStudyDialogFactory(this::getCol, this).attachToActivity(this); + SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(getBaseContext()); //we need to restore here, as we need it before super.onCreate() is called. @@ -2302,7 +2307,7 @@ private void handleDeckSelection(long did, DeckSelectionType selectionType) { } else if (getCol().getSched().newDue() || getCol().getSched().revDue()) { // If there are no cards to review because of the daily study limit then give "Study more" option UIUtils.showSnackbar(this, R.string.studyoptions_limit_reached, false, R.string.study_more, v -> { - CustomStudyDialog d = CustomStudyDialog.newInstance( + CustomStudyDialog d = mCustomStudyDialogFactory.newCustomStudyDialog().withArguments( CustomStudyDialog.CONTEXT_MENU_LIMITS, getCol().getDecks().selected(), true); showDialogFragment(d); @@ -2333,7 +2338,7 @@ private void handleDeckSelection(long did, DeckSelectionType selectionType) { } else { // Otherwise say there are no cards scheduled to study, and give option to do custom study UIUtils.showSnackbar(this, R.string.studyoptions_empty_schedule, false, R.string.custom_study, v -> { - CustomStudyDialog d = CustomStudyDialog.newInstance( + CustomStudyDialog d = mCustomStudyDialogFactory.newCustomStudyDialog().withArguments( CustomStudyDialog.CONTEXT_MENU_EMPTY_SCHEDULE, getCol().getDecks().selected(), true); showDialogFragment(d); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java index 809ed9b7e637..aad62cdc1757 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsActivity.java @@ -23,6 +23,7 @@ import com.ichi2.anki.StudyOptionsFragment.StudyOptionsListener; import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialogFactory; import com.ichi2.widget.WidgetStatus; import timber.log.Timber; @@ -38,6 +39,8 @@ protected void onCreate(Bundle savedInstanceState) { if (showedActivityFailedScreen(savedInstanceState)) { return; } + CustomStudyDialogFactory customStudyDialogFactory = new CustomStudyDialogFactory(this::getCol, this); + customStudyDialogFactory.attachToActivity(this); super.onCreate(savedInstanceState); // The empty frame layout is a workaround for fragments not showing when they are added // to android.R.id.content when an action bar is used in Android 2.1 (and potentially diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java index 00134d0f6a51..15f7136aa834 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java @@ -55,6 +55,7 @@ import com.ichi2.utils.BooleanGetter; import com.ichi2.utils.HtmlUtils; +import androidx.fragment.app.FragmentFactory; import timber.log.Timber; import static com.ichi2.anim.ActivityTransitionAnimation.Direction.*; @@ -288,9 +289,11 @@ private void initAllContentViews(@NonNull View studyOptionsView) { * Show the context menu for the custom study options */ private void showCustomStudyContextMenu() { - CustomStudyDialog d = CustomStudyDialog.newInstance(CustomStudyDialog.CONTEXT_MENU_STANDARD, - getCol().getDecks().selected()); - ((AnkiActivity)getActivity()).showDialogFragment(d); + final AnkiActivity ankiActivity = ((AnkiActivity) requireActivity()); + final FragmentFactory fragmentFactory = ankiActivity.getSupportFragmentManager().getFragmentFactory(); + CustomStudyDialog d = (CustomStudyDialog) fragmentFactory.instantiate(ankiActivity.getClassLoader(), CustomStudyDialog.class.getName()); + d.withArguments(CustomStudyDialog.CONTEXT_MENU_STANDARD, getCol().getDecks().selected()); + ankiActivity.showDialogFragment(d); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java index 7b5f30484c59..36b4f513735c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java @@ -35,6 +35,7 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentFactory; import timber.log.Timber; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -160,9 +161,12 @@ int[] getListIds() { case CONTEXT_MENU_CUSTOM_STUDY: { Timber.i("Custom study option selected"); long did = getArguments().getLong("did"); - CustomStudyDialog d = CustomStudyDialog.newInstance( - CustomStudyDialog.CONTEXT_MENU_STANDARD, did); - ((AnkiActivity) getActivity()).showDialogFragment(d); + + final AnkiActivity ankiActivity = ((AnkiActivity) requireActivity()); + final FragmentFactory fragmentFactory = ankiActivity.getSupportFragmentManager().getFragmentFactory(); + CustomStudyDialog d = (CustomStudyDialog) fragmentFactory.instantiate(ankiActivity.getClassLoader(), CustomStudyDialog.class.getName()); + d.withArguments(CustomStudyDialog.CONTEXT_MENU_STANDARD, did); + ankiActivity.showDialogFragment(d); break; } case CONTEXT_MENU_CREATE_SHORTCUT: diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index 7dbcc7a84c0a..9fc654873481 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -75,7 +75,7 @@ public class CustomStudyDialog extends AnalyticsDialogFragment implements private static final int CUSTOM_STUDY_REV = 101; private static final int CUSTOM_STUDY_FORGOT = 102; @VisibleForTesting - static final int CUSTOM_STUDY_AHEAD = 103; + public static final int CUSTOM_STUDY_AHEAD = 103; private static final int CUSTOM_STUDY_RANDOM = 104; private static final int CUSTOM_STUDY_PREVIEW = 105; private static final int CUSTOM_STUDY_TAGS = 106; @@ -83,6 +83,17 @@ public class CustomStudyDialog extends AnalyticsDialogFragment implements private static final int DECK_OPTIONS = 107; private static final int MORE_OPTIONS = 108; + + private final CustomStudyListener mCustomStudyListener; + private final Collection mCollection; + + + public CustomStudyDialog(Collection collection, CustomStudyListener customStudyListener) { + this.mCollection = collection; + this.mCustomStudyListener = customStudyListener; + } + + public interface CustomStudyListener extends CreateCustomStudySessionListener.Callback { void onExtendStudyLimits(); void showDialogFragment(DialogFragment newFragment); @@ -90,34 +101,22 @@ public interface CustomStudyListener extends CreateCustomStudySessionListener.Ca void startActivityForResultWithoutAnimation(Intent intent, int requestCode); } - /** - * Instance factories - */ - public static CustomStudyDialog newInstance(int id, long did) { - return newInstance(id, did, false); + + public CustomStudyDialog withArguments(int id, long did) { + return withArguments(id, did, false); } - public static CustomStudyDialog newInstance(int id, long did, boolean jumpToReviewer) { - CustomStudyDialog f = new CustomStudyDialog(); - Bundle args = new Bundle(); + + public CustomStudyDialog withArguments(int id, long did, boolean jumpToReviewer) { + Bundle args = this.getArguments(); + if (args == null) { + args = new Bundle(); + } args.putInt("id", id); args.putLong("did", did); args.putBoolean("jumpToReviewer", jumpToReviewer); - f.setArguments(args); - return f; - } - - - private CustomStudyListener mCustomStudyListener; - private Collection mCollection; - - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - AnkiActivity ankiActivity = (AnkiActivity) requireActivity(); - mCustomStudyListener = (CustomStudyListener) ankiActivity; - mCollection = ankiActivity.getCol(); + this.setArguments(args); + return this; } @@ -158,8 +157,12 @@ private MaterialDialog buildContextMenu(int id) { } case MORE_OPTIONS: { // User asked to see all custom study options - CustomStudyDialog d = CustomStudyDialog.newInstance(CONTEXT_MENU_STANDARD, - requireArguments().getLong("did"), jumpToReviewer); + final CustomStudyDialog d = new CustomStudyDialog(mCollection, mCustomStudyListener) + .withArguments( + CONTEXT_MENU_STANDARD, + requireArguments().getLong("did"), + jumpToReviewer + ); mCustomStudyListener.showDialogFragment(d); break; } @@ -178,8 +181,12 @@ private MaterialDialog buildContextMenu(int id) { } default: { // User asked for a standard custom study option - CustomStudyDialog d = CustomStudyDialog.newInstance(view.getId(), - requireArguments().getLong("did"), jumpToReviewer); + final CustomStudyDialog d = new CustomStudyDialog(mCollection, mCustomStudyListener) + .withArguments( + view.getId(), + requireArguments().getLong("did"), + jumpToReviewer + ); mCustomStudyListener.showDialogFragment(d); } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialogFactory.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialogFactory.java new file mode 100644 index 000000000000..331204ff32a9 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialogFactory.java @@ -0,0 +1,35 @@ +package com.ichi2.anki.dialogs.customstudy; + +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener; +import com.ichi2.libanki.Collection; +import com.ichi2.utils.ExtendedFragmentFactory; +import com.ichi2.utils.FunctionalInterfaces.Supplier; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +public class CustomStudyDialogFactory extends ExtendedFragmentFactory { + + final Supplier mCollectionSupplier; + final CustomStudyListener mCustomStudyListener; + + public CustomStudyDialogFactory(Supplier mCollectionSupplier, CustomStudyListener mCustomStudyListener) { + this.mCollectionSupplier = mCollectionSupplier; + this.mCustomStudyListener = mCustomStudyListener; + } + + @NonNull + @Override + public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { + Class cls = loadFragmentClass(classLoader, className); + if (cls == CustomStudyDialog.class) { + return newCustomStudyDialog(); + } + return super.instantiate(classLoader, className); + } + + @NonNull + public CustomStudyDialog newCustomStudyDialog() { + return new CustomStudyDialog(mCollectionSupplier.get(), mCustomStudyListener); + } +} diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java index 6b88ccf2bc94..be9974be8aba 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java @@ -16,25 +16,27 @@ package com.ichi2.anki.dialogs; -import android.content.Intent; +import android.os.Bundle; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; -import com.ichi2.anki.AnkiActivity; -import com.ichi2.anki.CardTemplateBrowserAppearanceEditor; import com.ichi2.anki.R; import com.ichi2.anki.RobolectricTest; import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog.CustomStudyListener; +import com.ichi2.anki.dialogs.customstudy.CustomStudyDialogFactory; +import com.ichi2.libanki.Collection; import com.ichi2.libanki.Deck; +import com.ichi2.libanki.sched.AbstractSched; import org.hamcrest.Matchers; +import org.junit.After; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.robolectric.annotation.Config; -import androidx.annotation.NonNull; import androidx.fragment.app.testing.FragmentScenario; import androidx.lifecycle.Lifecycle; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -42,16 +44,43 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsNull.notNullValue; +import static org.mockito.Mockito.*; @RunWith(AndroidJUnit4.class) public class CustomStudyDialogTest extends RobolectricTest { + CustomStudyListener mockListener; + + + @Override + public void setUp() { + super.setUp(); + mockListener = mock(CustomStudyListener.class); + } + + + @Override + @After + public void tearDown() { + super.tearDown(); + reset(mockListener); + } + + + private static T whatever() { + return null; + } + @Test public void learnAheadCardsRegressionTest() { // #6289 - Regression Test - CustomStudyDialog args = CustomStudyDialog.newInstance(CustomStudyDialog.CUSTOM_STUDY_AHEAD, 1); + Bundle args = new CustomStudyDialog(whatever(), whatever()) + .withArguments(CustomStudyDialog.CUSTOM_STUDY_AHEAD, 1) + .getArguments(); - FragmentScenario scenario = getDialogScenario(args); + + CustomStudyDialogFactory factory = new CustomStudyDialogFactory(this::getCol, mockListener); + FragmentScenario scenario = FragmentScenario.launch(CustomStudyDialog.class, args, factory); scenario.moveToState(Lifecycle.State.STARTED); @@ -75,63 +104,29 @@ public void learnAheadCardsRegressionTest() { @Test - @Ignore("#8406") + @Ignore("ERROR") //java.lang.UnsupportedOperationException: Failed to resolve attribute at index 13: TypedValue{t=0x2/d=0x7f040340 a=-1} @Config(qualifiers = "en") public void increaseNewCardLimitRegressionTest(){ // #8338 - Regression Test - CustomStudyDialog standard = CustomStudyDialog.newInstance(CustomStudyDialog.CONTEXT_MENU_STANDARD, 1); + Bundle args = new CustomStudyDialog(whatever(), whatever()) + .withArguments(CustomStudyDialog.CONTEXT_MENU_STANDARD, 1) + .getArguments(); + + Collection mockCollection = mock(Collection.class, Mockito.RETURNS_DEEP_STUBS); + AbstractSched mockSched = mock(AbstractSched.class); + when(mockCollection.getSched()).thenReturn(mockSched); + when(mockSched.newCount()).thenReturn(0); - FragmentScenario scenarioStandard = getDialogScenario(standard); - scenarioStandard.moveToState(Lifecycle.State.STARTED); + CustomStudyDialogFactory factory = new CustomStudyDialogFactory(() -> mockCollection, mockListener); + FragmentScenario scenario = FragmentScenario.launch(CustomStudyDialog.class, args, factory); - scenarioStandard.onFragment(f -> { + scenario.moveToState(Lifecycle.State.STARTED); + + scenario.onFragment(f -> { MaterialDialog dialog = (MaterialDialog)f.getDialog(); assertThat(dialog,notNullValue()); assertThat(dialog.getItems(), Matchers.not(Matchers.hasItem(getResourceString(R.string.custom_study_increase_new_limit)))); }); } - - - @NonNull - private FragmentScenario getDialogScenario(CustomStudyDialog args) { - FragmentScenario scenario = FragmentScenario.launch(CustomStudyDialogForTesting.class, args.getArguments()); - - // Pick an arbitrary easy activity - CustomStudyActivity d = super.startActivityNormallyOpenCollectionWithIntent(CustomStudyActivity.class, new Intent()); - - scenario.onFragment(f -> f.setActivity(d)); - return scenario; - } - - private static class CustomStudyActivity extends CardTemplateBrowserAppearanceEditor implements CustomStudyListener { - - @Override - public void onCreateCustomStudySession() { - // Intentionally blank - } - - - @Override - public void onExtendStudyLimits() { - // Intentionally blank - } - } - - - public static class CustomStudyDialogForTesting extends CustomStudyDialog { - private AnkiActivity mAnkiActivity; - - @SuppressWarnings("WeakerAccess") - public void setActivity(T ankiActivity) { - mAnkiActivity = ankiActivity; - } - - - @NonNull - @Override - protected AnkiActivity requireAnkiActivity() { - return mAnkiActivity; - } - } } From dd40d95d3358a8343b70eb0221eed29cab2e7d1b Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Tue, 6 Apr 2021 17:17:18 +0200 Subject: [PATCH 113/171] fix increaseNewCardLimitRegressionTest + we are using mock collection for the CustomStudyDialog but still other parts of the code access a real collection, so we must ensure that collection is loaded first so we don't get net/ankiweb/rsdroid/BackendException$BackendDbException$BackendDbLockedException + R.style.Theme_AppCompat must be passed to avoid java.lang.UnsupportedOperationException: Failed to resolve attribute at index 13: TypedValue{t=0x2/d=0x7f040340 a=-1} --- .../com/ichi2/anki/dialogs/CustomStudyDialogTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java index be9974be8aba..406182eee9fb 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/CustomStudyDialogTest.java @@ -31,7 +31,6 @@ import org.hamcrest.Matchers; import org.junit.After; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -104,7 +103,6 @@ public void learnAheadCardsRegressionTest() { @Test - @Ignore("ERROR") //java.lang.UnsupportedOperationException: Failed to resolve attribute at index 13: TypedValue{t=0x2/d=0x7f040340 a=-1} @Config(qualifiers = "en") public void increaseNewCardLimitRegressionTest(){ // #8338 - Regression Test @@ -112,6 +110,11 @@ public void increaseNewCardLimitRegressionTest(){ .withArguments(CustomStudyDialog.CONTEXT_MENU_STANDARD, 1) .getArguments(); + // we are using mock collection for the CustomStudyDialog but still other parts of the code + // access a real collection, so we must ensure that collection is loaded first + // so we don't get net/ankiweb/rsdroid/BackendException$BackendDbException$BackendDbLockedException + ensureCollectionLoadIsSynchronous(); + Collection mockCollection = mock(Collection.class, Mockito.RETURNS_DEEP_STUBS); AbstractSched mockSched = mock(AbstractSched.class); when(mockCollection.getSched()).thenReturn(mockSched); @@ -119,7 +122,7 @@ public void increaseNewCardLimitRegressionTest(){ CustomStudyDialogFactory factory = new CustomStudyDialogFactory(() -> mockCollection, mockListener); - FragmentScenario scenario = FragmentScenario.launch(CustomStudyDialog.class, args, factory); + FragmentScenario scenario = FragmentScenario.launch(CustomStudyDialog.class, args, R.style.Theme_AppCompat, factory); scenario.moveToState(Lifecycle.State.STARTED); From ef6eb1b69f0dcab6983209295f40bfe3b6ad7958 Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Tue, 6 Apr 2021 18:36:36 +0200 Subject: [PATCH 114/171] NF: use requireContext instead of requireActivity Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> --- .../com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java index 9fc654873481..124c9d014f19 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/customstudy/CustomStudyDialog.java @@ -473,7 +473,7 @@ private void createCustomStudySession(JSONArray delays, Object[] terms, Boolean Timber.i("Found deck: '%s'", customStudyDeck); if (cur.isStd()) { Timber.w("Deck: '%s' was non-dynamic", customStudyDeck); - UIUtils.showThemedToast(requireActivity(), getString(R.string.custom_study_deck_exists), true); + UIUtils.showThemedToast(requireContext(), getString(R.string.custom_study_deck_exists), true); return; } else { Timber.i("Emptying dynamic deck '%s' for custom study", customStudyDeck); From d7057d3c979086f2c5712156ac6c564a57cda9cd Mon Sep 17 00:00:00 2001 From: Tarekk Mohamed Abdalla Date: Tue, 6 Apr 2021 19:08:20 +0200 Subject: [PATCH 115/171] NF: introduce FragmentFactoryUtils#instantiate A convenience util method that instantiate a fragment using the passed activity's fragment factory + Added simple test --- .../com/ichi2/anki/StudyOptionsFragment.java | 4 +- .../anki/dialogs/DeckPickerContextMenu.java | 4 +- .../com/ichi2/utils/FragmentFactoryUtils.java | 32 +++++++++++ .../ichi2/utils/FragmentFactoryUtilsTest.java | 54 +++++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/utils/FragmentFactoryUtils.java create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/FragmentFactoryUtilsTest.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java index 15f7136aa834..e14c65918ecd 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java @@ -53,6 +53,7 @@ import com.ichi2.libanki.Deck; import com.ichi2.themes.StyledProgressDialog; import com.ichi2.utils.BooleanGetter; +import com.ichi2.utils.FragmentFactoryUtils; import com.ichi2.utils.HtmlUtils; import androidx.fragment.app.FragmentFactory; @@ -290,8 +291,7 @@ private void initAllContentViews(@NonNull View studyOptionsView) { */ private void showCustomStudyContextMenu() { final AnkiActivity ankiActivity = ((AnkiActivity) requireActivity()); - final FragmentFactory fragmentFactory = ankiActivity.getSupportFragmentManager().getFragmentFactory(); - CustomStudyDialog d = (CustomStudyDialog) fragmentFactory.instantiate(ankiActivity.getClassLoader(), CustomStudyDialog.class.getName()); + CustomStudyDialog d = FragmentFactoryUtils.instantiate(ankiActivity, CustomStudyDialog.class); d.withArguments(CustomStudyDialog.CONTEXT_MENU_STANDARD, getCol().getDecks().selected()); ankiActivity.showDialogFragment(d); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java index 36b4f513735c..3891b4b4c5bb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckPickerContextMenu.java @@ -28,6 +28,7 @@ import com.ichi2.anki.analytics.AnalyticsDialogFragment; import com.ichi2.anki.dialogs.customstudy.CustomStudyDialog; import com.ichi2.libanki.Collection; +import com.ichi2.utils.FragmentFactoryUtils; import java.lang.annotation.Retention; import java.util.ArrayList; @@ -163,8 +164,7 @@ int[] getListIds() { long did = getArguments().getLong("did"); final AnkiActivity ankiActivity = ((AnkiActivity) requireActivity()); - final FragmentFactory fragmentFactory = ankiActivity.getSupportFragmentManager().getFragmentFactory(); - CustomStudyDialog d = (CustomStudyDialog) fragmentFactory.instantiate(ankiActivity.getClassLoader(), CustomStudyDialog.class.getName()); + CustomStudyDialog d = FragmentFactoryUtils.instantiate(ankiActivity, CustomStudyDialog.class); d.withArguments(CustomStudyDialog.CONTEXT_MENU_STANDARD, did); ankiActivity.showDialogFragment(d); break; diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/FragmentFactoryUtils.java b/AnkiDroid/src/main/java/com/ichi2/utils/FragmentFactoryUtils.java new file mode 100644 index 000000000000..c65817f8268c --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/utils/FragmentFactoryUtils.java @@ -0,0 +1,32 @@ +/* + Copyright (c) 2021 Tarek Mohamed + + 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.utils; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentFactory; + +public class FragmentFactoryUtils { + + /** + * A convenience util method that instantiate a fragment using the passed activity {@link FragmentFactory} + */ + public static F instantiate(FragmentActivity activity, Class cls) { + final FragmentFactory factory = activity.getSupportFragmentManager().getFragmentFactory(); + return (F) factory.instantiate(activity.getClassLoader(), cls.getName()); + } + +} diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/FragmentFactoryUtilsTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/FragmentFactoryUtilsTest.java new file mode 100644 index 000000000000..8a98f3f88827 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/FragmentFactoryUtilsTest.java @@ -0,0 +1,54 @@ +/* + Copyright (c) 2021 Tarek Mohamed + + 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.utils; + +import org.junit.Test; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentFactory; +import androidx.fragment.app.FragmentManager; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +public class FragmentFactoryUtilsTest { + + + private static class TestFragment extends Fragment {} + + @Test + public void test_instantiate() { + FragmentActivity activity = mock(FragmentActivity.class); + FragmentManager manager = mock(FragmentManager.class); + FragmentFactory factory = mock(FragmentFactory.class); + ClassLoader classLoader = mock(ClassLoader.class); + + TestFragment testFragment = new TestFragment(); + + when(activity.getSupportFragmentManager()).thenReturn(manager); + when(activity.getClassLoader()).thenReturn(classLoader); + + when(manager.getFragmentFactory()).thenReturn(factory); + when(factory.instantiate(classLoader, testFragment.getClass().getName())) + .thenReturn(testFragment); + + + Fragment result = FragmentFactoryUtils.instantiate(activity, TestFragment.class); + assertEquals(testFragment, result); + verify(factory, times(1)).instantiate(classLoader, testFragment.getClass().getName()); + } +} \ No newline at end of file From e35a337155fe2e80564ff14cd694a3388505ca7a Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Thu, 8 Apr 2021 22:05:09 +0100 Subject: [PATCH 116/171] Extract and test dayOffset related calculations In preparation for Issue 6203 --- .../main/java/com/ichi2/anki/Preferences.java | 30 ++++++++---- .../java/com/ichi2/anki/PreferencesTest.java | 48 +++++++++++++++++++ 2 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java index c560f7c3bbe6..76179bcb2871 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java @@ -572,8 +572,7 @@ private void initPreference(android.preference.Preference pref) { ((android.preference.ListPreference)pref).setValueIndex(conf.getInt("newSpread")); break; case "dayOffset": - Calendar calendar = col.crtGregorianCalendar(); - ((SeekBarPreference)pref).setValue(calendar.get(Calendar.HOUR_OF_DAY)); + ((SeekBarPreference)pref).setValue(getDayOffset(col)); break; case "newTimezoneHandling": android.preference.CheckBoxPreference checkBox = (android.preference.CheckBoxPreference) pref; @@ -603,6 +602,21 @@ private void initPreference(android.preference.Preference pref) { updateSummary(pref); } + @VisibleForTesting + public static int getDayOffset(Collection col) { + Calendar calendar = col.crtGregorianCalendar(); + return calendar.get(Calendar.HOUR_OF_DAY); + } + + @VisibleForTesting + public void setDayOffset(int hours) { + Calendar date = getCol().crtGregorianCalendar(); + date.set(Calendar.HOUR_OF_DAY, hours); + getCol().setCrt(date.getTimeInMillis() / 1000); + getCol().setMod(); + BootService.scheduleNotification(getCol().getTime(), this); + } + /** * Code which is run when a SharedPreference change has been detected @@ -660,12 +674,7 @@ private void updatePreference(SharedPreferences prefs, String key, PreferenceCon getCol().setMod(); break; case "dayOffset": { - int hours = ((SeekBarPreference) pref).getValue(); - Calendar date = getCol().crtGregorianCalendar(); - date.set(Calendar.HOUR_OF_DAY, hours); - getCol().setCrt(date.getTimeInMillis() / 1000); - getCol().setMod(); - BootService.scheduleNotification(getCol().getTime(), this); + setDayOffset(((SeekBarPreference) pref).getValue()); break; } case "minimumCardsDueForNotification": { @@ -1048,4 +1057,9 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin ((Preferences) getActivity()).updatePreference(sharedPreferences, key, this); } } + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public void attachBaseContext(Context context) { + super.attachBaseContext(context); + } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java new file mode 100644 index 000000000000..1c2822d21087 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 David Allison + * + * 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; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +@RunWith(AndroidJUnit4.class) +public class PreferencesTest extends RobolectricTest { + + @Test + public void testDayOffsetExhaustive() { + Preferences preferences = getInstance(); + for (int i = 0; i < 24; i++) { + preferences.setDayOffset(i); + assertThat(Preferences.getDayOffset(getCol()), is(i)); + } + } + + + @NonNull + protected Preferences getInstance() { + Preferences preferences = new Preferences(); + preferences.attachBaseContext(getTargetContext()); + return preferences; + } + +} From 6fec7d3f078114775647f5752731413a5f6df732 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:08:31 +0100 Subject: [PATCH 117/171] fix: SchedV2 rollover time We were not setting "rollover", but were using it in SchedV2 Now, we set this in the preferences if SchedV2 is being used Replicates Anki Commit: https://github.com/ankitects/anki/commit/b17a0552d061af360e35e1cda48b2d228c94b66e Fixes 6203 --- .../main/java/com/ichi2/anki/Preferences.java | 38 ++++++++++++++++--- .../com/ichi2/anki/services/BootService.java | 25 +++++++++++- .../java/com/ichi2/anki/PreferencesTest.java | 24 ++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java index 76179bcb2871..4c2177096bf7 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java @@ -602,22 +602,48 @@ private void initPreference(android.preference.Preference pref) { updateSummary(pref); } + /** Returns the hour that the collection rolls over to the next day */ @VisibleForTesting public static int getDayOffset(Collection col) { - Calendar calendar = col.crtGregorianCalendar(); - return calendar.get(Calendar.HOUR_OF_DAY); + switch (col.schedVer()) { + default: + case 1: + Calendar calendar = col.crtGregorianCalendar(); + return calendar.get(Calendar.HOUR_OF_DAY); + case 2: + return col.getConf().optInt("rollover", 4); + } } + /** Sets the hour that the collection rolls over to the next day */ @VisibleForTesting public void setDayOffset(int hours) { - Calendar date = getCol().crtGregorianCalendar(); - date.set(Calendar.HOUR_OF_DAY, hours); - getCol().setCrt(date.getTimeInMillis() / 1000); - getCol().setMod(); + switch (getSchedVer(getCol())) { + default: + case 1: + Calendar date = getCol().crtGregorianCalendar(); + date.set(Calendar.HOUR_OF_DAY, hours); + getCol().setCrt(date.getTimeInMillis() / 1000); + getCol().setMod(); + break; + case 2: + getCol().getConf().put("rollover", hours); + getCol().flush(); + break; + } BootService.scheduleNotification(getCol().getTime(), this); } + protected static int getSchedVer(Collection col) { + int ver = col.schedVer(); + if (ver < 1 || ver > 2) { + Timber.w("Unknown scheduler version: %d", ver); + } + return ver; + } + + /** * Code which is run when a SharedPreference change has been detected * @param prefs instance of SharedPreferences diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java b/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java index 4a8a0e594e81..2057dedc1cc3 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java @@ -131,7 +131,7 @@ public static void scheduleNotification(Time time, Context context) { } final Calendar calendar = time.calendar(); - calendar.set(Calendar.HOUR_OF_DAY, sp.getInt("dayOffset", 0)); + calendar.set(Calendar.HOUR_OF_DAY, getRolloverHourOfDay(context)); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); final PendingIntent notificationIntent = @@ -143,4 +143,27 @@ public static void scheduleNotification(Time time, Context context) { notificationIntent ); } + + /** Returns the hour of day when rollover to the next day occurs */ + protected static int getRolloverHourOfDay(Context context) { + // TODO; We might want to use the BootService retry code here when called from preferences. + + // TODO: This is a historic value, might be better as the default 4 is used elsewhere for rollover + int defValue = 0; + + try { + Collection col = CollectionHelper.getInstance().getCol(context); + switch (col.schedVer()) { + default: + case 1: + SharedPreferences sp = AnkiDroidApp.getSharedPrefs(context); + return sp.getInt("dayOffset", defValue); + case 2: + return col.getConf().optInt("rollover", defValue); + } + } catch (Exception e) { + Timber.w(e); + return defValue; + } + } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java index 1c2822d21087..3e81fcf5aafa 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/PreferencesTest.java @@ -16,6 +16,8 @@ package com.ichi2.anki; +import com.ichi2.anki.exception.ConfirmModSchemaException; + import org.junit.Test; import org.junit.runner.RunWith; @@ -37,6 +39,28 @@ public void testDayOffsetExhaustive() { } } + @Test + public void testDayOffsetExhaustiveV2() throws ConfirmModSchemaException { + getCol().changeSchedulerVer(2); + Preferences preferences = getInstance(); + for (int i = 0; i < 24; i++) { + preferences.setDayOffset(i); + assertThat(Preferences.getDayOffset(getCol()), is(i)); + } + } + + @Test + public void setDayOffsetSetsConfig() throws ConfirmModSchemaException { + getCol().changeSchedulerVer(2); + Preferences preferences = getInstance(); + + int offset = Preferences.getDayOffset(getCol()); + assertThat("Default offset should be 4", offset, is(4)); + + preferences.setDayOffset(2); + + assertThat("rollover config should be set to new value", getCol().getConf().optInt("rollover", 4), is(2)); + } @NonNull protected Preferences getInstance() { From 719fe26d1b9c6838cf04ea693cd0a299e664eb34 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:09:33 +0100 Subject: [PATCH 118/171] NF: simplify method call --- AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java | 1 - .../src/main/java/com/ichi2/libanki/stats/Stats.java | 9 ++------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java index 4c2177096bf7..521913f51ee4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java @@ -603,7 +603,6 @@ private void initPreference(android.preference.Preference pref) { } /** Returns the hour that the collection rolls over to the next day */ - @VisibleForTesting public static int getDayOffset(Collection col) { switch (col.schedVer()) { default: diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/stats/Stats.java b/AnkiDroid/src/main/java/com/ichi2/libanki/stats/Stats.java index 7bb07cfd1083..3cc8b65f3082 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/stats/Stats.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/stats/Stats.java @@ -23,6 +23,7 @@ import android.util.Pair; import com.ichi2.anki.AnkiDroidApp; +import com.ichi2.anki.Preferences; import com.ichi2.anki.R; import com.ichi2.anki.stats.OverviewStatsBuilder; import com.ichi2.anki.stats.OverviewStatsBuilder.OverviewStats.AnswerButtonsOverview; @@ -865,13 +866,7 @@ public boolean calculateBreakdown(AxisType type) { if (lim.length() > 0) { lim = " and " + lim; } - int rolloverHour; - if (mCol.schedVer() == 1) { - Calendar sd = mCol.crtGregorianCalendar(); - rolloverHour = sd.get(Calendar.HOUR_OF_DAY); - } else { - rolloverHour = mCol.getConf().optInt("rollover", 4); - } + int rolloverHour = Preferences.getDayOffset(mCol); int pd = _periodDays(); if (pd > 0) { lim += " and id > " + ((mCol.getSched().getDayCutoff() - (SECONDS_PER_DAY * pd)) * 1000); From 02f784593c0b391364dd8e52d3cb98bb21e01f2a Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:11:08 +0100 Subject: [PATCH 119/171] BootService: Use 4AM as the default rollover hour All other code uses 4, it seems like a more sensible default This will be correct if the preference was never set, but won't be if the preference was set, but an error occurred. --- .../src/main/java/com/ichi2/anki/services/BootService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java b/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java index 2057dedc1cc3..399711320632 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/services/BootService.java @@ -148,8 +148,7 @@ public static void scheduleNotification(Time time, Context context) { protected static int getRolloverHourOfDay(Context context) { // TODO; We might want to use the BootService retry code here when called from preferences. - // TODO: This is a historic value, might be better as the default 4 is used elsewhere for rollover - int defValue = 0; + int defValue = 4; try { Collection col = CollectionHelper.getInstance().getCol(context); From ce2d6bc2579d059c6d655565aa56f5dc0dff24b6 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sun, 11 Apr 2021 16:03:02 +0200 Subject: [PATCH 120/171] NF: NonNull note --- AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java | 1 + .../src/main/java/com/ichi2/libanki/Collection.java | 10 +++++----- .../com/ichi2/anki/AbstractFlashcardViewerTest.java | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java index f63ba12e6b9e..b37f73bb2396 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.java @@ -222,6 +222,7 @@ public class NoteEditor extends AnkiActivity implements private Spinner mNoteTypeSpinner; private Spinner mNoteDeckSpinner; + // Non Null after onCollectionLoaded, but still null after construction. So essentially @NonNull but it would fail. private Note mEditorNote; @Nullable /* Null if adding a new card. Presently NonNull if editing an existing note - but this is subject to change */ diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java index a5cf113851fa..0250f4b194e6 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Collection.java @@ -599,7 +599,7 @@ public int noteCount() { * Return a new note with the default model from the deck * @return The new note */ - public Note newNote() { + public @NonNull Note newNote() { return newNote(true); } @@ -609,7 +609,7 @@ public Note newNote() { * the configuration (curModel) * @return The new note */ - public Note newNote(boolean forDeck) { + public @NonNull Note newNote(boolean forDeck) { return newNote(getModels().current(forDeck)); } @@ -618,7 +618,7 @@ public Note newNote(boolean forDeck) { * @param m The model to use for the new note * @return The new note */ - public Note newNote(Model m) { + public @NonNull Note newNote(Model m) { return new Note(this, m); } @@ -627,7 +627,7 @@ public Note newNote(Model m) { * @param note A note to add if it generates card * @return Number of card added. */ - public int addNote(Note note) { + public int addNote(@NonNull Note note) { return addNote(note, Models.AllowEmpty.ONLY_CLOZE); } @@ -637,7 +637,7 @@ public int addNote(Note note) { * @param allowEmpty Whether we accept to add it even if it should generate no card. Useful to import note even if buggy * @return Number of card added */ - public int addNote(Note note, Models.AllowEmpty allowEmpty) { + public int addNote(@NonNull Note note, Models.AllowEmpty allowEmpty) { // check we have card models available, then save ArrayList cms = findTemplates(note, allowEmpty); // Todo: upstream, we accept to add a not even if it generates no card. Should be ported to ankidroid diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/AbstractFlashcardViewerTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/AbstractFlashcardViewerTest.java index 19d4ed943616..399036609e43 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/AbstractFlashcardViewerTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/AbstractFlashcardViewerTest.java @@ -403,4 +403,4 @@ private NonAbstractFlashcardViewer getViewer() { advanceRobolectricLooperWithSleep(); return viewer; } -} \ No newline at end of file +} From 300bb0b666ef2035bfc7eaf7306e48e8722dcc8a Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Mon, 12 Apr 2021 02:45:12 +0100 Subject: [PATCH 121/171] NF: rename: id_dont_create -> id_for_name https://github.com/david-allison-1/anki/blob/c9e12052e2c3d6c146f06461abef6f60ef8cca58/pylib/anki/decks.py#L141-L145 --- .../com/ichi2/anki/provider/CardContentProvider.java | 2 +- AnkiDroid/src/main/java/com/ichi2/libanki/Decks.java | 6 +++--- AnkiDroid/src/main/java/com/ichi2/libanki/Finder.java | 11 ++--------- .../src/test/java/com/ichi2/libanki/DecksTest.java | 11 ++++------- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java b/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java index 5f840e6e16b0..2aed04431ebd 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/provider/CardContentProvider.java @@ -1029,7 +1029,7 @@ public Uri insert(@NonNull Uri uri, ContentValues values) { case DECKS: // Insert new deck with specified name String deckName = values.getAsString(FlashCardsContract.Deck.DECK_NAME); - did = col.getDecks().id_dont_create(deckName); + did = col.getDecks().id_for_name(deckName); if (did != null) { throw new IllegalArgumentException("Deck name already exists: " + deckName); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Decks.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Decks.java index 58594036781a..f7ce747b42b9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Decks.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Decks.java @@ -314,7 +314,7 @@ public void flush() { * *********************************************************** */ - public Long id_dont_create(String name) { + public Long id_for_name(String name) { name = usable_name(name); Deck deck = byName(name); if (deck != null) { @@ -343,7 +343,7 @@ private String usable_name(String name) { */ public Long id(String name, String type) throws FilteredAncestor { name = usable_name(name); - Long id = id_dont_create(name); + Long id = id_for_name(name); if (id != null) { return id; } @@ -382,7 +382,7 @@ private Long id_create_name_valid(String name, String type) { */ public Long id_safe(String name, String type) { name = usable_name(name); - Long id = id_dont_create(name); + Long id = id_for_name(name); if (id != null) { return id; } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Finder.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Finder.java index df63d994f8d2..3952958578ec 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Finder.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Finder.java @@ -24,11 +24,7 @@ import android.util.Pair; -import com.ichi2.async.CancelListener; - import com.ichi2.async.CollectionTask; -import com.ichi2.async.ProgressSender; -import com.ichi2.libanki.Deck; import com.ichi2.utils.JSONArray; import com.ichi2.utils.JSONObject; @@ -37,13 +33,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; -import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -675,10 +668,10 @@ public String _findDeck(String val) { ids = dids(mCol.getDecks().selected()); } else if (!val.contains("*")) { // single deck - ids = dids(mCol.getDecks().id_dont_create(val)); + ids = dids(mCol.getDecks().id_for_name(val)); } else { // wildcard - ids = dids(mCol.getDecks().id_dont_create(val)); + ids = dids(mCol.getDecks().id_for_name(val)); if (ids == null) { ids = new ArrayList<>(); val = val.replace("*", ".*"); diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.java b/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.java index 4948dd1ed9ff..2bba1570125f 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/DecksTest.java @@ -13,10 +13,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; -import static android.service.autofill.Validators.not; import static com.ichi2.testutils.AnkiAssert.assertEqualsArrayList; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -25,7 +23,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.ArgumentMatchers.isNull; @@ -122,10 +119,10 @@ public void test_basic() throws FilteredAncestor { n.setItem("Front", "abc"); col.addNote(n); - assertEquals(decks.id_dont_create("new deck").longValue(), parentId); - assertEquals(decks.id_dont_create(" New Deck ").longValue(), parentId); - assertNull(decks.id_dont_create("Not existing deck")); - assertNull(decks.id_dont_create("new deck::not either")); + assertEquals(decks.id_for_name("new deck").longValue(), parentId); + assertEquals(decks.id_for_name(" New Deck ").longValue(), parentId); + assertNull(decks.id_for_name("Not existing deck")); + assertNull(decks.id_for_name("new deck::not either")); } From db3b52eec8f7bae1e2875c7efd36ae9eba35a990 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 9 Apr 2021 10:21:30 +0200 Subject: [PATCH 122/171] NF: comment and simplify DismissNote --- .../java/com/ichi2/async/CollectionTask.java | 61 +++++++++++++------ 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index 365b84d3cc96..aedd2b71e175 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -450,22 +450,43 @@ public UndoDeleteNote(Note note, ArrayList allCs, @NonNull Card card) { } } + + + /** + * Represents an action that remove a card from the Reviewer without reviewing it. + */ public static abstract class DismissNote extends Task { - private final Card mCard; + protected final Card mCard; + /** + * @param card The card that was in the reviewer. It usually is cloned and then restored if the action is undone + */ public DismissNote(Card card) { this.mCard = card; } - protected abstract void actualTask(Collection col, Card card); + /** + * The part of the task that is specific to this object. E.g. suspending, deleting, burying... + * @param col The collection + * + */ + protected abstract void actualTask(Collection col); + + /** + * @param col + * @param collectionTask A listener for the task. It waits for a card to display in the reviewer. + * Fetching a new card can possibly be cancelled, however the actual task is not cancellable. + Indeed, if you clicked on suspend and leave the reviewer, the card should still be reviewed and there is no need for a next card. + * @return whether the action ended succesfully + */ protected BooleanGetter task(Collection col, ProgressSenderAndCancelListener collectionTask) { try { col.getDb().executeInTransaction(() -> { col.getSched().deferReset(); - actualTask(col, mCard); + actualTask(col); // With sHadCardQueue set, getCard() resets the scheduler prior to getting the next card collectionTask.doProgress(col.getSched().getCard()); }); @@ -484,11 +505,11 @@ public BuryCard(Card card) { } @Override - protected void actualTask(Collection col, Card card) { + protected void actualTask(Collection col) { // collect undo information - col.markUndo(revertToProvidedState(R.string.menu_bury_card, card)); + col.markUndo(revertToProvidedState(R.string.menu_bury_card, mCard)); // then bury - col.getSched().buryCards(new long[] {card.getId()}); + col.getSched().buryCards(new long[] {mCard.getId()}); } } @@ -498,11 +519,11 @@ public BuryNote(Card card) { } @Override - protected void actualTask(Collection col, Card card) { + protected void actualTask(Collection col) { // collect undo information - col.markUndo(revertToProvidedState(R.string.menu_bury_note, card)); + col.markUndo(revertToProvidedState(R.string.menu_bury_note, mCard)); // then bury - col.getSched().buryNote(card.note().getId()); + col.getSched().buryNote(mCard.note().getId()); } } @@ -512,15 +533,15 @@ public SuspendCard(Card card) { } @Override - protected void actualTask(Collection col, Card card) { + protected void actualTask(Collection col) { // collect undo information - Card suspendedCard = card.clone(); + Card suspendedCard = mCard.clone(); col.markUndo(new UndoSuspendCard(suspendedCard)); // suspend card - if (card.getQueue() == Consts.QUEUE_TYPE_SUSPENDED) { - col.getSched().unsuspendCards(new long[] {card.getId()}); + if (mCard.getQueue() == Consts.QUEUE_TYPE_SUSPENDED) { + col.getSched().unsuspendCards(new long[] {mCard.getId()}); } else { - col.getSched().suspendCards(new long[] {card.getId()}); + col.getSched().suspendCards(new long[] {mCard.getId()}); } } } @@ -531,14 +552,14 @@ public SuspendNote(Card card) { } @Override - protected void actualTask(Collection col, Card card) { + protected void actualTask(Collection col) { // collect undo information - ArrayList cards = card.note().cards(); + ArrayList cards = mCard.note().cards(); long[] cids = new long[cards.size()]; for (int i = 0; i < cards.size(); i++) { cids[i] = cards.get(i).getId(); } - col.markUndo(revertToProvidedState(R.string.menu_suspend_note, card)); + col.markUndo(revertToProvidedState(R.string.menu_suspend_note, mCard)); // suspend note col.getSched().suspendCards(cids); } @@ -550,11 +571,11 @@ public DeleteNote(Card card) { } @Override - protected void actualTask(Collection col, Card card) { - Note note = card.note(); + protected void actualTask(Collection col) { + Note note = mCard.note(); // collect undo information ArrayList allCs = note.cards(); - col.markUndo(new UndoDeleteNote(note, allCs, card)); + col.markUndo(new UndoDeleteNote(note, allCs, mCard)); // delete note col.remNotes(new long[] {note.getId()}); } From 2c8861256927a04fe404e46ba048e0c5ffea7388 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 9 Apr 2021 16:28:46 +0200 Subject: [PATCH 123/171] NF: `DismissNotes`'s `actualTask` return whether it succeeded Indeed, it always returned one out of two values. Using false or true is nicer --- .../java/com/ichi2/async/CollectionTask.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java index aedd2b71e175..ebae5867744d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java +++ b/AnkiDroid/src/main/java/com/ichi2/async/CollectionTask.java @@ -740,6 +740,12 @@ public DismissNotes(List cardIds) { this.mCardIds = cardIds; } + + /** + * @param col + * @param collectionTask Represents the background tasks. + * @return whether the task succeeded, and the array of cards affected. + */ protected PairWithBoolean task(Collection col, ProgressSenderAndCancelListener collectionTask) { // query cards Card[] cards = new Card[mCardIds.size()]; @@ -750,9 +756,9 @@ protected PairWithBoolean task(Collection col, ProgressSenderAndCancelLi try { col.getDb().getDatabase().beginTransaction(); try { - PairWithBoolean ret = actualTask(col, collectionTask, cards); - if (ret != null) { - return ret; + boolean succeeded = actualTask(col, collectionTask, cards); + if (!succeeded) { + return new PairWithBoolean<>(false, null); } col.getDb().getDatabase().setTransactionSuccessful(); } finally { @@ -772,9 +778,9 @@ protected PairWithBoolean task(Collection col, ProgressSenderAndCancelLi * @param col The collection * @param collectionTask, where to send progress and listen for cancellation * @param cards Cards to which the task should be applied - * @return value to return, or null if `task` should deal with it directly. + * @return Whether the tasks succeeded. */ - protected abstract PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards); + protected abstract boolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards); } public static class SuspendCardMulti extends DismissNotes { @@ -782,7 +788,7 @@ public SuspendCardMulti(List cardIds) { super(cardIds); } - protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { + protected boolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { AbstractSched sched = col.getSched(); // collect undo information long[] cids = new long[cards.length]; @@ -816,7 +822,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa } sched.deferReset(); - return null; + return true; } } @@ -828,12 +834,12 @@ public Flag(List cardIds, int flag) { mFlag = flag; } - protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { + protected boolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { col.setUserFlag(mFlag, mCardIds); for (Card c : cards) { c.load(); } - return null; + return true; } } @@ -842,7 +848,7 @@ public MarkNoteMulti(List cardIds) { super(cardIds); } - protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { + protected boolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { Set notes = CardUtils.getNotes(Arrays.asList(cards)); // collect undo information List originalMarked = new ArrayList<>(); @@ -865,7 +871,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa for (Card c : cards) { c.load(); } - return null; + return true; } } @@ -875,7 +881,7 @@ public DeleteNoteMulti(List cardIds) { super(cardIds); } - protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { + protected boolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { AbstractSched sched = col.getSched(); // list of all ids to pass to remNotes method. // Need Set (-> unique) so we don't pass duplicates to col.remNotes() @@ -898,7 +904,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa sched.deferReset(); // pass back all cards because they can't be retrieved anymore by the caller (since the note is deleted) collectionTask.doProgress(allCards.toArray(new Card[allCards.size()])); - return null; + return true; } } @@ -909,14 +915,14 @@ public ChangeDeckMulti(List cardIds, long newDid) { mNewDid = newDid; } - protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { + protected boolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { Timber.i("Changing %d cards to deck: '%d'", cards.length, mNewDid); Deck deckData = col.getDecks().get(mNewDid); if (Decks.isDynamic(deckData)) { //#5932 - can't change to a dynamic deck. Use "Rebuild" Timber.w("Attempted to move to dynamic deck. Cancelling task."); - return new PairWithBoolean<>(false, null); + return false; } //Confirm that the deck exists (and is not the default) @@ -924,11 +930,11 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa long actualId = deckData.getLong("id"); if (actualId != mNewDid) { Timber.w("Attempted to move to deck %d, but got %d", mNewDid, actualId); - return new PairWithBoolean<>(false, null); + return false; } } catch (Exception e) { Timber.e(e, "failed to check deck"); - return new PairWithBoolean<>(false, null); + return false; } long[] changedCardIds = new long[cards.length]; @@ -955,7 +961,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa UndoAction changeDeckMulti = new UndoChangeDeckMulti(cards, originalDids); // mark undo for all at once col.markUndo(changeDeckMulti); - return null; + return true; } } @@ -966,7 +972,7 @@ public RescheduleRepositionReset(List cardIds, @StringRes int undoNameId) mUndoNameId = undoNameId; } - protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { + protected boolean actualTask(Collection col, ProgressSenderAndCancelListener collectionTask, Card[] cards) { AbstractSched sched = col.getSched(); // collect undo information, sensitive to memory pressure, same for all 3 cases try { @@ -981,7 +987,7 @@ protected PairWithBoolean actualTask(Collection col, ProgressSenderAndCa // In all cases schedule a new card so Reviewer doesn't sit on the old one col.reset(); collectionTask.doProgress(sched.getCard()); - return null; + return true; } protected abstract void actualActualTask(AbstractSched sched); From 11f4babb0bf6da09f2f7920558bfda30e9bfa326 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 10 Apr 2021 11:56:51 +0200 Subject: [PATCH 124/171] NF: Improve DeckPreviewStatistics type --- .../com/ichi2/anki/stats/AnkiStatsTaskHandler.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java b/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java index 00b872f86c85..b0880883502f 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java @@ -18,6 +18,7 @@ import android.content.res.Resources; import android.database.Cursor; import android.os.AsyncTask; +import android.util.Pair; import android.view.View; import android.webkit.WebView; import android.widget.ProgressBar; @@ -74,7 +75,7 @@ public CreateStatisticsOverview createStatisticsOverview(View... views){ } public static DeckPreviewStatistics createReviewSummaryStatistics(Collection col, TextView view){ DeckPreviewStatistics deckPreviewStatistics = new DeckPreviewStatistics(); - deckPreviewStatistics.execute(col, view); + deckPreviewStatistics.execute(new Pair<>(col, view)); return deckPreviewStatistics; } @@ -183,7 +184,7 @@ protected void onPostExecute(String html) { } } - private static class DeckPreviewStatistics extends AsyncTask { + private static class DeckPreviewStatistics extends AsyncTask, Void, String> { private TextView mTextView; private boolean mIsRunning = false; @@ -194,19 +195,19 @@ public DeckPreviewStatistics() { } @Override - protected String doInBackground(Object... params) { + protected String doInBackground(Pair... params) { //make sure only one task of CreateChartTask is running, first to run should get sLock //only necessary on lower APIs because after honeycomb only one thread is used for all asynctasks sLock.lock(); try { - Collection collection = (Collection) params[0]; + Collection collection = params[0].first; if (!mIsRunning || collection == null || collection.getDb() == null) { Timber.d("Quitting DeckPreviewStatistics before execution"); return null; } else { Timber.d("Starting DeckPreviewStatistics"); } - mTextView = (TextView) params[1]; + mTextView = params[0].second; //eventually put this in Stats (in desktop it is not though) int cards; From 59cc652c877a604c397037c0c66da263a4457d97 Mon Sep 17 00:00:00 2001 From: Kartikey Saran <55613721+kartikeysaran@users.noreply.github.com> Date: Wed, 14 Apr 2021 17:14:54 +0530 Subject: [PATCH 125/171] Fix #8293 Forward porting Fragment startActivityForResult/onActvityResult to new Activity APIs (#8356) * Updated CardTemplateEditor.java * Created Custom Contracts * Using Lambdas * Using Activity Results Contracts * Removed Deprecation marker Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> Co-authored-by: Mike Hardy --- .../com/ichi2/anki/CardTemplateEditor.java | 38 ++++---- .../com/ichi2/anki/StudyOptionsFragment.java | 94 +++++++++---------- 2 files changed, 63 insertions(+), 69 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java index dc7ffa42b793..e4572e960f5c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplateEditor.java @@ -18,11 +18,16 @@ package com.ichi2.anki; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.Bundle; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContract; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -520,7 +525,6 @@ public boolean onOptionsItemSelected(MenuItem item) { } - @SuppressWarnings("deprecation") // Tracked as https://github.com/ankidroid/Anki-Android/issues/8293 private void performPreview() { Collection col = mTemplateEditor.getCol(); TemporaryModel tempModel = mTemplateEditor.getTempModel(); @@ -541,7 +545,7 @@ private void performPreview() { // Save the model and pass the filename if updated tempModel.setEditedModelFileName(TemporaryModel.saveTempModel(mTemplateEditor, tempModel.getModel())); i.putExtra(TemporaryModel.INTENT_MODEL_FILENAME, tempModel.getEditedModelFileName()); - startActivityForResult(i, REQUEST_PREVIEWER); + onRequestPreviewResult.launch(i); } @@ -575,7 +579,6 @@ private String getCurrentTemplateName(TemporaryModel tempModel) { } - @SuppressWarnings("deprecation") // Tracked as https://github.com/ankidroid/Anki-Android/issues/8293 private void launchCardBrowserAppearance(JSONObject currentTemplate) { Context context = AnkiDroidApp.getInstance().getBaseContext(); if (context == null) { @@ -584,7 +587,7 @@ private void launchCardBrowserAppearance(JSONObject currentTemplate) { return; } Intent browserAppearanceIntent = CardTemplateBrowserAppearanceEditor.getIntentFromTemplate(context, currentTemplate); - startActivityForResult(browserAppearanceIntent, REQUEST_CARD_BROWSER_APPEARANCE); + onCardBrowserAppearanceActivtyResult.launch(browserAppearanceIntent); } @@ -627,31 +630,24 @@ private boolean deletionWouldOrphanNote(Collection col, TemporaryModel tempModel return false; } - - @SuppressWarnings("deprecation") // Tracked as https://github.com/ankidroid/Anki-Android/issues/8293 - @Override - public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CARD_BROWSER_APPEARANCE) { - onCardBrowserAppearanceResult(resultCode, data); + ActivityResultLauncher onCardBrowserAppearanceActivtyResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() != RESULT_OK) { return; } + onCardBrowserAppearanceResult(result.getData()); + }); - if (requestCode == REQUEST_PREVIEWER) { + ActivityResultLauncher onRequestPreviewResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() != RESULT_OK) { + return; + } TemporaryModel.clearTempModelFiles(); // Make sure the fragments reinitialize, otherwise there is staleness on return ((TemplatePagerAdapter)mTemplateEditor.mViewPager.getAdapter()).ordinalShift(); mTemplateEditor.mViewPager.getAdapter().notifyDataSetChanged(); - } - } - - - private void onCardBrowserAppearanceResult(int resultCode, @Nullable Intent data) { - if (resultCode != RESULT_OK) { - Timber.i("Activity Cancelled: Card Template Browser Appearance"); - return; - } + }); + private void onCardBrowserAppearanceResult(@Nullable Intent data) { CardTemplateBrowserAppearanceEditor.Result result = CardTemplateBrowserAppearanceEditor.Result.fromIntent(data); if (result == null) { Timber.w("Error processing Card Template Browser Appearance result"); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java index e14c65918ecd..6cbc9575728d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/StudyOptionsFragment.java @@ -21,6 +21,8 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -159,11 +161,10 @@ private void openFilteredDeckOptions() { * deck has no options associated with it yet and should use a default * set of values. */ - @SuppressWarnings("deprecation") // Tracked as https://github.com/ankidroid/Anki-Android/issues/8293 private void openFilteredDeckOptions(boolean defaultConfig) { Intent i = new Intent(getActivity(), FilteredDeckOptions.class); i.putExtra("defaultConfig", defaultConfig); - getActivity().startActivityForResult(i, DECK_OPTIONS); + onDeckOptionsActivityResult.launch(i); ActivityTransitionAnimation.slide(getActivity(), FADE); } @@ -243,12 +244,11 @@ private void closeStudyOptions(int result) { } - @SuppressWarnings("deprecation") // Tracked as https://github.com/ankidroid/Anki-Android/issues/8293 private void openReviewer() { Intent reviewer = new Intent(getActivity(), Reviewer.class); if (mFragmented) { mToReviewer = true; - getActivity().startActivityForResult(reviewer, AnkiActivity.REQUEST_REVIEW); + onRequestPreviewActivityResult.launch(reviewer); } else { // Go to DeckPicker after studying when not tablet reviewer.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); @@ -316,7 +316,6 @@ public void onPostExecute(BooleanGetter v) { } }; - @SuppressWarnings("deprecation") // Tracked as https://github.com/ankidroid/Anki-Android/issues/8293 @Override public boolean onMenuItemClick(MenuItem item) { int itemId = item.getItemId(); @@ -330,7 +329,7 @@ public boolean onMenuItemClick(MenuItem item) { openFilteredDeckOptions(); } else { Intent i = new Intent(getActivity(), DeckOptions.class); - getActivity().startActivityForResult(i, DECK_OPTIONS); + onDeckOptionsActivityResult.launch(i); ActivityTransitionAnimation.slide(getActivity(), FADE); } return true; @@ -440,57 +439,56 @@ private void configureToolbarInternal(boolean recur) { } } - - @SuppressWarnings("deprecation") // Tracked as https://github.com/ankidroid/Anki-Android/issues/8293 - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - Timber.d("onActivityResult (requestCode = %d, resultCode = %d)", requestCode, resultCode); - - // rebuild action bar + ActivityResultLauncher onRequestPreviewActivityResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { configureToolbar(); - - // boot back to deck picker if there was an error - if (resultCode == DeckPicker.RESULT_DB_ERROR || resultCode == DeckPicker.RESULT_MEDIA_EJECTED) { - closeStudyOptions(resultCode); + if ((result.getResultCode() == DeckPicker.RESULT_DB_ERROR) || (result.getResultCode() == DeckPicker.RESULT_MEDIA_EJECTED)) { + closeStudyOptions(result.getResultCode()); return; } - - // perform some special actions depending on which activity we're returning from - if (requestCode == STATISTICS || requestCode == BROWSE_CARDS) { - // select original deck if the statistics or card browser were opened, - // which can change the selected deck - if (intent.hasExtra("originalDeck")) { - getCol().getDecks().select(intent.getLongExtra("originalDeck", 0L)); + if (result.getResultCode() == Reviewer.RESULT_NO_MORE_CARDS) { + // If no more cards getting returned while counts > 0 (due to learn ahead limit) then show a snackbar + if ((getCol().getSched().count() > 0) && (mStudyOptionsView != null)) { + View rootLayout = mStudyOptionsView.findViewById(R.id.studyoptions_main); + UIUtils.showSnackbar(getActivity(), R.string.studyoptions_no_cards_due, false, 0, null, rootLayout); } } - if (requestCode == DECK_OPTIONS) { - if (mLoadWithDeckOptions) { - mLoadWithDeckOptions = false; - Deck deck = getCol().getDecks().current(); - if (deck.isDyn() && deck.has("empty")) { - deck.remove("empty"); - } - mProgressDialog = StyledProgressDialog.show(getActivity(), null, - getResources().getString(R.string.rebuild_filtered_deck), true); - TaskManager.launchCollectionTask(new CollectionTask.RebuildCram(), getCollectionTaskListener(true)); - } else { - TaskManager.waitToFinish(); - refreshInterface(true); - } - } else if (requestCode == AnkiActivity.REQUEST_REVIEW) { - if (resultCode == Reviewer.RESULT_NO_MORE_CARDS) { - // If no more cards getting returned while counts > 0 (due to learn ahead limit) then show a snackbar - if (getCol().getSched().count() > 0 && mStudyOptionsView != null) { - View rootLayout = mStudyOptionsView.findViewById(R.id.studyoptions_main); - UIUtils.showSnackbar(getActivity(), R.string.studyoptions_no_cards_due, false, 0, null, rootLayout); - } + }); + + ActivityResultLauncher onDeckOptionsActivityResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + configureToolbar(); + if ((result.getResultCode() == DeckPicker.RESULT_DB_ERROR) || (result.getResultCode() == DeckPicker.RESULT_MEDIA_EJECTED)) { + closeStudyOptions(result.getResultCode()); + return; + } + if (mLoadWithDeckOptions) { + mLoadWithDeckOptions = false; + Deck deck = getCol().getDecks().current(); + if (deck.isDyn() && deck.has("empty")) { + deck.remove("empty"); } - } else if (requestCode == STATISTICS && mCurrentContentView == CONTENT_CONGRATS) { + mProgressDialog = StyledProgressDialog.show(getActivity(), null, + getResources().getString(R.string.rebuild_filtered_deck), true); + TaskManager.launchCollectionTask(new CollectionTask.RebuildCram(), getCollectionTaskListener(true)); + } else { + TaskManager.waitToFinish(); + refreshInterface(true); + } + }); + + ActivityResultLauncher onStatisticsActivityResult = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + configureToolbar(); + if ((result.getResultCode() == DeckPicker.RESULT_DB_ERROR) || (result.getResultCode() == DeckPicker.RESULT_MEDIA_EJECTED)) { + closeStudyOptions(result.getResultCode()); + return; + } + if (result.getData().hasExtra("originalDeck")) { + getCol().getDecks().select(result.getData().getLongExtra("originalDeck", 0L)); + } + if (mCurrentContentView == CONTENT_CONGRATS) { mCurrentContentView = CONTENT_STUDY_OPTIONS; setFragmentContentView(mStudyOptionsView); } - } + }); private void dismissProgressDialog() { if (mStudyOptionsView != null && mStudyOptionsView.findViewById(R.id.progress_bar) != null) { From fdeeef0dbe68c4d4b164cf4df4998373afda2b28 Mon Sep 17 00:00:00 2001 From: mrudultora Date: Sun, 11 Apr 2021 00:17:21 +0530 Subject: [PATCH 126/171] Added a test to check all the constants in actions class are annotated --- .../anki/analytics/AnalyticsConstantsTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java index 42aaab678d34..44b595b73f79 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java @@ -140,5 +140,22 @@ public static long getFieldSize() { .filter(x -> x.isAnnotationPresent(AnalyticsConstant.class)) .count(); } + + + /** + * This test is used to check whether all the string constants of Actions are annotated with @AnalyticsConstant. + * If not, then a runtime exception is thrown. + */ + @Test + public void fieldAnnotatedOrNot(){ + UsageAnalytics.Actions actions = new UsageAnalytics.Actions(); + Field[] field; + field = actions.getClass().getDeclaredFields(); + for (Field value : field) { + if (!value.isAnnotationPresent(AnalyticsConstant.class) && !value.isSynthetic()) { + throw new RuntimeException("All the fields in Actions class must be annotated with @AnalyticsConstant. It seems " + value.getName() + " is not annotated."); + } + } + } } } From 3451cc58d65bea7a55b6826a1d94375fd13ff470 Mon Sep 17 00:00:00 2001 From: Farjad Ilyas Date: Sat, 10 Apr 2021 23:28:01 +0500 Subject: [PATCH 127/171] Init collection after storage permission grant - handleStartup() sets up collection immediately if permission granted - handleStartup() requests for storage permission if it's not granted - handleStartup() called in onCreate() & after permission granted - firstOpenCollection() not responsible for error handling anymore --- .../main/java/com/ichi2/anki/DeckPicker.java | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 4ed3d2e08132..776c2bfa0eae 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -449,15 +449,12 @@ protected void onCreate(Bundle savedInstanceState) throws SQLException { if (showedActivityFailedScreen(savedInstanceState)) { return; } - + mCustomStudyDialogFactory = new CustomStudyDialogFactory(this::getCol, this).attachToActivity(this); - SharedPreferences preferences = AnkiDroidApp.getSharedPrefs(getBaseContext()); - //we need to restore here, as we need it before super.onCreate() is called. restoreWelcomeMessage(savedInstanceState); - // Open Collection on UI thread while splash screen is showing - boolean colOpen = firstCollectionOpen(); + handleStartup(); // Then set theme and content view super.onCreate(savedInstanceState); @@ -529,28 +526,27 @@ protected void onCreate(Bundle savedInstanceState) throws SQLException { mReviewSummaryTextView = findViewById(R.id.today_stats_text_view); - Timber.i("colOpen: %b", colOpen); - // if permission is denied, firstCollectionOpen() requests it and onRequestPermissionsResult continues execution - if (Permissions.hasStorageAccessPermission(this)) { - handleStartup(colOpen); - } - - mShortAnimDuration = getResources().getInteger(android.R.integer.config_shortAnimTime); } - /** The first call in showing dialogs for startup - error or success */ - private void handleStartup(boolean colOpen) { - // TODO: colOpen is not colIsOpen() if called from onCreate - we should fix this mismatch of terms - // or use the same variable if the semantics should have been equivalent - if (colOpen) { - // Show any necessary dialogs (e.g. changelog, special messages, etc) - SharedPreferences sharedPrefs = AnkiDroidApp.getSharedPrefs(this); - showStartupScreensAndDialogs(sharedPrefs, 0); + /** + * The first call in showing dialogs for startup - error or success. + * Attempts startup if storage permission has been acquired, else, it requests the permission + * */ + private void handleStartup() { + if (Permissions.hasStorageAccessPermission(this)) { + boolean colOpen = firstCollectionOpen(); + if (colOpen) { + // Show any necessary dialogs (e.g. changelog, special messages, etc) + SharedPreferences sharedPrefs = AnkiDroidApp.getSharedPrefs(this); + showStartupScreensAndDialogs(sharedPrefs, 0); + } else { + // Show error dialogs + StartupFailure failure = InitialActivity.getStartupFailureType(this); + handleStartupFailure(failure); + } } else { - // Show error dialogs - StartupFailure failure = InitialActivity.getStartupFailureType(this); - handleStartupFailure(failure); + requestStoragePermission(); } } @@ -607,7 +603,7 @@ private boolean applyDeckPickerBackground(View view) throws OutOfMemoryError { } /** - * Try to open the Collection for the first time, and do some error handling if it wasn't successful + * Try to open the Collection for the first time * @return whether or not we were successful */ private boolean firstCollectionOpen() { @@ -621,16 +617,16 @@ private boolean firstCollectionOpen() { .show(); return false; } - if (Permissions.hasStorageAccessPermission(this)) { - Timber.i("User has permissions to access collection"); - // Show error dialog if collection could not be opened - return CollectionHelper.getInstance().getColSafe(this) != null; - } else if (mClosedWelcomeMessage) { + + return CollectionHelper.getInstance().getColSafe(this) != null; + } + + public void requestStoragePermission() { + if (mClosedWelcomeMessage) { // DEFECT #5847: This fails if the activity is killed. //Even if the dialog is showing, we want to show it again. ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION); - return false; } else { Timber.i("Displaying initial permission request dialog"); // Request storage permission if we don't have it (e.g. on Android 6.0+) @@ -647,7 +643,6 @@ private boolean firstCollectionOpen() { .cancelable(false) .canceledOnTouchOutside(false) .show(); - return false; } } @@ -942,7 +937,7 @@ public void onRequestPermissionsResult (int requestCode, @NonNull String[] permi if (requestCode == REQUEST_STORAGE_PERMISSION && permissions.length == 1) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { invalidateOptionsMenu(); - handleStartup(colIsOpen()); + handleStartup(); } else { // User denied access to file storage so show error toast and display "App Info" Toast.makeText(this, R.string.startup_no_storage_permission, Toast.LENGTH_LONG).show(); From 7a91f9e050060b23aff58b7e37b7b3008f1e200b Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sun, 11 Apr 2021 09:07:13 +0200 Subject: [PATCH 128/171] NF: Card and Note constructors are not-python like In ankitects/anki@7ddaf93 the code was moved to rust, where it uses two separate functions. Really no reason to keep python-like code --- .../src/main/java/com/ichi2/libanki/Card.java | 50 +++++++++---------- .../src/main/java/com/ichi2/libanki/Note.java | 42 +++++++--------- 2 files changed, 42 insertions(+), 50 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Card.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Card.java index 55c3d9c66869..728a04302306 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Card.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Card.java @@ -120,35 +120,35 @@ public class Card implements Cloneable { private int mLastIvl; - public Card(Collection col) { - this(col, null); - } - - - public Card(Collection col, Long id) { + public Card(@NonNull Collection col) { mCol = col; mTimerStarted = 0L; mQA = null; mNote = null; - if (id != null) { - mId = id; - load(); - } else { - // to flush, set nid, ord, and due - mId = mCol.getTime().timestampID(mCol.getDb(), "cards"); - mDid = 1; - mType = Consts.CARD_TYPE_NEW; - mQueue = Consts.QUEUE_TYPE_NEW; - mIvl = 0; - mFactor = 0; - mReps = 0; - mLapses = 0; - mLeft = 0; - mODue = 0; - mODid = 0; - mFlags = 0; - mData = ""; - } + // to flush, set nid, ord, and due + mId = mCol.getTime().timestampID(mCol.getDb(), "cards"); + mDid = 1; + mType = Consts.CARD_TYPE_NEW; + mQueue = Consts.QUEUE_TYPE_NEW; + mIvl = 0; + mFactor = 0; + mReps = 0; + mLapses = 0; + mLeft = 0; + mODue = 0; + mODid = 0; + mFlags = 0; + mData = ""; + } + + + public Card(@NonNull Collection col, @NonNull Long id) { + mCol = col; + mTimerStarted = 0L; + mQA = null; + mNote = null; + mId = id; + load(); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Note.java b/AnkiDroid/src/main/java/com/ichi2/libanki/Note.java index bf87abdb0749..7b93b7f3073d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Note.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Note.java @@ -32,6 +32,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import timber.log.Timber; @@ -56,35 +57,26 @@ public class Note implements Cloneable { private boolean mNewlyAdded; - public Note(Collection col, Long id) { - this(col, null, id); - } - - - public Note(Collection col, Model model) { - this(col, model, null); + public Note(@NonNull Collection col, @NonNull Long id) { + mCol = col; + mId = id; + load(); } - public Note(Collection col, Model model, Long id) { - assert !(model != null && id != null); + public Note(@NonNull Collection col, @NonNull Model model) { mCol = col; - if (id != null) { - mId = id; - load(); - } else { - mId = mCol.getTime().timestampID(mCol.getDb(), "notes"); - mGuId = Utils.guid64(); - mModel = model; - mMid = model.getLong("id"); - mTags = new ArrayList<>(); - mFields = new String[model.getJSONArray("flds").length()]; - Arrays.fill(mFields, ""); - mFlags = 0; - mData = ""; - mFMap = Models.fieldMap(mModel); - mScm = mCol.getScm(); - } + mId = mCol.getTime().timestampID(mCol.getDb(), "notes"); + mGuId = Utils.guid64(); + mModel = model; + mMid = model.getLong("id"); + mTags = new ArrayList<>(); + mFields = new String[model.getJSONArray("flds").length()]; + Arrays.fill(mFields, ""); + mFlags = 0; + mData = ""; + mFMap = Models.fieldMap(mModel); + mScm = mCol.getScm(); } From 31f05d46b44602c4b00b0cef9ae76b3fcaf8ece6 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 14 Apr 2021 18:05:57 +0530 Subject: [PATCH 129/171] Add unit tests for NamedJSONComparator class (#8557) * Add unit tests for NamedJSONComparator --- .../ichi2/utils/NamedJSONComparatorTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/utils/NamedJSONComparatorTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/NamedJSONComparatorTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/NamedJSONComparatorTest.java new file mode 100644 index 000000000000..349c4993c31b --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/utils/NamedJSONComparatorTest.java @@ -0,0 +1,53 @@ +/**************************************************************************************** + * * + * 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 . * + ****************************************************************************************/ + +package com.ichi2.utils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.lessThan; + +@RunWith(AndroidJUnit4.class) +public class NamedJSONComparatorTest { + + @Test + public void checkIfReturnsCorrectValueForSameNames() { + JSONObject firstObject = new JSONObject(); + firstObject.put("name", "TestName"); + + JSONObject secondObject = new JSONObject(); + secondObject.put("name", "TestName"); + + assertThat(NamedJSONComparator.INSTANCE.compare(firstObject, secondObject), equalTo(0)); + } + + @Test + public void checkIfReturnsCorrectValueForDifferentNames() { + JSONObject firstObject = new JSONObject(); + firstObject.put("name", "TestName1"); + + JSONObject secondObject = new JSONObject(); + secondObject.put("name", "TestName2"); + + assertThat(NamedJSONComparator.INSTANCE.compare(firstObject, secondObject), lessThan(0)); + } +} From 6a7b73c25a4689ae96227bae919fdae123057824 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sun, 11 Apr 2021 10:23:31 +0200 Subject: [PATCH 130/171] NF: PreviewerCard's note is final --- .../com/ichi2/anki/CardTemplatePreviewer.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java index 4930066bdd59..936af4f620f9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardTemplatePreviewer.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.List; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import timber.log.Timber; @@ -280,9 +281,7 @@ protected Card getCard(Collection col, long cardListIndex) { } try { JSONObject template = model.getJSONArray("tmpls").getJSONObject(ordinal); - PreviewerCard card = (PreviewerCard)getCol().getNewLinkedCard(new PreviewerCard(getCol()), n, template, 1, 0L, false); - card.setNote(n); - return card; + return getCol().getNewLinkedCard(new PreviewerCard(getCol(), n), n, template, 1, 0L, false); } catch (Exception e) { Timber.e("getDummyCard() unable to create card"); } @@ -293,16 +292,18 @@ protected Card getCard(Collection col, long cardListIndex) { /** Override certain aspects of Card behavior so we may display unsaved data */ public class PreviewerCard extends Card { - private Note mNote; + @Nullable private final Note mNote; - private PreviewerCard(Collection col) { + private PreviewerCard(Collection col, @NonNull Note note) { super(col); + mNote = note; } private PreviewerCard(Collection col, long id) { super(col, id); + mNote = null; } @@ -326,12 +327,6 @@ public Note note() { } - /** set an unsaved note to use for rendering */ - public void setNote(Note note) { - mNote = note; - } - - /** if we have an unsaved note, never return empty */ @Override public boolean isEmpty() { From 4809c77a444ef2c198a01e7cded26555f0600623 Mon Sep 17 00:00:00 2001 From: Farjad Ilyas Date: Fri, 9 Apr 2021 18:19:09 +0500 Subject: [PATCH 131/171] Fix AnkiStatsTaskHandler's AsyncTasks memory leak - Replaces the strong references to view objects in AsyncTasks with WeakReferences - Turns the extended AsyncTasks into static inner classes - Checks if a weakly referenced object isn't null before using. Attempts to update UI only if view objects exist. --- .../main/java/com/ichi2/anki/Statistics.java | 2 +- .../anki/stats/AnkiStatsTaskHandler.java | 115 ++++++++++++------ 2 files changed, 77 insertions(+), 40 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java b/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java index c0f271ced063..3e8d74abc6c1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Statistics.java @@ -130,7 +130,7 @@ public void onNothingSelected(AdapterView parent) { mActionBarSpinner.setVisibility(View.VISIBLE); // Setup Task Handler - mTaskHandler = new AnkiStatsTaskHandler(col); + mTaskHandler = AnkiStatsTaskHandler.getInstance(col); // Dirty way to get text size from a TextView with current style, change if possible float size = new FixedTextView(this).getTextSize(); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java b/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java index b0880883502f..bcfd4bc6fcd4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/stats/AnkiStatsTaskHandler.java @@ -31,6 +31,7 @@ import com.wildplot.android.rendering.PlotSheet; import java.io.UnsupportedEncodingException; +import java.lang.ref.WeakReference; import java.net.URLEncoder; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -50,8 +51,7 @@ public class AnkiStatsTaskHandler { private static final Lock sLock = new ReentrantLock(); - public AnkiStatsTaskHandler(Collection collection){ - sInstance = this; + private AnkiStatsTaskHandler(Collection collection){ mCollectionData = collection; } @@ -63,13 +63,20 @@ public static AnkiStatsTaskHandler getInstance() { return sInstance; } + public synchronized static AnkiStatsTaskHandler getInstance(Collection collection) { + if (sInstance == null) { + sInstance = new AnkiStatsTaskHandler(collection); + } + return sInstance; + } + public CreateChartTask createChart(Stats.ChartType chartType, View... views){ - CreateChartTask createChartTask = new CreateChartTask(chartType); + CreateChartTask createChartTask = new CreateChartTask(chartType, mCollectionData, mStatType, mDeckId); createChartTask.execute(views); return createChartTask; } public CreateStatisticsOverview createStatisticsOverview(View... views){ - CreateStatisticsOverview createChartTask = new CreateStatisticsOverview(); + CreateStatisticsOverview createChartTask = new CreateStatisticsOverview(mCollectionData, mStatType, mDeckId); createChartTask.execute(views); return createChartTask; } @@ -79,17 +86,23 @@ public static DeckPreviewStatistics createReviewSummaryStatistics(Collection col return deckPreviewStatistics; } - private class CreateChartTask extends AsyncTask{ - private ChartView mImageView; - private ProgressBar mProgressBar; + private static class CreateChartTask extends AsyncTask{ + private WeakReference mImageView; + private WeakReference mProgressBar; + private final WeakReference mCollectionData; + private final Stats.AxisType mStatType; + private final long mDeckId; private boolean mIsRunning = false; private final Stats.ChartType mChartType; - public CreateChartTask(Stats.ChartType chartType){ + public CreateChartTask(Stats.ChartType chartType, Collection collection, Stats.AxisType statType, long deckId){ super(); mIsRunning = true; mChartType = chartType; + mCollectionData = new WeakReference<>(collection); + mStatType = statType; + mDeckId = deckId; } @Override @@ -97,16 +110,19 @@ protected PlotSheet doInBackground(View... params) { //make sure only one task of CreateChartTask is running, first to run should get sLock //only necessary on lower APIs because after honeycomb only one thread is used for all asynctasks sLock.lock(); + Collection collectionData = mCollectionData.get(); try { - if (!mIsRunning) { + if (!mIsRunning || (collectionData == null)) { Timber.d("Quitting CreateChartTask (%s) before execution", mChartType.name()); return null; } else { Timber.d("Starting CreateChartTask, type: %s", mChartType.name()); } - mImageView = (ChartView) params[0]; - mProgressBar = (ProgressBar) params[1]; - ChartBuilder chartBuilder = new ChartBuilder(mImageView, mCollectionData, + ChartView imageView = (ChartView) params[0]; + mImageView = new WeakReference<>(imageView); + mProgressBar = new WeakReference<>((ProgressBar) params[1]); + + ChartBuilder chartBuilder = new ChartBuilder(imageView, collectionData, mDeckId, mChartType); return chartBuilder.renderChart(mStatType); } finally { @@ -121,24 +137,33 @@ protected void onCancelled() { @Override protected void onPostExecute(PlotSheet plotSheet) { - if (plotSheet != null && mIsRunning) { - mImageView.setData(plotSheet); - mProgressBar.setVisibility(View.GONE); - mImageView.setVisibility(View.VISIBLE); - mImageView.invalidate(); + ChartView imageView = mImageView.get(); + ProgressBar progressBar = mProgressBar.get(); + + if ((plotSheet != null) && mIsRunning && (imageView != null) && (progressBar != null)) { + imageView.setData(plotSheet); + progressBar.setVisibility(View.GONE); + imageView.setVisibility(View.VISIBLE); + imageView.invalidate(); } } } - private class CreateStatisticsOverview extends AsyncTask{ - private WebView mWebView; - private ProgressBar mProgressBar; + private static class CreateStatisticsOverview extends AsyncTask{ + private WeakReference mWebView; + private WeakReference mProgressBar; + private final WeakReference mCollectionData; + private final Stats.AxisType mStatType; + private final long mDeckId; private boolean mIsRunning = false; - public CreateStatisticsOverview(){ + public CreateStatisticsOverview(Collection collection, Stats.AxisType statType, long deckId){ super(); mIsRunning = true; + mCollectionData = new WeakReference<>(collection); + mStatType = statType; + mDeckId = deckId; } @Override @@ -146,16 +171,20 @@ protected String doInBackground(View... params) { //make sure only one task of CreateChartTask is running, first to run should get sLock //only necessary on lower APIs because after honeycomb only one thread is used for all asynctasks sLock.lock(); + Collection collectionData = mCollectionData.get(); try { - if (!mIsRunning) { + if (!mIsRunning || (collectionData == null)) { Timber.d("Quitting CreateStatisticsOverview before execution"); return null; } else { Timber.d("Starting CreateStatisticsOverview"); } - mWebView = (WebView) params[0]; - mProgressBar = (ProgressBar) params[1]; - OverviewStatsBuilder overviewStatsBuilder = new OverviewStatsBuilder(mWebView, mCollectionData, mDeckId, mStatType); + + WebView webView = (WebView) params[0]; + mWebView = new WeakReference<>(webView); + mProgressBar = new WeakReference<>((ProgressBar) params[1]); + + OverviewStatsBuilder overviewStatsBuilder = new OverviewStatsBuilder(webView, collectionData, mDeckId, mStatType); return overviewStatsBuilder.createInfoHtmlString(); } finally { sLock.unlock(); @@ -169,23 +198,27 @@ protected void onCancelled() { @Override protected void onPostExecute(String html) { - if (html != null && mIsRunning) { + WebView webView = mWebView.get(); + ProgressBar progressBar = mProgressBar.get(); + + if ((html != null) && mIsRunning && (webView != null) && (progressBar != null)) { try { - mWebView.loadData(URLEncoder.encode(html, "UTF-8").replaceAll("\\+", " "), "text/html; charset=utf-8", "utf-8"); + webView.loadData(URLEncoder.encode(html, "UTF-8").replaceAll("\\+", " "), "text/html; charset=utf-8", "utf-8"); } catch (UnsupportedEncodingException e) { Timber.w(e); } - mProgressBar.setVisibility(View.GONE); - int backgroundColor = Themes.getColorFromAttr(mWebView.getContext(), android.R.attr.colorBackground); - mWebView.setBackgroundColor(backgroundColor); - mWebView.setVisibility(View.VISIBLE); - mWebView.invalidate(); + progressBar.setVisibility(View.GONE); + int backgroundColor = Themes.getColorFromAttr(webView.getContext(), android.R.attr.colorBackground); + webView.setBackgroundColor(backgroundColor); + webView.setVisibility(View.VISIBLE); + webView.invalidate(); } } } + private static class DeckPreviewStatistics extends AsyncTask, Void, String> { - private TextView mTextView; + private WeakReference mTextView; private boolean mIsRunning = false; @@ -207,7 +240,9 @@ protected String doInBackground(Pair... params) { } else { Timber.d("Starting DeckPreviewStatistics"); } - mTextView = params[0].second; + + TextView textView = params[0].second; + mTextView = new WeakReference<>(textView); //eventually put this in Stats (in desktop it is not though) int cards; @@ -222,7 +257,7 @@ protected String doInBackground(Pair... params) { cards = cur.getInt(0); minutes = (int) Math.round(cur.getInt(1) / 60.0); } - Resources res = mTextView.getResources(); + Resources res = textView.getResources(); final String span = res.getQuantityString(R.plurals.in_minutes, minutes, minutes); return res.getQuantityString(R.plurals.studied_cards_today, cards, cards, span); } finally { @@ -237,10 +272,12 @@ protected void onCancelled() { @Override protected void onPostExecute(String todayStatString) { - if (todayStatString != null && mIsRunning) { - mTextView.setText(todayStatString); - mTextView.setVisibility(View.VISIBLE); - mTextView.invalidate(); + TextView textView = mTextView.get(); + + if ((todayStatString != null) && mIsRunning && (textView != null)) { + textView.setText(todayStatString); + textView.setVisibility(View.VISIBLE); + textView.invalidate(); } } } From 246fba3811d60dd3f302e50210393da3c8a39016 Mon Sep 17 00:00:00 2001 From: tapish2000 <44476667+tapish2000@users.noreply.github.com> Date: Fri, 9 Apr 2021 00:04:38 +0530 Subject: [PATCH 132/171] Display Order of cards in Card Browser corrected --- AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java index 8e3e7eda805e..c08f5e60cd18 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java @@ -267,6 +267,8 @@ protected void changeCardOrder(int which) { mCards.reverse(); updateList(); } + // To update the collection + getCol().flush(); } From 9c2e7788262006017c7939fa514072e915efeded Mon Sep 17 00:00:00 2001 From: tapish2000 <44476667+tapish2000@users.noreply.github.com> Date: Sun, 11 Apr 2021 04:09:26 +0530 Subject: [PATCH 133/171] Unit Test to check display order is persistant on restart --- .../main/java/com/ichi2/anki/CardBrowser.java | 4 +-- .../java/com/ichi2/anki/CardBrowserTest.java | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java index c08f5e60cd18..209af183b855 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.java @@ -241,7 +241,7 @@ enum Column { return true; }; - + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) protected void changeCardOrder(int which) { if (which != mOrder) { mOrder = which; @@ -268,7 +268,7 @@ protected void changeCardOrder(int which) { updateList(); } // To update the collection - getCol().flush(); + getCol().getDb().setMod(true); } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java index f2ee5e2a4406..fc6eb7186459 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/CardBrowserTest.java @@ -5,6 +5,7 @@ import android.app.Application; import android.content.ComponentName; import android.content.Intent; +import android.os.Bundle; import android.view.MenuItem; import android.widget.AdapterView; import android.widget.ListView; @@ -13,6 +14,7 @@ import com.ichi2.libanki.Consts; import com.ichi2.libanki.Note; import com.ichi2.libanki.Deck; +import com.ichi2.libanki.utils.Time; import com.ichi2.testutils.AnkiAssert; import com.ichi2.testutils.IntentAssert; @@ -527,6 +529,39 @@ public void checkSearchString() { assertThat("Results should only be from the selected deck", cardBrowser.getCardCount(), is(1)); } + /** PR #8553 **/ + @Test + public void checkDisplayOrderPersistence() { + // Start the Card Browser with Basic Model + ensureCollectionLoadIsSynchronous(); + ActivityController cardBrowserController = Robolectric.buildActivity(CardBrowser.class, new Intent()) + .create().start().resume().visible(); + saveControllerForCleanup(cardBrowserController); + advanceRobolectricLooperWithSleep(); + + // Make sure card has default value in sortType field + assertThat("Initially Card Browser has order = noteFld", getCol().getConf().get("sortType"), is("noteFld")); + + // Store the current (before changing the database) Mod Time + long initialMod = getCol().getMod(); + + // Change the display order of the card browser + cardBrowserController.get().changeCardOrder(7); // order no. 7 corresponds to "cardEase" + + // Kill and restart the activity and ensure that display order is preserved + Bundle outBundle = new Bundle(); + cardBrowserController.saveInstanceState(outBundle); + cardBrowserController.pause().stop().destroy(); + cardBrowserController = Robolectric.buildActivity(CardBrowser.class).create(outBundle).start().resume().visible(); + saveControllerForCleanup(cardBrowserController); + + // Find the current (after database has been changed) Mod time + long finalMod = getCol().getMod(); + + assertThat("Card Browser has the new sortType field", getCol().getConf().get("sortType"), is("cardEase")); + Assert.assertNotEquals("Modification time must change", initialMod, finalMod); + } + protected void assertUndoDoesNotContain(CardBrowser browser, @StringRes int resId) { ShadowActivity shadowActivity = shadowOf(browser); MenuItem item = shadowActivity.getOptionsMenu().findItem(R.id.action_undo); From 39c9804a20b5c03df28a0e69ce926e35a9e3971a Mon Sep 17 00:00:00 2001 From: diego Date: Wed, 14 Apr 2021 15:11:09 +0200 Subject: [PATCH 134/171] test: parsing hours as JUnit parameterized test (#8590) * test: parsing hours as JUnit parameterized test Co-authored-by: David Allison <62114487+david-allison-1@users.noreply.github.com> --- .../ichi2/preferences/TimePreferenceTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 AnkiDroid/src/test/java/com/ichi2/preferences/TimePreferenceTest.java diff --git a/AnkiDroid/src/test/java/com/ichi2/preferences/TimePreferenceTest.java b/AnkiDroid/src/test/java/com/ichi2/preferences/TimePreferenceTest.java new file mode 100644 index 000000000000..0a648b0e75a2 --- /dev/null +++ b/AnkiDroid/src/test/java/com/ichi2/preferences/TimePreferenceTest.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2021 Diego Rodriguez + * + * + * 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.preferences; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +@RunWith(Parameterized.class) +public class TimePreferenceTest { + private final String parsableHour; + private final int expectedHour; + + + public TimePreferenceTest(String parsableHour, int expectedHour) { + this.parsableHour = parsableHour; + this.expectedHour = expectedHour; + } + + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + {"00:00", 0}, + {"01:00", 1}, + {"24:00", 24} + }); + } + + @Test + public void shouldParseHours() { + int actualHour = TimePreference.parseHours(this.parsableHour); + + assertEquals(expectedHour, actualHour); + } +} From add7f7216cfc9e1bd5e88c6605b5d8357dab2805 Mon Sep 17 00:00:00 2001 From: Nicola Dardanis Date: Tue, 13 Apr 2021 00:10:49 +0200 Subject: [PATCH 135/171] fix: Call super#onRequestPermissionResult in MultimediaEditFieldActivity --- .../multimediacard/activity/MultimediaEditFieldActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java index 86bba6047870..ed642e938079 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/MultimediaEditFieldActivity.java @@ -342,13 +342,14 @@ private void recreateEditingUIUsingCachedRequest() { recreateEditingUi(mCurrentChangeRequest); } - public void onRequestPermissionsResult (int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (mCurrentChangeRequest == null) { cancelActivityWithAssertionFailure("mCurrentChangeRequest should be set before requesting permissions"); return; } Timber.d("onRequestPermissionsResult. Code: %d", requestCode); + super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_AUDIO_PERMISSION && permissions.length == 1) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { From 9479f77421a33796e294466c8a29e4d8d330148c Mon Sep 17 00:00:00 2001 From: Tushar Bhatt Date: Wed, 14 Apr 2021 18:44:23 +0530 Subject: [PATCH 136/171] test: ensure deepClone() returns a new instance (#8595) --- .../src/test/java/com/ichi2/utils/JSONObjectTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java index beff462c2520..36c704557f00 100644 --- a/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/utils/JSONObjectTest.java @@ -152,6 +152,17 @@ public void deepCloneTest() { Assert.assertEquals(removeQuotes(correctJsonObjectNestedWithArray.toString()), jsonObjectSubType.toString()); } + + /** + * Tests that the a new copy is returned instead of a reference to the original. + */ + @Test + public void deepCloneReferenceTest() { + JSONObject clone = correctJsonObjectBasic.deepClone(); + // Both objects should point to different memory address + Assert.assertNotEquals(clone, correctJsonObjectBasic); + } + @Test public void fromMapTest() { JSONObject fromMapJsonObject = JSONObject.fromMap(booleanMap); From 0ec2609d4f233d594bfcb98c719ba0b6ce1b764e Mon Sep 17 00:00:00 2001 From: Almas Ahmad Date: Wed, 14 Apr 2021 18:45:44 +0530 Subject: [PATCH 137/171] test: ensure strip does nothing on stripped string (#8596) --- AnkiDroid/src/test/java/com/ichi2/utils/StringUtilTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AnkiDroid/src/test/java/com/ichi2/utils/StringUtilTest.java b/AnkiDroid/src/test/java/com/ichi2/utils/StringUtilTest.java index f4ad771d9875..71b0012692c9 100644 --- a/AnkiDroid/src/test/java/com/ichi2/utils/StringUtilTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/utils/StringUtilTest.java @@ -80,4 +80,9 @@ public void strip_trailing_spaces() { public void strip_trailing_and_leading_spaces() { assertEquals("Tarek", StringUtil.strip("\n\u2006 \r\n\t\u000CTarek \u0009")); } + + @Test + public void strip_does_nothing_on_stripped_string() { + assertEquals("Java", StringUtil.strip("Java")); + } } From 55e2a8c365e5567f208801ee7710a62516cf78d0 Mon Sep 17 00:00:00 2001 From: Farjad Ilyas Date: Wed, 14 Apr 2021 05:04:29 +0500 Subject: [PATCH 138/171] Don't persist Anki logo in background on startup * homescreen.xml: copy xlarge's background attr placement to normal --- AnkiDroid/src/main/res/layout/homescreen.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/AnkiDroid/src/main/res/layout/homescreen.xml b/AnkiDroid/src/main/res/layout/homescreen.xml index 9c5c80e74f33..74c98b2a4ce0 100644 --- a/AnkiDroid/src/main/res/layout/homescreen.xml +++ b/AnkiDroid/src/main/res/layout/homescreen.xml @@ -3,6 +3,7 @@ android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="?android:attr/colorBackground" android:fitsSystemWindows="true"> Date: Wed, 14 Apr 2021 19:13:01 +0530 Subject: [PATCH 139/171] Improve dialog usage by setting default focus on EditText (#8518) * Improve dialog usage by setting default focus on EditText * Add parameter details in method documentation --- .../main/java/com/ichi2/anki/DeckPicker.java | 10 ++- .../anki/DeckPickerFloatingActionMenu.java | 11 +--- .../ichi2/anki/MaterialEditTextDialog.java | 61 +++++++++++++++++++ 3 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/MaterialEditTextDialog.java diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 776c2bfa0eae..06280a84fc77 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -2610,9 +2610,8 @@ public void renameDeckDialog(final long did) { final String currentName = getCol().getDecks().name(did); mDialogEditText.setText(currentName); mDialogEditText.setSelection(mDialogEditText.getText().length()); - new MaterialDialog.Builder(DeckPicker.this) + new MaterialEditTextDialog.Builder(DeckPicker.this, mDialogEditText) .title(res.getString(R.string.rename_deck)) - .customView(mDialogEditText, true) .positiveText(R.string.rename) .negativeText(R.string.dialog_cancel) .onPositive((dialog, which) -> { @@ -2638,7 +2637,7 @@ public void renameDeckDialog(final long did) { } }) .onNegative((dialog, which) -> dismissAllDialogFragments()) - .build().show(); + .show(); } @@ -2913,9 +2912,8 @@ private void createSubDeckDialog(long did) { mDialogEditText = new FixedEditText(this); mDialogEditText.setSingleLine(); mDialogEditText.setSelection(mDialogEditText.getText().length()); - new MaterialDialog.Builder(DeckPicker.this) + new MaterialEditTextDialog.Builder(DeckPicker.this, mDialogEditText) .title(R.string.create_subdeck) - .customView(mDialogEditText, true) .positiveText(R.string.dialog_ok) .negativeText(R.string.dialog_cancel) .onPositive((dialog, which) -> { @@ -2938,7 +2936,7 @@ private void createSubDeckDialog(long did) { } }) .onNegative((dialog, which) -> dismissAllDialogFragments()) - .build().show(); + .show(); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java index 1b22d0c841cd..77e123edc720 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.java @@ -19,17 +19,13 @@ import android.animation.Animator; import android.content.SharedPreferences; import android.view.View; -import android.view.WindowManager; import android.widget.EditText; import android.widget.LinearLayout; -import com.afollestad.materialdialogs.MaterialDialog; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.ichi2.libanki.Decks; import com.ichi2.ui.FixedEditText; -import java.util.Objects; - import timber.log.Timber; public class DeckPickerFloatingActionMenu { @@ -82,11 +78,9 @@ public void onClick(View view) { closeFloatingActionMenu(); EditText mDialogEditText = new FixedEditText(mDeckPicker); mDialogEditText.setSingleLine(true); - mDialogEditText.requestFocus(); - MaterialDialog materialDialog = new MaterialDialog.Builder(mDeckPicker) + new MaterialEditTextDialog.Builder(mDeckPicker, mDialogEditText) .title(R.string.new_deck) .positiveText(R.string.dialog_ok) - .customView(mDialogEditText, true) .onPositive((dialog, which) -> { String deckName = mDialogEditText.getText().toString(); if (Decks.isValidDeckName(deckName)) { @@ -101,9 +95,6 @@ public void onClick(View view) { }) .negativeText(R.string.dialog_cancel) .show(); - - // Open keyboard when dialog shows - Objects.requireNonNull(materialDialog.getWindow()).setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); } }); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/MaterialEditTextDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/MaterialEditTextDialog.java new file mode 100644 index 000000000000..d74e44e765e3 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/MaterialEditTextDialog.java @@ -0,0 +1,61 @@ +/**************************************************************************************** + * * + * 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 . * + ****************************************************************************************/ + +package com.ichi2.anki; + +import android.content.Context; +import android.widget.EditText; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.ichi2.utils.AndroidUiUtils; + +import java.util.Objects; + +import androidx.annotation.NonNull; + +public class MaterialEditTextDialog extends MaterialDialog { + protected MaterialEditTextDialog(Builder builder) { + super(builder); + } + + public static class Builder extends MaterialDialog.Builder { + + public EditText editText; + + public Builder(@NonNull Context context, EditText editText) { + super(context); + this.editText = editText; + } + + @Override + public MaterialDialog show() { + customView(editText, true); + MaterialDialog materialDialog = super.show(); + displayKeyboard(editText, materialDialog); + return materialDialog; + } + + /** + * Method to display keyboard when dialog shows. + * @param editText EditText present in the dialog. + * @param materialDialog Dialog which contains the EditText and needs the keyboard to be displayed. + */ + public void displayKeyboard(EditText editText, MaterialDialog materialDialog) { + AndroidUiUtils.setFocusAndOpenKeyboard(editText, Objects.requireNonNull(materialDialog.getWindow())); + } + } +} From d0890de143d98609e91c6d6d55c00b0bca4be30a Mon Sep 17 00:00:00 2001 From: Mohaned Elfeky Date: Wed, 14 Apr 2021 14:44:19 +0100 Subject: [PATCH 140/171] Enable/disable lookup button depending on Lookup Dictionary preference (#8441) * added preference for lookup button * added constant value if lookup is false Co-authored-by: Mohaned hassan Co-authored-by: Mike Hardy --- .../java/com/ichi2/anki/reviewer/ActionButtonStatus.java | 6 ++++++ AnkiDroid/src/main/res/menu/reviewer.xml | 4 ++-- AnkiDroid/src/main/res/values/01-core.xml | 1 + AnkiDroid/src/main/res/xml/preferences_custom_buttons.xml | 6 ++++++ .../com/ichi2/anki/reviewer/ActionButtonStatusTest.java | 1 + 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ActionButtonStatus.java b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ActionButtonStatus.java index 17475a480544..b8830372b7f4 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ActionButtonStatus.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/reviewer/ActionButtonStatus.java @@ -5,6 +5,7 @@ import android.view.Menu; import android.view.MenuItem; +import com.ichi2.anki.Lookup; import com.ichi2.anki.R; import com.ichi2.themes.Themes; @@ -65,6 +66,11 @@ public void setup(SharedPreferences preferences) { setupButton(preferences, R.id.action_toggle_whiteboard, "customButtonEnableWhiteboard", SHOW_AS_ACTION_NEVER); setupButton(preferences, R.id.action_save_whiteboard, "customButtonSaveWhiteboard", SHOW_AS_ACTION_NEVER); setupButton(preferences, R.id.action_change_whiteboard_pen_color, "customButtonWhiteboardPenColor", SHOW_AS_ACTION_IF_ROOM); + if (!Lookup.isAvailable()) { + mCustomButtons.put(R.id.action_search_dictionary, MENU_DISABLED); + } else { + setupButton(preferences, R.id.action_search_dictionary, "customButtonLookup", SHOW_AS_ACTION_NEVER); + } } diff --git a/AnkiDroid/src/main/res/menu/reviewer.xml b/AnkiDroid/src/main/res/menu/reviewer.xml index 8a40dc668a1e..6088953814d2 100644 --- a/AnkiDroid/src/main/res/menu/reviewer.xml +++ b/AnkiDroid/src/main/res/menu/reviewer.xml @@ -119,8 +119,8 @@ + android:title="@string/menu_lookup" + /> diff --git a/AnkiDroid/src/main/res/values/01-core.xml b/AnkiDroid/src/main/res/values/01-core.xml index b6e8b54c210c..b154d52cb462 100644 --- a/AnkiDroid/src/main/res/values/01-core.xml +++ b/AnkiDroid/src/main/res/values/01-core.xml @@ -101,6 +101,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/xml/preferences_custom_buttons.xml b/AnkiDroid/src/main/res/xml/preferences_custom_buttons.xml index c5256e2d33ef..61441cc56e8b 100644 --- a/AnkiDroid/src/main/res/xml/preferences_custom_buttons.xml +++ b/AnkiDroid/src/main/res/xml/preferences_custom_buttons.xml @@ -154,5 +154,11 @@ TODO: Add a unit test android:entryValues="@array/custom_button_values" android:key="customButtonClearWhiteboard" android:title="@string/clear_whiteboard" /> +
diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/ActionButtonStatusTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/ActionButtonStatusTest.java index 82f0e8fb7abe..ec0583ebbb89 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/ActionButtonStatusTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/reviewer/ActionButtonStatusTest.java @@ -43,6 +43,7 @@ public class ActionButtonStatusTest extends RobolectricTest { public void allCustomButtonsCanBeDisabled() { Set reviewerExpectedKeys = getCustomButtonsExpectedKeys(); Set actualPreferenceKeys = PreferenceUtils.getAllCustomButtonKeys(getTargetContext()); + reviewerExpectedKeys.add("customButtonLookup"); // preference isn't read if Lookup is disabled. assertThat("Each button in the Action Bar must be modifiable in Preferences - Reviewer - App Bar Buttons", reviewerExpectedKeys, From f5854aa49a9f3accee0a1c27a1c3f2bbeb128884 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Wed, 14 Apr 2021 07:14:14 -0500 Subject: [PATCH 141/171] Attempt to de-flake "regression_test_preview" with sleeps Hard to reproduce this one locally but hopefully this helps, it is our top flake I think in unit test runs --- .../src/test/java/com/ichi2/libanki/sched/SchedV2Test.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java index 1ea35b7ffd2f..759548dbda2f 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/sched/SchedV2Test.java @@ -1744,16 +1744,18 @@ public void regression_test_preview() throws Exception { deck.put("resched", false); sched.rebuildDyn(did); col.reset(); - advanceRobolectricLooper(); Card card; for(int i = 0; i < 3; i++) { + advanceRobolectricLooperWithSleep(); card = sched.getCard(); assertNotNull(card); sched.answerCard(card, Consts.BUTTON_ONE); } + advanceRobolectricLooperWithSleep(); assertEquals(1, sched.lrnCount()); card = sched.getCard(); assertEquals(1, sched.counts(card).getLrn()); + advanceRobolectricLooperWithSleep(); sched.answerCard(card, Consts.BUTTON_ONE); assertDoesNotThrow(col::undo); } From 3fe1aae5423de341a8ec0bc6bcc869eaf967c0ad Mon Sep 17 00:00:00 2001 From: AnkiDroid Translations Date: Wed, 14 Apr 2021 15:26:22 +0000 Subject: [PATCH 142/171] Updated strings from Crowdin --- AnkiDroid/src/main/res/values-af/01-core.xml | 1 + AnkiDroid/src/main/res/values-am/01-core.xml | 1 + AnkiDroid/src/main/res/values-ar/01-core.xml | 1 + AnkiDroid/src/main/res/values-az/01-core.xml | 1 + AnkiDroid/src/main/res/values-be/01-core.xml | 1 + AnkiDroid/src/main/res/values-bg/01-core.xml | 1 + AnkiDroid/src/main/res/values-bn/01-core.xml | 1 + AnkiDroid/src/main/res/values-ca/01-core.xml | 1 + AnkiDroid/src/main/res/values-ckb/01-core.xml | 1 + AnkiDroid/src/main/res/values-cs/01-core.xml | 1 + AnkiDroid/src/main/res/values-da/01-core.xml | 1 + AnkiDroid/src/main/res/values-de/01-core.xml | 1 + AnkiDroid/src/main/res/values-el/01-core.xml | 1 + AnkiDroid/src/main/res/values-eo/01-core.xml | 1 + .../src/main/res/values-es-rAR/01-core.xml | 1 + .../src/main/res/values-es-rES/01-core.xml | 1 + AnkiDroid/src/main/res/values-et/01-core.xml | 1 + AnkiDroid/src/main/res/values-eu/01-core.xml | 1 + AnkiDroid/src/main/res/values-fa/01-core.xml | 1 + AnkiDroid/src/main/res/values-fi/01-core.xml | 1 + AnkiDroid/src/main/res/values-fil/01-core.xml | 1 + AnkiDroid/src/main/res/values-fr/01-core.xml | 1 + AnkiDroid/src/main/res/values-fy/01-core.xml | 1 + AnkiDroid/src/main/res/values-ga/01-core.xml | 1 + AnkiDroid/src/main/res/values-gl/01-core.xml | 1 + AnkiDroid/src/main/res/values-got/01-core.xml | 1 + AnkiDroid/src/main/res/values-gu/01-core.xml | 1 + AnkiDroid/src/main/res/values-heb/01-core.xml | 1 + AnkiDroid/src/main/res/values-hi/01-core.xml | 1 + .../src/main/res/values-hi/07-cardbrowser.xml | 10 +++--- AnkiDroid/src/main/res/values-hr/01-core.xml | 1 + AnkiDroid/src/main/res/values-hu/01-core.xml | 1 + AnkiDroid/src/main/res/values-hy/01-core.xml | 1 + AnkiDroid/src/main/res/values-ind/01-core.xml | 1 + AnkiDroid/src/main/res/values-is/01-core.xml | 1 + AnkiDroid/src/main/res/values-it/01-core.xml | 1 + AnkiDroid/src/main/res/values-ja/01-core.xml | 1 + AnkiDroid/src/main/res/values-jv/01-core.xml | 1 + AnkiDroid/src/main/res/values-ka/01-core.xml | 1 + AnkiDroid/src/main/res/values-kk/01-core.xml | 1 + AnkiDroid/src/main/res/values-km/01-core.xml | 1 + AnkiDroid/src/main/res/values-ko/01-core.xml | 1 + AnkiDroid/src/main/res/values-ku/01-core.xml | 1 + AnkiDroid/src/main/res/values-ky/01-core.xml | 1 + AnkiDroid/src/main/res/values-lt/01-core.xml | 1 + AnkiDroid/src/main/res/values-lv/01-core.xml | 1 + AnkiDroid/src/main/res/values-mk/01-core.xml | 1 + AnkiDroid/src/main/res/values-mn/01-core.xml | 1 + AnkiDroid/src/main/res/values-mr/01-core.xml | 1 + AnkiDroid/src/main/res/values-ms/01-core.xml | 1 + AnkiDroid/src/main/res/values-my/01-core.xml | 1 + AnkiDroid/src/main/res/values-nl/01-core.xml | 3 +- .../src/main/res/values-nl/02-strings.xml | 10 +++--- .../src/main/res/values-nl/03-dialogs.xml | 4 +-- .../src/main/res/values-nl/07-cardbrowser.xml | 8 ++--- .../src/main/res/values-nl/10-preferences.xml | 14 ++++---- AnkiDroid/src/main/res/values-nn/01-core.xml | 1 + AnkiDroid/src/main/res/values-no/01-core.xml | 1 + AnkiDroid/src/main/res/values-or/01-core.xml | 1 + .../src/main/res/values-or/02-strings.xml | 12 +++---- .../src/main/res/values-or/03-dialogs.xml | 32 +++++++++---------- .../src/main/res/values-or/04-network.xml | 28 ++++++++-------- .../src/main/res/values-or/06-statistics.xml | 8 ++--- .../src/main/res/values-or/10-preferences.xml | 20 ++++++------ .../src/main/res/values-or/11-arrays.xml | 4 +-- .../res/values-or/16-multimedia-editor.xml | 10 +++--- AnkiDroid/src/main/res/values-pa/01-core.xml | 1 + AnkiDroid/src/main/res/values-pl/01-core.xml | 3 +- .../src/main/res/values-pl/02-strings.xml | 4 +-- .../src/main/res/values-pl/03-dialogs.xml | 2 +- .../src/main/res/values-pl/10-preferences.xml | 10 +++--- .../src/main/res/values-pt-rBR/01-core.xml | 1 + .../src/main/res/values-pt-rPT/01-core.xml | 1 + AnkiDroid/src/main/res/values-ro/01-core.xml | 1 + AnkiDroid/src/main/res/values-ru/01-core.xml | 3 +- .../src/main/res/values-ru/02-strings.xml | 6 ++-- .../src/main/res/values-ru/03-dialogs.xml | 4 +-- .../src/main/res/values-ru/10-preferences.xml | 6 ++-- AnkiDroid/src/main/res/values-sat/01-core.xml | 1 + AnkiDroid/src/main/res/values-sk/01-core.xml | 1 + AnkiDroid/src/main/res/values-sl/01-core.xml | 1 + AnkiDroid/src/main/res/values-sq/01-core.xml | 1 + AnkiDroid/src/main/res/values-sr/01-core.xml | 1 + AnkiDroid/src/main/res/values-ss/01-core.xml | 1 + AnkiDroid/src/main/res/values-sv/01-core.xml | 1 + AnkiDroid/src/main/res/values-sw/01-core.xml | 1 + AnkiDroid/src/main/res/values-ta/01-core.xml | 1 + AnkiDroid/src/main/res/values-te/01-core.xml | 1 + AnkiDroid/src/main/res/values-tg/01-core.xml | 1 + AnkiDroid/src/main/res/values-tgl/01-core.xml | 1 + AnkiDroid/src/main/res/values-th/01-core.xml | 1 + AnkiDroid/src/main/res/values-ti/01-core.xml | 1 + AnkiDroid/src/main/res/values-tn/01-core.xml | 1 + AnkiDroid/src/main/res/values-tr/01-core.xml | 1 + AnkiDroid/src/main/res/values-ts/01-core.xml | 1 + AnkiDroid/src/main/res/values-tt/01-core.xml | 5 +-- .../src/main/res/values-tt/02-strings.xml | 10 +++--- .../src/main/res/values-tt/03-dialogs.xml | 18 +++++------ .../src/main/res/values-tt/06-statistics.xml | 2 +- .../src/main/res/values-tt/07-cardbrowser.xml | 6 ++-- .../src/main/res/values-tt/08-widget.xml | 2 +- AnkiDroid/src/main/res/values-uk/01-core.xml | 1 + AnkiDroid/src/main/res/values-ur/01-core.xml | 1 + AnkiDroid/src/main/res/values-uz/01-core.xml | 1 + AnkiDroid/src/main/res/values-ve/01-core.xml | 1 + AnkiDroid/src/main/res/values-vi/01-core.xml | 3 +- .../src/main/res/values-vi/02-strings.xml | 10 +++--- .../src/main/res/values-vi/03-dialogs.xml | 4 +-- .../src/main/res/values-vi/10-preferences.xml | 14 ++++---- AnkiDroid/src/main/res/values-wo/01-core.xml | 1 + AnkiDroid/src/main/res/values-xh/01-core.xml | 1 + AnkiDroid/src/main/res/values-yue/01-core.xml | 1 + .../src/main/res/values-zh-rCN/01-core.xml | 1 + .../src/main/res/values-zh-rCN/02-strings.xml | 2 +- .../main/res/values-zh-rCN/10-preferences.xml | 4 +-- .../src/main/res/values-zh-rTW/01-core.xml | 1 + AnkiDroid/src/main/res/values-zu/01-core.xml | 1 + 117 files changed, 227 insertions(+), 138 deletions(-) diff --git a/AnkiDroid/src/main/res/values-af/01-core.xml b/AnkiDroid/src/main/res/values-af/01-core.xml index 4e4db5d0b79d..c736bf425cb8 100644 --- a/AnkiDroid/src/main/res/values-af/01-core.xml +++ b/AnkiDroid/src/main/res/values-af/01-core.xml @@ -116,6 +116,7 @@ Edit tags Verwyder regtig hierdie nota en al die samegaande karte?\n%s Kies teks + Lookup text Soek in %1$s Merk nota Unmark note diff --git a/AnkiDroid/src/main/res/values-am/01-core.xml b/AnkiDroid/src/main/res/values-am/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-am/01-core.xml +++ b/AnkiDroid/src/main/res/values-am/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ar/01-core.xml b/AnkiDroid/src/main/res/values-ar/01-core.xml index 15349186403a..b64171c0a7dc 100644 --- a/AnkiDroid/src/main/res/values-ar/01-core.xml +++ b/AnkiDroid/src/main/res/values-ar/01-core.xml @@ -136,6 +136,7 @@ تعديل الوسوم هل تريد حقًا حذف هذه الملحوظة وكل بطاقاتها؟\n%s تحديد النص + Lookup text البحث في %1$s تعليم الملحوظة إلغاء تعليم الملحوظة diff --git a/AnkiDroid/src/main/res/values-az/01-core.xml b/AnkiDroid/src/main/res/values-az/01-core.xml index 9238fe47e76e..d0f0a296e056 100644 --- a/AnkiDroid/src/main/res/values-az/01-core.xml +++ b/AnkiDroid/src/main/res/values-az/01-core.xml @@ -116,6 +116,7 @@ Edit tags Bu qeyd və bütün kartlar həqiqətən də silinsin?\n%s Mətni seç + Lookup text %1$s içində axtar Qeydi işarələ İşarəni götür diff --git a/AnkiDroid/src/main/res/values-be/01-core.xml b/AnkiDroid/src/main/res/values-be/01-core.xml index ba2041d840f7..83a84476edb9 100644 --- a/AnkiDroid/src/main/res/values-be/01-core.xml +++ b/AnkiDroid/src/main/res/values-be/01-core.xml @@ -126,6 +126,7 @@ Змянiць тэгi Сапраўды выдаліць гэту нататку і ўсе яе карткі?\n%s Выбраць тэкст + Lookup text Шукаць у %1$s Пазначыць нататку Зняць пазнаку з нататкі diff --git a/AnkiDroid/src/main/res/values-bg/01-core.xml b/AnkiDroid/src/main/res/values-bg/01-core.xml index dff26959107f..d3b940a090b4 100644 --- a/AnkiDroid/src/main/res/values-bg/01-core.xml +++ b/AnkiDroid/src/main/res/values-bg/01-core.xml @@ -116,6 +116,7 @@ Редакция на етикети Наистина изтрий тази бележка и всичките и карти? \n%s Избор на текст + Lookup text Търсене в %1$s Маркирай бележка Размаркирай бележка diff --git a/AnkiDroid/src/main/res/values-bn/01-core.xml b/AnkiDroid/src/main/res/values-bn/01-core.xml index 77be0c911484..87e630d27cb7 100644 --- a/AnkiDroid/src/main/res/values-bn/01-core.xml +++ b/AnkiDroid/src/main/res/values-bn/01-core.xml @@ -116,6 +116,7 @@ Edit tags সত্যি এই নোট এবং এর কার্ড মুছবেন? \n%s টেক্সট নির্বাচন করুন + Lookup text অনুসন্ধান করুন %1$s টীকা চিহ্ন নোটের টীকা চিহ্ন মুছুন diff --git a/AnkiDroid/src/main/res/values-ca/01-core.xml b/AnkiDroid/src/main/res/values-ca/01-core.xml index 382b5e7200df..1cd61cc1fae2 100644 --- a/AnkiDroid/src/main/res/values-ca/01-core.xml +++ b/AnkiDroid/src/main/res/values-ca/01-core.xml @@ -116,6 +116,7 @@ Edita etiquetes Segur que voleu suprimir aquesta nota i totes les seves fitxes?\n%s Selecciona text + Lookup text Cerca-ho a %1$s Marcar nota Desmarcar diff --git a/AnkiDroid/src/main/res/values-ckb/01-core.xml b/AnkiDroid/src/main/res/values-ckb/01-core.xml index 98cb018e2c19..7859ac31c7e2 100644 --- a/AnkiDroid/src/main/res/values-ckb/01-core.xml +++ b/AnkiDroid/src/main/res/values-ckb/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Nivîsê hilbijêre + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-cs/01-core.xml b/AnkiDroid/src/main/res/values-cs/01-core.xml index 569b5aaafbd6..251adcb3178c 100644 --- a/AnkiDroid/src/main/res/values-cs/01-core.xml +++ b/AnkiDroid/src/main/res/values-cs/01-core.xml @@ -126,6 +126,7 @@ Upravit štítky Opravdu smazat poznámku a všechny její karty?\n%s Vybrat text + Lookup text Vyhledat v %1$s Označit poznámku Odznačit poznámku diff --git a/AnkiDroid/src/main/res/values-da/01-core.xml b/AnkiDroid/src/main/res/values-da/01-core.xml index 61221196fd34..7d2e935d04c4 100644 --- a/AnkiDroid/src/main/res/values-da/01-core.xml +++ b/AnkiDroid/src/main/res/values-da/01-core.xml @@ -116,6 +116,7 @@ Redigér tags Vil du virkelig gerne slette denne note og alle dens kort?\n%s Vælg tekst + Lookup text Slå op i %1$s Markér note Fjern markering fra note diff --git a/AnkiDroid/src/main/res/values-de/01-core.xml b/AnkiDroid/src/main/res/values-de/01-core.xml index 623161c68abe..a8cff5ce42d5 100644 --- a/AnkiDroid/src/main/res/values-de/01-core.xml +++ b/AnkiDroid/src/main/res/values-de/01-core.xml @@ -116,6 +116,7 @@ Schlagwörter bearbeiten Diese Notiz und alle ihre Karten tatsächlich löschen?\n%s Text auswählen + Lookup text In %1$s nachschlagen Notiz markieren Markierung entfernen diff --git a/AnkiDroid/src/main/res/values-el/01-core.xml b/AnkiDroid/src/main/res/values-el/01-core.xml index 22a48ca60345..52723c2ba414 100644 --- a/AnkiDroid/src/main/res/values-el/01-core.xml +++ b/AnkiDroid/src/main/res/values-el/01-core.xml @@ -116,6 +116,7 @@ Επεξεργασία ετικετών Πραγματικά να διαγραφεί αυτή η σημείωση και όλες οι κάρτες της;\n%s Επιλογή κειμένου + Lookup text Αναζήτηση στο %1$s Μαρκάρισμα σημείωσης Ξεμαρκάρισμα σημείωσης diff --git a/AnkiDroid/src/main/res/values-eo/01-core.xml b/AnkiDroid/src/main/res/values-eo/01-core.xml index 1ed3bfd9f998..ca8ace7a43fe 100644 --- a/AnkiDroid/src/main/res/values-eo/01-core.xml +++ b/AnkiDroid/src/main/res/values-eo/01-core.xml @@ -116,6 +116,7 @@ Redakti etikedojn Ĉu vi certe volas forigi tiun ĉi noton kaj ĉiujn ĝiajn kartojn?\n%s Elekti tekston + Lookup text Serĉi en %1$s Marki noton Malmarki noton diff --git a/AnkiDroid/src/main/res/values-es-rAR/01-core.xml b/AnkiDroid/src/main/res/values-es-rAR/01-core.xml index 59ef9e4acfa6..d26ecee0557e 100644 --- a/AnkiDroid/src/main/res/values-es-rAR/01-core.xml +++ b/AnkiDroid/src/main/res/values-es-rAR/01-core.xml @@ -116,6 +116,7 @@ Editar etiquetas ¿Realmente eliminar esta nota y todas sus tarjetas?\n%s Seleccione el texto + Lookup text Revisión en %1$s Marcar nota Desmarcar nota diff --git a/AnkiDroid/src/main/res/values-es-rES/01-core.xml b/AnkiDroid/src/main/res/values-es-rES/01-core.xml index d3ba3ff3e09c..a81f6bec1b92 100644 --- a/AnkiDroid/src/main/res/values-es-rES/01-core.xml +++ b/AnkiDroid/src/main/res/values-es-rES/01-core.xml @@ -116,6 +116,7 @@ Editar etiquetas ¿Realmente deseas eliminar esta nota y todas sus tarjetas?\n%s Seleccionar texto + Lookup text Búsqueda en %1$s Marcar nota Desmarcar nota diff --git a/AnkiDroid/src/main/res/values-et/01-core.xml b/AnkiDroid/src/main/res/values-et/01-core.xml index 6a5e0a84bba1..b43b24e6dd9c 100644 --- a/AnkiDroid/src/main/res/values-et/01-core.xml +++ b/AnkiDroid/src/main/res/values-et/01-core.xml @@ -116,6 +116,7 @@ Muuda silte Kas tõesti kustutada see märge ja kõik selle kaardid?\n%s Vali text + Lookup text Otsi %1$s Tähista märge Eemalda tähistus märkelt diff --git a/AnkiDroid/src/main/res/values-eu/01-core.xml b/AnkiDroid/src/main/res/values-eu/01-core.xml index e64b134ee056..c378bc952890 100644 --- a/AnkiDroid/src/main/res/values-eu/01-core.xml +++ b/AnkiDroid/src/main/res/values-eu/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Hautatu testua + Lookup text Lookup in %1$s Markatu oharra Desmarkatu oharra diff --git a/AnkiDroid/src/main/res/values-fa/01-core.xml b/AnkiDroid/src/main/res/values-fa/01-core.xml index 43caa2fcc521..4b3fa4679e33 100644 --- a/AnkiDroid/src/main/res/values-fa/01-core.xml +++ b/AnkiDroid/src/main/res/values-fa/01-core.xml @@ -116,6 +116,7 @@ ویرایش تگ‌ها مطمئنید می خواهید این یادداشت و همه کارتهایش را پاک کنید؟\n%s انتخاب متن + Lookup text جستجو در %1$s نشانه‌گذاری یادداشت برداشتن علامتگذاری یادداشت diff --git a/AnkiDroid/src/main/res/values-fi/01-core.xml b/AnkiDroid/src/main/res/values-fi/01-core.xml index 4212218ad486..e675478a8721 100644 --- a/AnkiDroid/src/main/res/values-fi/01-core.xml +++ b/AnkiDroid/src/main/res/values-fi/01-core.xml @@ -116,6 +116,7 @@ Muokkaa tageja Haluatko varmasti poistaa tämän merkinnän ja kaikki sen kortit?\n%s Valitse teksti + Lookup text Haku %1$s Merkitse merkintä Poista merkintä diff --git a/AnkiDroid/src/main/res/values-fil/01-core.xml b/AnkiDroid/src/main/res/values-fil/01-core.xml index d81900e683f8..7dd6ea76906a 100644 --- a/AnkiDroid/src/main/res/values-fil/01-core.xml +++ b/AnkiDroid/src/main/res/values-fil/01-core.xml @@ -117,6 +117,7 @@ mga natitirang minuto Edit tags Talaga bang gusto mong burahin ang talang ito at pati na rin ang lahat ngmga kard?\n%s Pumili ng teksto + Lookup text Hanapin sa %1$s Markahan ang tala Alisin ang marka ng tala diff --git a/AnkiDroid/src/main/res/values-fr/01-core.xml b/AnkiDroid/src/main/res/values-fr/01-core.xml index 219e13ace7c6..f19bf80802c5 100644 --- a/AnkiDroid/src/main/res/values-fr/01-core.xml +++ b/AnkiDroid/src/main/res/values-fr/01-core.xml @@ -116,6 +116,7 @@ Modifier les étiquettes Voulez-vous vraiment supprimer cette note et toutes ses cartes ?\n%s Sélectionner le texte + Lookup text Trouvé en %1$s Marquer la note Démarquer la note diff --git a/AnkiDroid/src/main/res/values-fy/01-core.xml b/AnkiDroid/src/main/res/values-fy/01-core.xml index ea11c6d70b85..2d47a3f5940f 100644 --- a/AnkiDroid/src/main/res/values-fy/01-core.xml +++ b/AnkiDroid/src/main/res/values-fy/01-core.xml @@ -116,6 +116,7 @@ Edit tags Dizze memo en al syn kaarten fuortsmite?\n%s Tekst selektearje + Lookup text Opsykje yn %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ga/01-core.xml b/AnkiDroid/src/main/res/values-ga/01-core.xml index 181623d9f204..35a02fb357b6 100644 --- a/AnkiDroid/src/main/res/values-ga/01-core.xml +++ b/AnkiDroid/src/main/res/values-ga/01-core.xml @@ -131,6 +131,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-gl/01-core.xml b/AnkiDroid/src/main/res/values-gl/01-core.xml index ffc49676db06..53a4c8c362dc 100644 --- a/AnkiDroid/src/main/res/values-gl/01-core.xml +++ b/AnkiDroid/src/main/res/values-gl/01-core.xml @@ -116,6 +116,7 @@ Edit tags Tes a certeza de querer eliminar esta nota e todos os seus cartóns?\n%s Seleccionar texto + Lookup text Procurar en %1$s Marcar nota Desmarcar nota diff --git a/AnkiDroid/src/main/res/values-got/01-core.xml b/AnkiDroid/src/main/res/values-got/01-core.xml index 76cfc1137a21..18b7e0369e59 100644 --- a/AnkiDroid/src/main/res/values-got/01-core.xml +++ b/AnkiDroid/src/main/res/values-got/01-core.xml @@ -116,6 +116,7 @@ Edit tags Wileizu bi sunjai þo melein jah allos kartos is usniman?\n%s Walei bokos + Lookup text Gasokei in %1$s Gatarhei melein Ungatarhei melein diff --git a/AnkiDroid/src/main/res/values-gu/01-core.xml b/AnkiDroid/src/main/res/values-gu/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-gu/01-core.xml +++ b/AnkiDroid/src/main/res/values-gu/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-heb/01-core.xml b/AnkiDroid/src/main/res/values-heb/01-core.xml index f0d9eaf007d1..ce69a475155f 100644 --- a/AnkiDroid/src/main/res/values-heb/01-core.xml +++ b/AnkiDroid/src/main/res/values-heb/01-core.xml @@ -127,6 +127,7 @@ ערוך תגיות למחוק את הרשומה הזאת ואת כל הקלפים שלה?‏\n%s בחירת טקסט + Lookup text חפש ב%1$s סמן הערה הסר סימון הערה diff --git a/AnkiDroid/src/main/res/values-hi/01-core.xml b/AnkiDroid/src/main/res/values-hi/01-core.xml index 63ceb98f7606..e3b39dd8c993 100644 --- a/AnkiDroid/src/main/res/values-hi/01-core.xml +++ b/AnkiDroid/src/main/res/values-hi/01-core.xml @@ -116,6 +116,7 @@ टैग संपादित करें वास्तव में इस नोट और उसके सभी cards?\n%s को हटाना पाठ का चयन करें + Lookup text लुकअप में %1$s मार्क नोट करें नोट अचिह्नित करें diff --git a/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml index 06236ba55bbc..97ab05711fc0 100644 --- a/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values-hi/07-cardbrowser.xml @@ -102,11 +102,11 @@ Lapses Average Time Total Time - Card Type - Note Type - Deck Name - Card Id - Note Id + कार्ड प्रकार + नोट प्रकार + डेक का नाम + कार्ड की पहचान + नोट की पहचान समय दिनांक Rating diff --git a/AnkiDroid/src/main/res/values-hr/01-core.xml b/AnkiDroid/src/main/res/values-hr/01-core.xml index 877a60aba290..ecc4bd87ad81 100644 --- a/AnkiDroid/src/main/res/values-hr/01-core.xml +++ b/AnkiDroid/src/main/res/values-hr/01-core.xml @@ -121,6 +121,7 @@ Edit tags Zaista izbrisati ovu bilješku i sve njezine kartice?\n%s Odaberi tekst + Lookup text Traži u %1$s Označi bilješku Odznači bilješku diff --git a/AnkiDroid/src/main/res/values-hu/01-core.xml b/AnkiDroid/src/main/res/values-hu/01-core.xml index 8681fa50e4fd..1637176b2b94 100644 --- a/AnkiDroid/src/main/res/values-hu/01-core.xml +++ b/AnkiDroid/src/main/res/values-hu/01-core.xml @@ -116,6 +116,7 @@ Címkék szerkesztése Biztosan törölni akarod ezt a jegyzetet és az összes kártyáját?\n%s Szöveg kijelölése + Lookup text Keresés itt: %1$s Jegyzet megjelölése Jegyzet megjelölés megszüntetése diff --git a/AnkiDroid/src/main/res/values-hy/01-core.xml b/AnkiDroid/src/main/res/values-hy/01-core.xml index 8bf4496df530..1f40695ca722 100644 --- a/AnkiDroid/src/main/res/values-hy/01-core.xml +++ b/AnkiDroid/src/main/res/values-hy/01-core.xml @@ -116,6 +116,7 @@ Edit tags Ջնջե՞լ այս գրառումը և նրա բոլոր քարտերը:\n%s Ընտրել գրվածքը + Lookup text Որոնում %1$s-ում Նշել գրառումը Ապանշել գրառումը diff --git a/AnkiDroid/src/main/res/values-ind/01-core.xml b/AnkiDroid/src/main/res/values-ind/01-core.xml index b0883f91784c..be752d2bacb6 100644 --- a/AnkiDroid/src/main/res/values-ind/01-core.xml +++ b/AnkiDroid/src/main/res/values-ind/01-core.xml @@ -111,6 +111,7 @@ Edit tag Yakin akan menghapus catatan ini dan semua kartunya?\n%s Pilih teks + Lookup text Pencarian di %1$s Tandai catatan Batal tandai catatan diff --git a/AnkiDroid/src/main/res/values-is/01-core.xml b/AnkiDroid/src/main/res/values-is/01-core.xml index 0704f91409d3..847740481de8 100644 --- a/AnkiDroid/src/main/res/values-is/01-core.xml +++ b/AnkiDroid/src/main/res/values-is/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-it/01-core.xml b/AnkiDroid/src/main/res/values-it/01-core.xml index 10254c78e0fc..e1847d3a406c 100644 --- a/AnkiDroid/src/main/res/values-it/01-core.xml +++ b/AnkiDroid/src/main/res/values-it/01-core.xml @@ -116,6 +116,7 @@ Modifica etichette Vuoi veramente eliminare questa nota e tutte le sue carte?\n%s Seleziona testo + Lookup text Ricerca in %1$s Contrassegna nota Rimuovi contrassegno nota diff --git a/AnkiDroid/src/main/res/values-ja/01-core.xml b/AnkiDroid/src/main/res/values-ja/01-core.xml index 35888e44ea6a..abed4532cf6b 100644 --- a/AnkiDroid/src/main/res/values-ja/01-core.xml +++ b/AnkiDroid/src/main/res/values-ja/01-core.xml @@ -111,6 +111,7 @@ タグを編集 このカードの元となるノートを削除してもよろしいですか? 同じノートから作られたカード(例:裏表反転カード)があれば、それらも全て削除されます。\n%s テキストを選択 + Lookup text %1$s で検索 ノートにマークを付ける ノートのマークをはずす diff --git a/AnkiDroid/src/main/res/values-jv/01-core.xml b/AnkiDroid/src/main/res/values-jv/01-core.xml index 4088b283a29d..0683328796c3 100644 --- a/AnkiDroid/src/main/res/values-jv/01-core.xml +++ b/AnkiDroid/src/main/res/values-jv/01-core.xml @@ -111,6 +111,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ka/01-core.xml b/AnkiDroid/src/main/res/values-ka/01-core.xml index d36b2f40cbf2..75eda1624a6a 100644 --- a/AnkiDroid/src/main/res/values-ka/01-core.xml +++ b/AnkiDroid/src/main/res/values-ka/01-core.xml @@ -116,6 +116,7 @@ Edit tags ნამდვილად წავშალო ეს შენიშვნა და ყველა მისი ბარათი?\n%s აირჩიე ტექსტი + Lookup text Lookup in %1$s მონიშნე შენიშვნა გააუქმე შენიშვნა diff --git a/AnkiDroid/src/main/res/values-kk/01-core.xml b/AnkiDroid/src/main/res/values-kk/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-kk/01-core.xml +++ b/AnkiDroid/src/main/res/values-kk/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-km/01-core.xml b/AnkiDroid/src/main/res/values-km/01-core.xml index c7dc3a70fbb5..54d554958a08 100644 --- a/AnkiDroid/src/main/res/values-km/01-core.xml +++ b/AnkiDroid/src/main/res/values-km/01-core.xml @@ -111,6 +111,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ko/01-core.xml b/AnkiDroid/src/main/res/values-ko/01-core.xml index 3ca05d53f416..44a06e0a09e7 100644 --- a/AnkiDroid/src/main/res/values-ko/01-core.xml +++ b/AnkiDroid/src/main/res/values-ko/01-core.xml @@ -111,6 +111,7 @@ 태그編輯 다음 노트와 그 노트의 모든 카드를 정말로 삭제하시겠습니까?\n%s 텍스트 선택 + Lookup text %1$s에서 찾아보기 노트 표시 노트 표시 해제 diff --git a/AnkiDroid/src/main/res/values-ku/01-core.xml b/AnkiDroid/src/main/res/values-ku/01-core.xml index 98cb018e2c19..7859ac31c7e2 100644 --- a/AnkiDroid/src/main/res/values-ku/01-core.xml +++ b/AnkiDroid/src/main/res/values-ku/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Nivîsê hilbijêre + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ky/01-core.xml b/AnkiDroid/src/main/res/values-ky/01-core.xml index 5a933e8ed8d9..6a91fe5418c5 100644 --- a/AnkiDroid/src/main/res/values-ky/01-core.xml +++ b/AnkiDroid/src/main/res/values-ky/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-lt/01-core.xml b/AnkiDroid/src/main/res/values-lt/01-core.xml index 0afd7dce5d2d..fb038783319c 100644 --- a/AnkiDroid/src/main/res/values-lt/01-core.xml +++ b/AnkiDroid/src/main/res/values-lt/01-core.xml @@ -126,6 +126,7 @@ Edit tags Ar tikrai norite ištrinti šį užrašą ir visas su juo susijusias korteles?\n%s Žymėti tekstą + Lookup text Ieškoti: %1$s Žymėti užrašą Atžymėti užrašą diff --git a/AnkiDroid/src/main/res/values-lv/01-core.xml b/AnkiDroid/src/main/res/values-lv/01-core.xml index f24b2c1cfbb0..ee8e6f8b411c 100644 --- a/AnkiDroid/src/main/res/values-lv/01-core.xml +++ b/AnkiDroid/src/main/res/values-lv/01-core.xml @@ -121,6 +121,7 @@ Edit tags Tiešām dzēst šo piezīmi un visas pievienotās kārtis?\n%s Iezīmēt tekstu + Lookup text Meklēt iekš %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-mk/01-core.xml b/AnkiDroid/src/main/res/values-mk/01-core.xml index 96cd38f5a24e..aad90d4f7fd8 100644 --- a/AnkiDroid/src/main/res/values-mk/01-core.xml +++ b/AnkiDroid/src/main/res/values-mk/01-core.xml @@ -116,6 +116,7 @@ Edit tags Навистина избришете ја оваа белешка и сите нејзини картички?\n%s Изберете текст + Lookup text Пребарување во %1$s Означите белешка Отштиклирај ја белешката diff --git a/AnkiDroid/src/main/res/values-mn/01-core.xml b/AnkiDroid/src/main/res/values-mn/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-mn/01-core.xml +++ b/AnkiDroid/src/main/res/values-mn/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-mr/01-core.xml b/AnkiDroid/src/main/res/values-mr/01-core.xml index 173b2e546e80..c9e547a3e9ed 100644 --- a/AnkiDroid/src/main/res/values-mr/01-core.xml +++ b/AnkiDroid/src/main/res/values-mr/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ms/01-core.xml b/AnkiDroid/src/main/res/values-ms/01-core.xml index 27fe6c8920fb..71052d76f9b1 100644 --- a/AnkiDroid/src/main/res/values-ms/01-core.xml +++ b/AnkiDroid/src/main/res/values-ms/01-core.xml @@ -111,6 +111,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-my/01-core.xml b/AnkiDroid/src/main/res/values-my/01-core.xml index 3e6aee4acb26..6ac4f5c8d756 100644 --- a/AnkiDroid/src/main/res/values-my/01-core.xml +++ b/AnkiDroid/src/main/res/values-my/01-core.xml @@ -111,6 +111,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-nl/01-core.xml b/AnkiDroid/src/main/res/values-nl/01-core.xml index fdf67c4219bf..a6062d06f416 100644 --- a/AnkiDroid/src/main/res/values-nl/01-core.xml +++ b/AnkiDroid/src/main/res/values-nl/01-core.xml @@ -116,6 +116,7 @@ Tags bewerken Deze memo en al zijn kaarten verwijderen?\n%s Tekst selecteren + Lookup text Opzoeken in %1$s Memo markeren Memo onmarkeren @@ -154,7 +155,7 @@ Aangepaste studiesessie Gelieve eerst de bestaande aangepaste studieset te hernoemen. Deze leerset is leeg - Deck Search + Leerset zoeken Kaart toevoegen Ongeldige setnaam Deze leerset is leeg. Druk op de + knop om nieuwe inhoud toe te voegen. diff --git a/AnkiDroid/src/main/res/values-nl/02-strings.xml b/AnkiDroid/src/main/res/values-nl/02-strings.xml index 0450b37cf81d..678bda6f1898 100644 --- a/AnkiDroid/src/main/res/values-nl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-nl/02-strings.xml @@ -122,7 +122,7 @@ Kaarten herschikken... %1$s (van “%2$s”) De kaarten toegewezen aan niets zullen worden verwijderd. Als een memo resterende kaarten heeft, zullen zij verloren gaan. Weet u zeker dat u wilt doorgaan? - Timebox reached + Timebox bereikt %1$d kaart geoefend in %2$s %1$d kaarten geoefend in %2$s @@ -315,7 +315,7 @@ Fout met de kaartinhoud: Laden van ‘%s ’ is mislukt Fout met de kaartinhoud: Media-update vereist - Loading http resources is no longer supported + Het laden van http bronnen wordt niet langer ondersteund Laden van set ‘%s ’ mislukt Sets doorzoeken @@ -347,7 +347,7 @@ + - - Email address is required - Password is required - Press back again to exit + E-mailadres vereist + Wachtwoord is vereist + Druk nogmaals op \'terug\' om af te sluiten diff --git a/AnkiDroid/src/main/res/values-nl/03-dialogs.xml b/AnkiDroid/src/main/res/values-nl/03-dialogs.xml index 47b9f54e34c9..4d8e32d31ef8 100644 --- a/AnkiDroid/src/main/res/values-nl/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-nl/03-dialogs.xml @@ -228,8 +228,8 @@ Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Stuur probleemoplossingsrapport + Rapport is al ingediend Een automatische synchronisatie kan worden geactiveerd in %d seconde diff --git a/AnkiDroid/src/main/res/values-nl/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-nl/07-cardbrowser.xml index dcf15264f5b6..ffb451db9f08 100644 --- a/AnkiDroid/src/main/res/values-nl/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values-nl/07-cardbrowser.xml @@ -46,7 +46,7 @@ Alle leersets Memo\'s verwijderen Kaart opschorten - WIjzig dek + Wijzig leerset Kaarten herstellen Memo\'s markeren Memo onmarkeren @@ -85,8 +85,8 @@ (nieuw) (gefilterd) (leren) - Geen kaarten gevonden in set ‘%s - Alle sets doorzoeken + Geen kaarten gevonden in leerset ‘%s + Alle leersets doorzoeken Onbekend %d kaart verwijderd @@ -104,7 +104,7 @@ Totale tijd Kaarttype Memotype - Kaartenset Naam + Leerset Naam Kaart ID Memo-ID Tijd diff --git a/AnkiDroid/src/main/res/values-nl/10-preferences.xml b/AnkiDroid/src/main/res/values-nl/10-preferences.xml index 5ce5eb2cb313..d03512c4b7f3 100644 --- a/AnkiDroid/src/main/res/values-nl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-nl/10-preferences.xml @@ -151,8 +151,8 @@ XXX s Tijd om de volgende vraag te tonen Taal selecteren - Disable card hardware render - Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. + Hardware render kaart uitschakelen + Hardware render is sneller maar kan problemen hebben, met name op Android 8/8.1. Als u delen van de gebruikersinterface voor de beoordeling van de kaart niet kunt zien, probeer dan deze instelling. Veilige weergavemodus Alle animaties uitschakelen en veiligere methode gebruiken om kaarten te trekken. Dit kan nodig zijn voor sommige E-inkt-apparaten. Bewerkingsmodus voor een enkel veld uitschakelen @@ -174,8 +174,8 @@ Max vooruit leren Stel een tijdslimiet in XXX min - New timezone handling - Start of next day (0 is midnight, 23 is 11PM) + Nieuwe tijdzone afhandeling + Begin van de volgende dag (0 is middernacht, 23 is 11PM) XXX uur na middernacht Experimentele V2-planner Schakel de experimentele planner in. Dwingt veranderingen in een bepaalde richting bij de volgende synchronisatie @@ -291,8 +291,8 @@ Zet in de memobewerker alle instanties van <br> om in nieuwe lijnen bij het bewerken van een kaart. Eenvoudig getypte antwoordopmaak Herstelt het verschijnen van \'􏿾\' in getypte antwoordresultaten - Focus ‘type in answer’ - Automatically focus the ‘type in answer’ field in the reviewer + Focus op ‘type in antwoord’ + Automatisch focussen op het ‘type in antwoord’ veld in de beoordelaar - Open Changelog + Wijzigingslogboek openen diff --git a/AnkiDroid/src/main/res/values-nn/01-core.xml b/AnkiDroid/src/main/res/values-nn/01-core.xml index e121a5bf23b4..5f13b770e4d8 100644 --- a/AnkiDroid/src/main/res/values-nn/01-core.xml +++ b/AnkiDroid/src/main/res/values-nn/01-core.xml @@ -116,6 +116,7 @@ Edit tags Slette dette notatet med alle kortene?\n%s Merk tekst + Lookup text Oppslag i %1$s Marker notat Fjern notatmarkering diff --git a/AnkiDroid/src/main/res/values-no/01-core.xml b/AnkiDroid/src/main/res/values-no/01-core.xml index e121a5bf23b4..5f13b770e4d8 100644 --- a/AnkiDroid/src/main/res/values-no/01-core.xml +++ b/AnkiDroid/src/main/res/values-no/01-core.xml @@ -116,6 +116,7 @@ Edit tags Slette dette notatet med alle kortene?\n%s Merk tekst + Lookup text Oppslag i %1$s Marker notat Fjern notatmarkering diff --git a/AnkiDroid/src/main/res/values-or/01-core.xml b/AnkiDroid/src/main/res/values-or/01-core.xml index 67487ec69ea8..11f79c25780f 100644 --- a/AnkiDroid/src/main/res/values-or/01-core.xml +++ b/AnkiDroid/src/main/res/values-or/01-core.xml @@ -116,6 +116,7 @@ ଟ୍ୟାଗ୍ ସମ୍ପାଦନା ଆପଣ ପ୍ରକୃତରେ ଏହି ନୋଟ୍ ଏବଂ ଏହାର ସମସ୍ତ କାର୍ଡ ବିଲୋପ କରିବାକୁ ଚାହୁଁଛନ୍ତି କି?\n%s ପାଠ୍ୟ ଚୟନ କରନ୍ତୁ + Lookup text %1$s ରେ ଦେଖନ୍ତୁ ନୋଟ ଚିନ୍ହିତ କରନ୍ତୁ ନୋଟ ଅଣଚିନ୍ହିତ କରନ୍ତୁ diff --git a/AnkiDroid/src/main/res/values-or/02-strings.xml b/AnkiDroid/src/main/res/values-or/02-strings.xml index 8813fd4b0278..34a3ca01f6a2 100644 --- a/AnkiDroid/src/main/res/values-or/02-strings.xml +++ b/AnkiDroid/src/main/res/values-or/02-strings.xml @@ -63,14 +63,14 @@ ଧଳାପଟା ଦେଖାଅ ଧଳାପଟା ଲୁଚାଅ ଧଳାପଟା ସଫା କରନ୍ତୁ - ଅଡିଓ ପୁଣି ଚଲାନ୍ତୁ + ଅଡ଼ିଓ ପୁଣି ଚଲାନ୍ତୁ ପତ୍ର ଲିଚ ବୋଲି ଚିନ୍ହିତ ହୋଇ ନିଲମ୍ବିତ ହେଇଛି ପତ୍ରକୁ ଲିଚ ବୋଲି ଚିନ୍ହିତ ହେଇଛି ଖରାପ ଫୋଣ୍ଟ ଫାଇଲ %s ଅଜଣା ତ୍ରୁଟି ଏହା WebView ରେ ଏକ ଆନ୍ତରିକ ତ୍ରୁଟି ଯୋଗୁଁ ହେଇଛି ମେମୋରୀ ସରିଗଲା - WebView ସିଷ୍ଟମ୍ ଦ୍ୱାରା ସମାପ୍ତ ହେଲା। ଏହା ସାଧାରଣତଃ ଘଟେ ଯେତେବେଳେ ଆପ୍ ର ମେମୋରୀ ସରିଯାଏ, ପ୍ରାୟତଃ ବଡ଼ ଫଣ୍ଟ କିମ୍ବା ମିଡିଆ ହେତୁ। + WebView ସିଷ୍ଟମ୍ ଦ୍ୱାରା ସମାପ୍ତ ହେଲା। ଏହା ସାଧାରଣତଃ ଘଟେ ଯେତେବେଳେ ଆପ୍ ର ମେମୋରୀ ସରିଯାଏ, ପ୍ରାୟତଃ ବଡ଼ ଫଣ୍ଟ କିମ୍ବା ମିଡ଼ିଆ ହେତୁ। WebView ପ୍ରସ୍ତୁତକର୍ତ୍ତା ଦୁର୍ଘଟଣାଗ୍ରସ୍ତ ହୋଇଛି। କାରଣ: %s ସାଂଘାତିକ ତ୍ରୁଟି: WebView ପ୍ରସ୍ତୁତକର୍ତ୍ତା ଦୁର୍ଘଟଣାଗ୍ରସ୍ତ ହୋଇଛି। କାରଣ: %s ସିଷ୍ଟମର WebView ପ୍ରସ୍ତୁତକର୍ତ୍ତାରେ ବିଫଳତା @@ -122,7 +122,7 @@ ପତ୍ର ପୁନଃବ୍ୟବସ୍ଥିତ ହଉଛି %1$s (“%2$s” ରୁ) ବିନା କୌଣସି ଜିନିଷକୁ ମ୍ୟାପ୍ ହୋଇଥିବା ପତ୍ର ବିଲୋପ ହେଇଯିବ। ଯଦି କୌଣସି ନୋଟ୍ ର ଅବଶିଷ୍ଟ ପତ୍ର ନାହିଁ, ତେବେ ଏହା ନଷ୍ଟ ହୋଇଯିବ। ଆପଣ ଜାରି ରଖିବାକୁ ଚାହୁଁଛନ୍ତି କି? - Timebox reached + ସମୟବାକ୍ସ ପହଞ୍ଚିଛି %1$d ପତ୍ର %2$s ସମୟରେ ଅଧ୍ୟୟନ କଲେ %1$d ଟି ପତ୍ର %2$s ସମୟରେ ଅଧ୍ୟୟନ କଲେ @@ -177,7 +177,7 @@ ସଂଗ୍ରହକୁ ବଦଳାଉଛି… ଆମଦାନୀ ବାଧାପ୍ରାପ୍ତ: AnkiDroid ବନ୍ଦ ହୋଇଗଲା | କାର୍ଯ୍ୟସୂଚୀ ଅନ୍ତର୍ଭୂକ୍ତ କରନ୍ତୁ - ମିଡିଆ ଅନ୍ତର୍ଭୂକ୍ତ କରନ୍ତୁ + ମିଡ଼ିଆ ଅନ୍ତର୍ଭୂକ୍ତ କରନ୍ତୁ ସଂଗ୍ରହକୁ Anki package ଭାବରେ ରପ୍ତାନି କରିବେ କି? “%s” କୁ apkg ଫାଇଲ ଭାବରେ ରପ୍ତାନି କରିବେ କି? Anki ପ୍ୟାକେଜ୍ ଫାଇଲ୍ ରପ୍ତାନି କରୁଛି… @@ -313,7 +313,7 @@ କ୍ଲିପବୋର୍ଡରେ ଡିବଗ୍ ସୂଚନା କପି କରିବାରେ ତ୍ରୁଟି କାର୍ଡ ବିଷୟବସ୍ତୁ ତ୍ରୁଟି: ‘%s ଲୋଡ୍ କରିବାରେ ବିଫଳ ହେଲା - କାର୍ଡ ବିଷୟବସ୍ତୁ ତ୍ରୁଟି: ମିଡିଆ ଅଦ୍ୟତନ ଆବଶ୍ୟକ + କାର୍ଡ ବିଷୟବସ୍ତୁ ତ୍ରୁଟି: ମିଡ଼ିଆ ଅଦ୍ୟତନ ଆବଶ୍ୟକ http ଉତ୍ସଗୁଡ଼ିକୁ ଧାରଣ କରିବା ଆଉ ସମର୍ଥିତ ନୁହେଁ ଡେକ୍ ‘%s’ ଲୋଡ୍ ହବାରେ ବିଫଳ ହେଲା @@ -347,6 +347,6 @@ - ଇମେଲ୍ ଠିକଣା ଆବଶ୍ୟକ - ପାସୱାର୍ଡ ଆବଶ୍ୟକ + ପାସ୍ଓ୍ୱାର୍ଡ଼ ଆବଶ୍ୟକ ପ୍ରସ୍ଥାନ କରିବା ପାଇଁ ପୁନର୍ବାର ଦବାନ୍ତୁ diff --git a/AnkiDroid/src/main/res/values-or/03-dialogs.xml b/AnkiDroid/src/main/res/values-or/03-dialogs.xml index 79181d6a9653..8b3d346ec567 100644 --- a/AnkiDroid/src/main/res/values-or/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-or/03-dialogs.xml @@ -107,7 +107,7 @@ ଗତ x ଦିନରେ ଯୋଡି ହୋଇଥିବା ନୂତନ ପତ୍ରଗୁଡ଼ିକର ପୂର୍ବାବଲୋକନ କରନ୍ତୁ: କଷ୍ଟମ୍ ଅଧ୍ୟୟନ ତାସଖଣ୍ଡ ପୁନଃନିର୍ମାଣ କରିହେଲା ନାହିଁ ସଂଗ୍ରହ ଆମଦାନି ହଉଛି … - ମିଡିଆ ଫାଇଲଗୁଡିକ ଆମଦାନୀ ହଉଛି %1$d%%… + ମିଡ଼ିଆ ଫାଇଲଗୁଡିକ ଆମଦାନୀ କରୁଛି %1$d%%… ନୋଟ୍ ଆମଦାନି:%1$d%%\n • ପତ୍ର ଆମଦାନି:%2$d%%\n• ପରବର୍ତ୍ତୀ ପ୍ରକ୍ରିୟାକରଣ:%3$d%% ବିଦ୍ୟମାନ %2$d ନୋଟଗୁଡିକର %1$dକୁ ଅଦ୍ୟତନ କରାଯାଇଛି କିଛି ଅଦ୍ୟତନକୁ ଅଣଦେଖା କରାଯାଇଥିଲା କାରଣ ନୋଟ୍ ପ୍ରକାର ବଦଳିଛି @@ -133,18 +133,18 @@ ଦେଖନ୍ତୁ ନିର୍ଦ୍ଦିଷ୍ଟ ପଥ ଏକ ବୈଧ ନିର୍ଦେଶିକା ନୁହେଁ - ମିଡିଆ ଯାଞ୍ଚ କରିବେ କି? - ବଡ଼ ମିଡିଆ ସଂଗ୍ରହ ସହିତ ଏହା ଏକ ଦୀର୍ଘ ସମୟ ନେଇପାରେ - ମିଡିଆ ଯାଞ୍ଚ କରୁଛି… - ମିଡିଆ ଯାଞ୍ଚ କରାଯାଇଛି - ମିଡିଆ ଯାଞ୍ଚ ବିଫଳ ହେଲା + ମିଡ଼ିଆ ଯାଞ୍ଚ କରିବେ କି? + ବଡ଼ ମିଡ଼ିଆ ସଂଗ୍ରହ ସହିତ ଏହା ଏକ ଦୀର୍ଘ ସମୟ ନେଇପାରେ + ମିଡ଼ିଆ ଯାଞ୍ଚ କରୁଛି… + ମିଡ଼ିଆ ଯାଞ୍ଚ କରାଯାଇଛି + ମିଡ଼ିଆ ଯାଞ୍ଚ ବିଫଳ ହେଲା ଅବୈଧ ଏନକୋଡିଂ ସହିତ ଫାଇଲଗୁଡିକ: %d - ମିଡିଆ ଫୋଲ୍ଡରରେ ଫାଇଲଗୁଡିକ କିନ୍ତୁ କୌଣସି ପତ୍ର ଦ୍ୱାରା ବ୍ୟବହୃତ ହୁଏ ନାହିଁ : %d - ପାତ୍ରରେ ବ୍ୟବହୃତ ଫାଇଲଗୁଡ଼ିକ କିନ୍ତୁ ମିଡିଆ ଫୋଲ୍ଡରରେ ନୁହେଁ: %d + ମିଡ଼ିଆ ଫୋଲ୍ଡରରେ ଫାଇଲଗୁଡିକ କିନ୍ତୁ କୌଣସି ପତ୍ର ଦ୍ୱାରା ବ୍ୟବହୃତ ହୁଏ ନାହିଁ : %d + ପାତ୍ରରେ ବ୍ୟବହୃତ ଫାଇଲଗୁଡ଼ିକ କିନ୍ତୁ ମିଡ଼ିଆ ଫୋଲ୍ଡରରେ ନୁହେଁ: %d କୌଣସି ଅବ୍ୟବହୃତ କିମ୍ବା ନିଖୋଜ ଫାଇଲ ମିଳିଲା ନାହିଁ - ମିଡିଆ ଡାଟାବେସ୍ ପୁନଃନିର୍ମିତ + ମିଡ଼ିଆ ଡାଟାବେସ୍ ପୁନଃନିର୍ମିତ ଅବ୍ୟବହୃତ ବିଲୋପ କରନ୍ତୁ - ମିଡିଆ ବିଲୋପ କରୁଛି… + ମିଡ଼ିଆ ବିଲୋପ କରୁଛି… ବିଲୋପ ଫଳାଫଳ %d ଟିଏ ଫାଇଲ୍ ବିଲୋପ ହୋଇଛି | @@ -161,15 +161,15 @@ ବିଲୋପ କରିବାକୁ ପତ୍ର : %d ବିଲୋପିତ ପତ୍ର: %d - ମାଇକ୍ରୋଫୋନ୍ ଅନୁମତି ପାଇପାରିଲା ନାହିଁ - କ୍ୟାମେରା ଅନୁମତି ପାଇପାରିଲା ନାହିଁ | କାର୍ଯ୍ୟକାରିତା ଅକ୍ଷମ ହୋଇଛି | - ମଲ୍ଟିମିଡିଆ ସମ୍ପାଦକ ଖୋଲିବାରେ ବିଫଳ + ମାଇକ୍ରୋଫୋନ୍ ଅନୁମତି ପାଇପାରିଲା ନାହିଁ। + କ୍ୟାମେରା ଅନୁମତି ପାଇପାରିଲା ନାହିଁ। କାର୍ଯ୍ୟକାରିତା ଅକ୍ଷମ ହୋଇଛି। + ମଲ୍ଟିମିଡ଼ିଆ ସମ୍ପାଦକ ଖୋଲିବାରେ ବିଫଳ - ଅଡିଓ ରେକର୍ଡିଂ ଅନୁମତି ପ୍ରତ୍ୟାଖାନ ହେଲା + ଅଡ଼ିଓ ରେକର୍ଡିଂ ଅନୁମତି ପ୍ରତ୍ୟାଖାନ ହେଲା AnkiDroid କୁ ସ୍ୱାଗତ - AnkiDroid ଷ୍ଟୋରେଜ୍ ଅନୁମତି ଆବଶ୍ୟକ କରେ, ଯାହାକୁ ଆମେ ଆପଣଙ୍କର AnkiDroid ସଂଗ୍ରହ, ଫ୍ଲାସକାର୍ଡ ମିଡିଆ ଏବଂ ବ୍ୟାକଅପ୍ ଗଚ୍ଛିତ କରିବା ପାଇଁ ସ୍ୱତନ୍ତ୍ର ଭାବରେ ବ୍ୟବହାର କରୁ | ଆମର କୋଡ୍ ଖୋଲା ଉତ୍ସ ଅଟେ, ସ୍ୱେଚ୍ଛାସେବୀମାନଙ୍କ ଦ୍ୱାରା ଲିଖିତ ଏବଂ ଲକ୍ଷ ଲକ୍ଷ ଲୋକଙ୍କ ଦ୍ୱାରା ବିଶ୍ୱାସ କରାଯାଏ | \n\n ଯଦି ଆପଣଙ୍କର କିଛି ପ୍ରଶ୍ନ ଅଛି, ଦୟାକରି ଆମର ଇନ୍-ଆପ୍ ମାନୁଆଲକୁ ପ୍ରବେଶ କରନ୍ତୁ କିମ୍ବା ଆମର ସମର୍ଥନ ଫୋରମ୍ ପରିଦର୍ଶନ କରନ୍ତୁ | \n\n AnkiDroid ଚେଷ୍ଟା କରିଥିବାରୁ ଧନ୍ୟବାଦ!\n- AnkiDroid ବିକାଶ ଦଳ | - ଉନ୍ନତ ସମ୍ପାଦକ ପ୍ରଦର୍ଶନ କଲାବେଳେ ଅଜଣା ତ୍ରୁଟି ଘଟିଲା + AnkiDroid ଷ୍ଟୋରେଜ୍ ଅନୁମତି ଆବଶ୍ୟକ କରେ, ଯାହାକୁ ଆମେ ଆପଣଙ୍କର AnkiDroid ସଂଗ୍ରହ, ଫ୍ଲାସକାର୍ଡ ମିଡ଼ିଆ ଏବଂ ବ୍ୟାକଅପ୍ ଗଚ୍ଛିତ କରିବା ପାଇଁ ସ୍ୱତନ୍ତ୍ର ଭାବରେ ବ୍ୟବହାର କରୁ | ଆମର କୋଡ୍ ଖୋଲା ଉତ୍ସ ଅଟେ, ସ୍ୱେଚ୍ଛାସେବୀମାନଙ୍କ ଦ୍ୱାରା ଲିଖିତ ଏବଂ ଲକ୍ଷ ଲକ୍ଷ ଲୋକଙ୍କ ଦ୍ୱାରା ବିଶ୍ୱାସ କରାଯାଏ | \n\n ଯଦି ଆପଣଙ୍କର କିଛି ପ୍ରଶ୍ନ ଅଛି, ଦୟାକରି ଆମର ଇନ୍-ଆପ୍ ମାନୁଆଲକୁ ପ୍ରବେଶ କରନ୍ତୁ କିମ୍ବା ଆମର ସମର୍ଥନ ଫୋରମ୍ ପରିଦର୍ଶନ କରନ୍ତୁ | \n\n AnkiDroid ଚେଷ୍ଟା କରିଥିବାରୁ ଧନ୍ୟବାଦ!\n- AnkiDroid ବିକାଶ ଦଳ | + ଉନ୍ନତ ସମ୍ପାଦକ ପ୍ରଦର୍ଶନ କଲାବେଳେ ଅଜଣା ତ୍ରୁଟି ଘଟିଲା AnkiDroid ଅଦ୍ୟତନ ହେଲା AnkiDroid କୁ ନବୀକରଣ କରାଯାଇଛି। ଏହି ଅପଗ୍ରେଡ୍ ସମ୍ଭାବ୍ୟ ଡାଟାବେସ୍ ସମସ୍ୟା ପାଇଁ ସମାଧାନ ଅନ୍ତର୍ଭୂକ୍ତ କରେ, ଏବଂ ଆମେ ବର୍ତ୍ତମାନ ଡାଟାବେସ୍ ଯାଞ୍ଚ କରିବାକୁ ଆପଣଙ୍କୁ ପରାମର୍ଶ ଦେଉ। diff --git a/AnkiDroid/src/main/res/values-or/04-network.xml b/AnkiDroid/src/main/res/values-or/04-network.xml index b55b13f621a6..a59ce5b3226e 100644 --- a/AnkiDroid/src/main/res/values-or/04-network.xml +++ b/AnkiDroid/src/main/res/values-or/04-network.xml @@ -50,7 +50,7 @@ କ୍ଲାଉଡ୍ ସିଙ୍କ୍ ସେବା ବ୍ୟବହାର କରିବାକୁ ଆପଣ ନିଶ୍ଚିତ ଭାବରେ ଏକ ତୃତୀୟ ପକ୍ଷ ଆକାଉଣ୍ଟକୁ ଲଗ୍ ଇନ୍ କରିବେ | ପରବର୍ତ୍ତୀ ସୋପାନରେ ଆପଣ ଗୋଟିଏ ସୃଷ୍ଟି କରିପାରିବେ | ଇମେଲ୍ ଠିକଣା - ପାସୱାର୍ଡ + ପାସ୍ଓ୍ୱାର୍ଡ଼ ଲଗ୍ ଇନ୍ AnkiWeb ଖାତା ନାହିଁ କି? ଏହା ମାଗଣା ଅଟେ! ସୂଚନା: AnkiWeb AnkiDroid ସହିତ ଜଡ଼ିତ ନୁହେଁ @@ -58,8 +58,8 @@ ଭାବେ ଲଗ୍ ଇନ୍ କରିଛନ୍ତି ପ୍ରସ୍ଥାନ କର ଲଗ୍ ହେଉଛି… - ଅବୈଧ ଇମେଲ୍ ଠିକଣା କିମ୍ବା ପାସୱାର୍ଡ - ପାସୱାର୍ଡ ପୁନଃସେଟ୍ କରନ୍ତୁ + ଅବୈଧ ଇମେଲ୍ ଠିକଣା କିମ୍ବା ପାସ୍ଓ୍ୱାର୍ଡ଼ + ପାସ୍ଓ୍ୱାର୍ଡ଼ ପୁନଃସେଟ୍ କରନ୍ତୁ ଅଭିରୁଚି XXX ପାଇଁ ବିକଳ୍ପଗୁଡ଼ିକ @@ -73,7 +73,7 @@ ଅପଲୋଡ୍ କରୁଛି… ଡାଉନଲୋଡ୍ କରୁଛି… ସମକାଳୀନତା - ମିଡିଆ ଡାଉନଲୋଡ୍ %1$d/%2$d… + ମିଡ଼ିଆ ଡାଉନଲୋଡ୍ ହେଉଛି %1$d%2$d… ସ୍ଥାନୀୟ ଠାରୁ ସମ୍ପୂର୍ଣ୍ଣ ସିଙ୍କ୍ ଆପଣଙ୍କର ଘଣ୍ଟା %1$d ସେକେଣ୍ଡ୍ %2$s ଆଗପଛ ଅଛି | ନିଶ୍ଚିତ କରନ୍ତୁ ଯେ ଆପଣଙ୍କ ଫୋନରେ ତାରିଖ, ସମୟ, ଏବଂ ସମୟ ମଣ୍ଡଳ ସଠିକ୍ ଭାବରେ ସେଟ୍ ହୋଇଛି, ତାପରେ ପୁନର୍ବାର ସିଙ୍କ କରନ୍ତୁ | , ସମୟ କ୍ଷେତ୍ର ସମ୍ଭବତ ଭୁଲ୍ ଅଟେ @@ -86,7 +86,7 @@ ସର୍ଭର ବ୍ୟସ୍ତ | ପରେ ପୁନର୍ବାର ଚେଷ୍ଟା କରନ୍ତୁ | ଡାଉନଲୋଡ୍ ହୋଇଥିବା ଡାଟାବେସ୍ ଭ୍ରଷ୍ଟ ହୋଇଛି | ଦୟାକରି ପୁଣିଥରେ ଚେଷ୍ଟା କରନ୍ତୁ। ଡାଉନଲୋଡ୍ ହୋଇଥିବା ଫାଇଲ୍ ଦ୍ୱାରା ଆପଣଙ୍କର ସାମ୍ପ୍ରତିକ ସଂଗ୍ରହ ଓଭର୍ ରାଇଟ୍ ହୋଇପାରିବ ନାହିଁ | - ସଂଯୋଗ ସମୟ ସମାପ୍ତ ହୋଇଛି | ହୁଏତ ଆପଣଙ୍କର ଇଣ୍ଟରନେଟ୍ ସଂଯୋଗ ସମସ୍ୟାର ସମ୍ମୁଖୀନ ହେଉଛି, କିମ୍ବା ଆପଣଙ୍କର ମିଡିଆ ଫୋଲ୍ଡରରେ ଆପଣଙ୍କର ବହୁତ ବଡ ଫାଇଲ୍ ଅଛି | + ସଂଯୋଗ ସମୟ ସମାପ୍ତ ହୋଇଛି | ହୁଏତ ଆପଣଙ୍କର ଇଣ୍ଟରନେଟ୍ ସଂଯୋଗ ସମସ୍ୟାର ସମ୍ମୁଖୀନ ହେଉଛି, କିମ୍ବା ଆପଣଙ୍କର ମିଡ଼ିଆ ଫୋଲ୍ଡରରେ ଆପଣଙ୍କର ବହୁତ ବଡ଼ ଫାଇଲ୍ ଅଛି | ଡାଉନଲୋଡ୍ ହୋଇଥିବା ଫାଇଲ୍ SD କାର୍ଡରେ ସଂରକ୍ଷିତ ହୋଇପାରିବ ନାହିଁ | କାର୍ଡଟି କେବଳ ପଠନୀୟ ଭାବରେ ମାଉଣ୍ଟ ହୋଇଛି କିମ୍ବା ସେଠାରେ ପର୍ଯ୍ୟାପ୍ତ ସ୍ଥାନ ନାହିଁ | ଉନ୍ନତ ସେଟିଂସମୂହରେ କଷ୍ଟମ୍ ସିଙ୍କ ସର୍ଭର ଅବୈଧ (%s) | ଦୟାକରି ଏକ ବୈଧ ସର୍ଭର ପ୍ରବେଶ କରନ୍ତୁ, କିମ୍ବା ସେଟିଂକୁ ଅକ୍ଷମ କରନ୍ତୁ | ବିଲୋପଗୁଡ଼ିକୁ ସିଙ୍କ୍ କରୁଛି… @@ -101,26 +101,26 @@ ଅପଲୋଡ୍ ପୂର୍ବରୁ ଫାଇଲ୍ ଯାଞ୍ଚ କରୁଛି… ଡାଉନଲୋଡ୍ ହୋଇଥିବା ଫାଇଲ୍ ଯାଞ୍ଚ କରୁଛି… ପରିବର୍ତ୍ତିତ ମିଡିଆ ଖୋଜାହଉଛି… - ମିଡିଆ ଫାଇଲରେ କୌଣସି ପରିବର୍ତ୍ତନ ନାହିଁ | - ମିଡିଆ ସିଙ୍କ୍ ହୋଇଛି | - %dଟି ମିଡିଆ ଅପଲୋଡ୍ କରିବା ବାକି ଅଛି | - %d ଟି ମିଡିଆ ଫାଇଲଗୁଡ଼ିକ ଡାଉନଲୋଡ୍ କରିବା ବାକି ଅଛି | + ମିଡ଼ିଆ ଫାଇଲରେ କୌଣସି ପରିବର୍ତ୍ତନ ନାହିଁ | + ମିଡ଼ିଆ ସିଙ୍କ୍ ହୋଇଛି | + %dଟି ମିଡ଼ିଆ ଅପଲୋଡ୍ କରିବା ବାକି ଅଛି | + %d ଟି ମିଡ଼ିଆ ଫାଇଲଗୁଡ଼ିକ ଡାଉନଲୋଡ୍ କରିବା ବାକି ଅଛି | ସିଙ୍କ୍ କରିବା ପରେ, ସଂଗ୍ରହ ଏକ ଅସଙ୍ଗତ ଅବସ୍ଥାରେ ଥିଲା | ଏହି ସମସ୍ୟାର ସମାଧାନ ପାଇଁ, AnkiDroid ଏକ ସମ୍ପୂର୍ଣ୍ଣ ସିଙ୍କ୍କୁ ବାଧ୍ୟ କରିବ | ଆପଣ କେଉଁ ପାର୍ଶ୍ୱକୁ ରଖିବାକୁ ଚାହୁଁଛନ୍ତି ବାଛନ୍ତୁ | ମିଡିଆ ସିଙ୍କ୍ ସମାପ୍ତ ହୋଇଛି, କିନ୍ତୁ ମିଡିଆ ଗଣନା ସର୍ଭର ସହିତ ସହମତ ନୁହେଁ | ଯେ କୌଣସି ତ୍ରୁଟି ସଂଶୋଧନ କରିବାକୁ ପରବର୍ତ୍ତୀ ମିଡିଆ ସିଙ୍କ୍ ପୂର୍ଣ୍ଣ ହେବ | ମିଡିଆ ସିଙ୍କ ବିଫଳ ହେଲା କାରଣ ସାମ୍ପ୍ରତିକ ଡାଟାବେସ୍ ଭ୍ରଷ୍ଟ ହେଇଯାଇଛି | ଏକ ମିଡିଆ ଯାଞ୍ଚ ସୁପାରିଶ କରାଯାଏ | - ମିଡିଆ ତଥ୍ୟ ସିଙ୍କ୍ କରିବାରେ ତ୍ରୁଟି | - ମିଡିଆ ସିଙ୍କ କରିବା ସମୟରେ ତ୍ରୁଟି | ଏକ ମିଡିଆ ଯାଞ୍ଚ ସୁପାରିଶ କରାଯାଏ | + ମିଡ଼ିଆ ତଥ୍ୟ ସିଙ୍କ୍ କରିବାରେ ତ୍ରୁଟି | + ମିଡ଼ିଆ ସିଙ୍କ୍ କରିବା ସମୟରେ ତ୍ରୁଟି | ଏକ ମିଡ଼ିଆ ଯାଞ୍ଚ ସୁପାରିଶ କରାଯାଏ | ସିଙ୍କ୍ କରିବା ପୂର୍ବରୁ ଦୟାକରି ‘%s’ ଚଲାନ୍ତୁ | ସ୍ଥାନୀୟ ସୁଦୂର - ପୂର୍ଣ୍ଣ ସିଙ୍କ୍କୁ ବାଧ୍ୟ - ପରବର୍ତ୍ତୀ ସିଙ୍କରେ, ଗୋଟିଏ ଦିଗରେ ବଳ ପରିବର୍ତ୍ତନ ହୁଏ | + ପୂର୍ଣ୍ଣ ସିଙ୍କ୍ ବାଧ୍ୟ କରନ୍ତୁ + ପରବର୍ତ୍ତୀ ସିଙ୍କ୍ ରେ, ଗୋଟିଏ ଦିଗରେ ପରିବର୍ତ୍ତନ କରିବାକୁ ବାଧ୍ୟ କରନ୍ତୁ ଯେତେବେଳେ ଆପଣ ଆପଣଙ୍କର ସଂଗ୍ରହକୁ ପରବର୍ତ୍ତୀ ସମୟରେ ସମକାଳୀନ କରିବେ ସେତେବେଳେ ଅନୁରୋଧ କରାଯାଇଥିବା ପରିବର୍ତ୍ତନ ଡାଟାବେସର ସମ୍ପୂର୍ଣ୍ଣ ଅପଲୋଡ୍ ଆବଶ୍ୟକ କରିବ | ଯଦି ଆପଣଙ୍କର ସମୀକ୍ଷା କିମ୍ବା ଅନ୍ୟାନ୍ୟ ପରିବର୍ତ୍ତନ ଅନ୍ୟ ଡିଭାଇସରେ ଅପେକ୍ଷା କରିଛି ଯାହା ଏପର୍ଯ୍ୟନ୍ତ ଏଠାରେ ସମକାଳୀନ ହୋଇନାହିଁ, ସେଗୁଡିକ ହଜିଯିବ | ଜାରି ରଖନ୍ତୁ? AnkiDroid ର ପୂର୍ବରୁ ସ୍ଥାପିତ ସଂସ୍କରଣରେ ଏକ ତ୍ରୁଟି ହେତୁ, ଏହାକୁ ସମ୍ପୂର୍ଣ୍ଣ ସିଙ୍କ କରିବାକୁ ବାଧ୍ୟ କରିବାକୁ ପରାମର୍ଶ ଦିଆଯାଇଛି | ପ୍ରକ୍ସି ପ୍ରାମାଣିକିକରଣ ଆବଶ୍ୟକ | ଏକ ସମୟରେ କେବଳ ଜଣେ ଗ୍ରାହକ AnkiWeb କୁ ପ୍ରବେଶ କରିପାରିବେ | ଯଦି ପୂର୍ବ ସିଙ୍କ ବିଫଳ ହେଲା, ଦୟାକରି କିଛି ମିନିଟରେ ପୁନର୍ବାର ଚେଷ୍ଟା କରନ୍ତୁ | - ଆପଣଙ୍କର ସଂଗ୍ରହ କିମ୍ବା ଏକ ମିଡିଆ ଫାଇଲ୍ ସିଙ୍କ୍ କରିବାକୁ ବହୁତ ବଡ ଅଟେ | + ଆପଣଙ୍କ ସଂଗ୍ରହ କିମ୍ୱା ଏକ ମିଡ଼ିଆ ଫାଇଲ୍ ସିଙ୍କ୍ କରିବାକୁ ବହୁତ ବଡ଼ ଅଟେ। AnkiWeb ଏକ ତ୍ରୁଟିର ସମ୍ମୁଖୀନ ହେଲା | ଦୟାକରି କିଛି ମିନିଟରେ ପୁନର୍ବାର ଚେଷ୍ଟା କରନ୍ତୁ, ଏବଂ ଯଦି ସମସ୍ୟା ଜାରି ରହେ, ଦୟାକରି ଏକ ତ୍ରୁଟି ରିପୋର୍ଟ ଦାଖଲ କରନ୍ତୁ | ଦୟାକରି AnkiDroid ର ସର୍ବଶେଷ ସଂସ୍କରଣକୁ ଅଦ୍ୟତନ କରନ୍ତୁ | AnkiWeb ରକ୍ଷଣାବେକ୍ଷଣରେ ଅଛି | ଦୟାକରି କିଛି ମିନିଟରେ ପୁନର୍ବାର ଚେଷ୍ଟା କରନ୍ତୁ | diff --git a/AnkiDroid/src/main/res/values-or/06-statistics.xml b/AnkiDroid/src/main/res/values-or/06-statistics.xml index a360ea632fc0..23d465bc726d 100644 --- a/AnkiDroid/src/main/res/values-or/06-statistics.xml +++ b/AnkiDroid/src/main/res/values-or/06-statistics.xml @@ -41,7 +41,7 @@ ଯୁବ ପରିପକ୍ୱ ଯୁବ+ଶିଖୁଛନ୍ତି - ଅଦୃଶ୍ୟ + ଅଣଦେଖା ନିଲମ୍ବିତ ଅଛି ସ୍ଥଗିତ ଅଛି ପୁନଃ ଶିକ୍ଷାଧିନ @@ -87,7 +87,7 @@ ହାରାହାରି: <b>%1$.1f</b> ପତ୍ର/ଦିନ ସମୁଦାୟ: <b>%1$d</b> ଟି ପତ୍ର "ହାରାହାରି ଅନ୍ତରାଳ: " - "ଦୀର୍ଘତମ ଅନ୍ତରାଳ: " + "ଦୀର୍ଘତମ ଅନ୍ତରାଳ: " <b>%1$.1f</b> ବର୍ଷ <b>%1$.1f</b> ମାସ <b>%1$.1f</b> ଦିନ @@ -111,9 +111,9 @@ ପୂର୍ବାନୁମାନ ସମୀକ୍ଷା ଗଣନା ସମୀକ୍ଷା ସମୟ - ଯୋଡା ଯାଇଛି + ଯୋଡ଼ା ଯାଇଛି ଅନ୍ତରାଳ - ଘଣ୍ଟାକ୍ରମେ ଦେଖିବା + ଘଣ୍ଟାକ୍ରମେ ଦେଖିବା ସପ୍ତାହକ୍ରମେ ଦେଖିବା ଉତ୍ତର ବଟନ୍ diff --git a/AnkiDroid/src/main/res/values-or/10-preferences.xml b/AnkiDroid/src/main/res/values-or/10-preferences.xml index 0c3c5cbcd5e0..1dc5452d1130 100644 --- a/AnkiDroid/src/main/res/values-or/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-or/10-preferences.xml @@ -76,7 +76,7 @@ ଉତ୍ତର ବଟନ ଆକାର Card browser font scaling କାର୍ଡ ବ୍ରାଉଜରରେ ଫାଇଲ ନାମ ଗୁଡ଼ିକୁ ଦର୍ଶାନ୍ତୁ - କାର୍ଡ ବ୍ରାଉଜର ପ୍ରଶ୍ନ/ଉତ୍ତର କ୍ଷେତ୍ରରେ ମିଡିଆ ଫାଇଲ ନାମ ଦର୍ଶାନ୍ତୁ + କାର୍ଡ ବ୍ରାଉଜର ପ୍ରଶ୍ନ/ଉତ୍ତର କ୍ଷେତ୍ରରେ ମିଡ଼ିଆ ଫାଇଲ୍ ନାମ ଦର୍ଶାନ୍ତୁ AnkiDroid directory ପୂର୍ଣ୍ଣଚିତ୍ରପଟ୍ଟ ମୋଡ୍ ବନ୍ଦ @@ -123,8 +123,8 @@ ସ୍ୱାଇପ୍ ସମ୍ବେଦନଶୀଳତା ପାଠ୍ୟ ରୁ କଥନ ଯଦି କୌଣସି ଧ୍ୱନି ଫାଇଲ୍ ଅନ୍ତର୍ଭୂକ୍ତ ହୁଏ ନାହିଁ ତେବେ ଏହା ପ୍ରଶ୍ନ ଏବଂ ଉତ୍ତର ପଢ଼େ - ସିଙ୍କରେ ମିଡିଆ ଆଣ - ସିଙ୍କ୍ କରିବା ସମୟରେ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ନିଖୋଜ ମିଡିଆ ଆଣ + ସିଙ୍କରେ ମିଡ଼ିଆ ଆଣ + ସିଙ୍କ୍ କରିବା ସମୟରେ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ନିଖୋଜ ମିଡ଼ିଆ ଆଣ AnkiWeb ଖାତା ଲଗ୍ ଇନ୍‌ ହୋଇନାହି %s @@ -146,7 +146,7 @@ କମ୍ପନ LED ସୂଚକ ସ୍ୱୟଂଚାଳିତ ଉତ୍ତର ପ୍ରଦର୍ଶନ - ଉପଭୋକ୍ତା ଇନପୁଟ୍ ବିନା ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଉତ୍ତର ଦେଖାନ୍ତୁ | ବିଳମ୍ବ, ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଖୋଲାଯାଇଥିବା ଅଡିଓ ଫାଇଲଗୁଡ଼ିକ ପାଇଁ ସମୟ ଅନ୍ତର୍ଭୁକ୍ତ କରେ + ଉପଭୋକ୍ତା ଇନପୁଟ୍ ବିନା ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଉତ୍ତର ଦେଖାନ୍ତୁ | ବିଳମ୍ବ, ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଖୋଲାଯାଇଥିବା ଅଡ଼ିଓ ଫାଇଲଗୁଡ଼ିକ ପାଇଁ ସମୟ ଅନ୍ତର୍ଭୁକ୍ତ କରେ ଉତ୍ତର ଦେଖାଇବାର ସମୟ ଆସିଗଲା XXX s ପରବର୍ତ୍ତୀ ପ୍ରଶ୍ନ ଦେଖାଇବାର ସମୟ ଆସିଗଲା @@ -211,8 +211,8 @@ ଯଦି ଆପଣ ମାଲିକାନା AnkiWeb ସେବା ବ୍ୟବହାର କରିବାକୁ ଚାହୁଁନାହାଁନ୍ତି ତେବେ ଆପଣ ଏଠାରେ ଏକ ବିକଳ୍ପ ସର୍ଭର ନିର୍ଦ୍ଦିଷ୍ଟ କରିପାରିବେ url ସିଙ୍କ୍ କର ସିଙ୍କ୍ url ଅବୈଧ - ମିଡିଆ ସିଙ୍କ୍ url - ମିଡିଆ ସିଙ୍କ url ଅବୈଧ + ମିଡ଼ିଆ ସିଙ୍କ୍ url + ମିଡ଼ିଆ ସିଙ୍କ୍ url ଅବୈଧ ଅଟେ ତାସଖଣ୍ଡ ଗୋଷ୍ଠୀର ବିକଳ୍ପଗୁଡ଼ିକର ପରିବର୍ତ୍ତନ ଏକାଧିକ ତାସଖଣ୍ଡ ଉପରେ ପ୍ରଭାବ ପକାଇବ | ଯଦି ଆପଣ କେବଳ ସାମ୍ପ୍ରତିକ ତାସଖଣ୍ଡ ପରିବର୍ତ୍ତନ କରିବାକୁ ଚାହୁଁଛନ୍ତି, ଦୟାକରି ପ୍ରଥମେ ଏକ ନୂତନ ବିକଳ୍ପ ଗୋଷ୍ଠୀ ଯୋଡନ୍ତୁ ଟ୍ୟାଗ୍ ଚୟନ କରନ୍ତୁ @@ -254,9 +254,9 @@ ସର୍ବାଧିକ ଉତ୍ତର ସମୟ XXX ସେକେଣ୍ଡ୍ ଉତ୍ତର ଟାଇମର୍ ଦେଖାନ୍ତୁ - ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଅଡିଓ ଚଲାନ୍ତୁ + ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଅଡ଼ିଓ ଚଲାନ୍ତୁ ପ୍ରଶ୍ନର ପୁନରାବୃତ୍ତି କରନ୍ତୁ - ଯେତେବେଳେ ଉତ୍ତର ଦେଖାଯାଏ, ଉଭୟ ପ୍ରଶ୍ନର ଏବଂ ଉତ୍ତର ଅଡିଓର ପୁନରାବୃତ୍ତି କର + ଯେତେବେଳେ ଉତ୍ତର ଦେଖାଯାଏ, ଉଭୟ ପ୍ରଶ୍ନର ଏବଂ ଉତ୍ତର ଅଡ଼ିଓର ପୁନରାବୃତ୍ତି କର ଗୋଷ୍ଠୀ ପରିଚାଳନା ବିକଳ୍ପ ଗୋଷ୍ଠୀ ଯୋଡନ୍ତୁ ଏବଂ ପରିବର୍ତ୍ତନ କରନ୍ତୁ ସାମ୍ପ୍ରତିକ ଗୋଷ୍ଠୀ @@ -291,8 +291,8 @@ ନୋଟ୍ ସମ୍ପାଦକରେ, କାର୍ଡ ସମ୍ପାଦନ କରିବା ସମୟରେ <br> କୁ ନୂତନ ଲାଇନରେ ରୂପାନ୍ତର କରନ୍ତୁ। ସରଳ ଟାଇପ୍ ହୋଇଥିବା ଉତ୍ତର ଫର୍ମାଟିଂ ଟାଇପ୍ ହୋଇଥିବା ଉତ୍ତର ଫଳାଫଳଗୁଡିକରେ ଦେଖାଯାଉଥିବା ‘�’ ଠିକ୍ କରେ | - Focus ‘type in answer’ - Automatically focus the ‘type in answer’ field in the reviewer + ‘ଉତ୍ତର ଟାଇପ୍ କର’ ଉପରେ ଧ୍ୟାନ ଦିଅନ୍ତୁ + ସମୀକ୍ଷକଙ୍କ ମଧ୍ୟରେ ‘ଉତ୍ତର ଟାଇପ୍ କର’ କ୍ଷେତ୍ରକୁ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଧ୍ୟାନ ଦିଅନ୍ତୁ ପରିବର୍ତ୍ତନ ଲଗ୍ ଖୋଲନ୍ତୁ diff --git a/AnkiDroid/src/main/res/values-or/11-arrays.xml b/AnkiDroid/src/main/res/values-or/11-arrays.xml index 6af55dda2cb3..09c691cc36da 100644 --- a/AnkiDroid/src/main/res/values-or/11-arrays.xml +++ b/AnkiDroid/src/main/res/values-or/11-arrays.xml @@ -44,12 +44,12 @@ କେବଳ ଟ୍ୟାଗ୍ କରନ୍ତୁ - ଯାଦୃଚ୍ଛିକ କ୍ରମରେ ନୂତନ କାର୍ଡ + ଅନିୟମିତ କ୍ରମରେ ନୂତନ କାର୍ଡ ଯୋଗ କ୍ରମରେ ନୂତନ କାର୍ଡ ସର୍ବପୁରାତନ ଆଗ - ଯାଦୃଚ୍ଛିକ + ଅନିୟମିତ ବଢୁଥିବା ବ୍ୟବଧାନ କମୁଥିବା ବ୍ୟବଧାନ ସବୁଠୁ ଅଧିକ ତ୍ରୁଟିଜନିତ diff --git a/AnkiDroid/src/main/res/values-or/16-multimedia-editor.xml b/AnkiDroid/src/main/res/values-or/16-multimedia-editor.xml index ceff9140b026..5902c09c5d2e 100644 --- a/AnkiDroid/src/main/res/values-or/16-multimedia-editor.xml +++ b/AnkiDroid/src/main/res/values-or/16-multimedia-editor.xml @@ -35,8 +35,8 @@ ପ୍ରତିଛବି ଯୋଡ଼ନ୍ତୁ - ଅଡିଓ କ୍ଲିପ୍ ଯୋଡ଼ନ୍ତୁ - ଅଡିଓ ରେକର୍ଡ କରନ୍ତୁ + ଅଡ଼ିଓ କ୍ଲିପ୍ ଯୋଡ଼ନ୍ତୁ + ଅଡ଼ିଓ ରେକର୍ଡ କରନ୍ତୁ ଉନ୍ନତ ସମ୍ପାଦକ କ୍ଲୋଜ୍ ଡିଲିଶନ କ୍ଷେତ୍ର ସଫା କରନ୍ତୁ @@ -67,8 +67,8 @@ ଲେଖା ପ୍ରତିଛବି - ଅଡିଓ ରେକର୍ଡିଂ - ଅଡିଓ କ୍ଲିପ୍ + ଅଡ଼ିଓ ରେକର୍ଡିଂ + ଅଡ଼ିଓ କ୍ଲିପ୍ ସମାପ୍ତ @@ -105,7 +105,7 @@ କିଛି ଭୁଲ ହେଲା %1$s କ୍ଷେତ୍ରରେ ମଲ୍ଟିମିଡ଼ିଆ ବିଷୟବସ୍ତୁ ସଂଲଗ୍ନ କରନ୍ତୁ - ମିଡିଆ ସଂଲଗ୍ନ କରନ୍ତୁ + ମିଡ଼ିଆ ସଂଲଗ୍ନ କରନ୍ତୁ ରେକର୍ଡିଂ ବିଫଳ ହେଲା diff --git a/AnkiDroid/src/main/res/values-pa/01-core.xml b/AnkiDroid/src/main/res/values-pa/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-pa/01-core.xml +++ b/AnkiDroid/src/main/res/values-pa/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-pl/01-core.xml b/AnkiDroid/src/main/res/values-pl/01-core.xml index bfcb423958d8..aed5213d4db9 100644 --- a/AnkiDroid/src/main/res/values-pl/01-core.xml +++ b/AnkiDroid/src/main/res/values-pl/01-core.xml @@ -126,6 +126,7 @@ Edytuj etykiety Jesteś pewny, że chcesz usunąć tę notatkę i wszystkie jej karty?\n%s Zaznacz tekst + Lookup text Wyszukiwanie w %1$s Wyróżnij notatkę Cofnij wyróżnienie notatki @@ -164,7 +165,7 @@ Sesja nauki niestandardowej Najpierw zmień nazwę istniejącej talii nauki niestandardowej. Ta talia jest pusta - Deck Search + Przeszukaj talię Dodaj kartę Nieprawidłowa nazwa talii Talia jest pusta. Wybierz przycisk + żeby dodać karty. diff --git a/AnkiDroid/src/main/res/values-pl/02-strings.xml b/AnkiDroid/src/main/res/values-pl/02-strings.xml index 21202750fc4c..8c59f1c0e03a 100644 --- a/AnkiDroid/src/main/res/values-pl/02-strings.xml +++ b/AnkiDroid/src/main/res/values-pl/02-strings.xml @@ -122,7 +122,7 @@ Zmiana kolejności kart... %1$s (z “%2$s”) Wszystkie nieprzypisane karty zostaną usunięte. Jeśli notatka nie zawiera już kart, również zostanie usunięta. Czy na pewno chcesz kontynuować? - Timebox reached + Osiągnięto limit czasu %1$d karta przerobiona w %2$s %1$d karty przerobione w %2$s @@ -374,5 +374,5 @@ Adres e-mail jest wymagany Hasło jest wymagane - Press back again to exit + Naciśnij ponownie, aby wyjść diff --git a/AnkiDroid/src/main/res/values-pl/03-dialogs.xml b/AnkiDroid/src/main/res/values-pl/03-dialogs.xml index c9214875680d..f6436bd858dd 100644 --- a/AnkiDroid/src/main/res/values-pl/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-pl/03-dialogs.xml @@ -242,7 +242,7 @@ Discord Facebook Twitter - Send troubleshooting report + Wyślij raport błędu Raport został już wysłany diff --git a/AnkiDroid/src/main/res/values-pl/10-preferences.xml b/AnkiDroid/src/main/res/values-pl/10-preferences.xml index 4d1d35f3ff3b..582ed78fd388 100644 --- a/AnkiDroid/src/main/res/values-pl/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-pl/10-preferences.xml @@ -151,8 +151,8 @@ XXX s Czas do pokazania kolejnego pytania Wybór języka - Disable card hardware render - Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. + Wyłącz renderowanie sprzętowe kart + Renderowanie sprzętowe jest szybsze, lecz mogą pojawić się problemy, zwłaszcza na Androidzie 8/8.1. Jeżeli nie widzisz części interfejsu przeglądarki, spróbuj tego ustawienia. Tryb bezpiecznego wyświetlania Wyłącz wszystkie animacje i użyj bezpieczniejszej metody rysowania kart. Urządzenia E-ink mogą tego wymagać. Wyłącz tryb edycji pojedynczego pola @@ -175,7 +175,7 @@ Ograniczenie czasu XXX min Obsługa nowej strefy czasowej - Start of next day (0 is midnight, 23 is 11PM) + Początek nowego dnia XXX godzin po północy Eksperymentalny planista V2 Włącz eksperymentalny program planujący. Wymusza zmiany w jednym kierunku przy następnej synchronizacji @@ -295,8 +295,8 @@ W Edytorze Notatek zmień wszelkie przykłady <br> na nowy wiersz kiedy karta jest edytowana. Proste formatowanie wpisywanej odpowiedzi Naprawia ‘􏿾’ pojawiające się we wpisywanych odpowiedziach - Focus ‘type in answer’ - Automatically focus the ‘type in answer’ field in the reviewer + Zaznacz pole \"podaj odpowiedź\" + Automatycznie zaznaczaj pole \"podaj odpowiedź\" w przeglądarce Otwórz listę zmian w programie diff --git a/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml b/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml index 8e3965d64306..054f057e4ac8 100644 --- a/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml +++ b/AnkiDroid/src/main/res/values-pt-rBR/01-core.xml @@ -116,6 +116,7 @@ Editar tags Deseja realmente excluir esta nota e todos seus cartões?\n%s Selecionar texto + Lookup text Pesquisar em %1$s Marcar nota Desmarcar nota diff --git a/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml b/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml index 308dd19451e8..b95e0f5c0e43 100644 --- a/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml +++ b/AnkiDroid/src/main/res/values-pt-rPT/01-core.xml @@ -116,6 +116,7 @@ Edit tags Deseja realmente eliminar esta nota e todas as suas fichas?\n%s Selecionar texto + Lookup text Procurar em %1$s Marcar nota Desmarcar nota diff --git a/AnkiDroid/src/main/res/values-ro/01-core.xml b/AnkiDroid/src/main/res/values-ro/01-core.xml index 11a8e028e35d..c4457f7b7152 100644 --- a/AnkiDroid/src/main/res/values-ro/01-core.xml +++ b/AnkiDroid/src/main/res/values-ro/01-core.xml @@ -121,6 +121,7 @@ Edit tags Chiar doriţi să ştergeţi această notă şi toate cardurile ei?\n%s sale Selectaţi text + Lookup text Căutare în %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ru/01-core.xml b/AnkiDroid/src/main/res/values-ru/01-core.xml index cb09151c1d1a..6308798afc25 100644 --- a/AnkiDroid/src/main/res/values-ru/01-core.xml +++ b/AnkiDroid/src/main/res/values-ru/01-core.xml @@ -126,6 +126,7 @@ Редактировать метки Удалить эту запись и все связанные карточки?\n%s Выделить текст + Lookup text Посмотреть в %1$s Отметить запись Снять отметку @@ -161,7 +162,7 @@ Очистить Создать подколоду Фильтрованная колода очищается… - Дополнительная учебная сессия + Дополнительное занятие Сначала переименуйте существующую дополнительную колоду Эта колода пуста Deck Search diff --git a/AnkiDroid/src/main/res/values-ru/02-strings.xml b/AnkiDroid/src/main/res/values-ru/02-strings.xml index cb9575c017ff..de6979f88740 100644 --- a/AnkiDroid/src/main/res/values-ru/02-strings.xml +++ b/AnkiDroid/src/main/res/values-ru/02-strings.xml @@ -371,7 +371,7 @@ + - - Email address is required - Password is required - Press back again to exit + Требуется адрес электронной почты + Требуется пароль + Нажмите «Назад» ещё раз, чтобы выйти diff --git a/AnkiDroid/src/main/res/values-ru/03-dialogs.xml b/AnkiDroid/src/main/res/values-ru/03-dialogs.xml index e892090830c1..4a30f2d5b51b 100644 --- a/AnkiDroid/src/main/res/values-ru/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-ru/03-dialogs.xml @@ -242,8 +242,8 @@ Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Отправить отчёт о неполадках + Отчёт уже отправлен Автоматическая синхронизация может быть запущена через %d секунду diff --git a/AnkiDroid/src/main/res/values-ru/10-preferences.xml b/AnkiDroid/src/main/res/values-ru/10-preferences.xml index cd5d15cd683f..429e6353f2b2 100644 --- a/AnkiDroid/src/main/res/values-ru/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-ru/10-preferences.xml @@ -151,8 +151,8 @@ XXX с Показать следующий вопрос через Выберите язык - Disable card hardware render - Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. + Отключить аппаратную отрисовку карточек + Аппаратная отрисовка быстрее, но может вызывать проблемы (особенно на Android 8/8.1). Попробуйте отключить её если при повторении пропадают элементы интерфейса. Статический режим экрана Отключает всю анимацию и использует безопасную отрисовку. Может понадобиться для экранов на электронных чернилах Отключить режим одного поля @@ -298,5 +298,5 @@ Focus ‘type in answer’ Automatically focus the ‘type in answer’ field in the reviewer - Open Changelog + Показать список изменений diff --git a/AnkiDroid/src/main/res/values-sat/01-core.xml b/AnkiDroid/src/main/res/values-sat/01-core.xml index cf661605e9b6..fef3f102e505 100644 --- a/AnkiDroid/src/main/res/values-sat/01-core.xml +++ b/AnkiDroid/src/main/res/values-sat/01-core.xml @@ -116,6 +116,7 @@ ᱴᱮᱜᱥ ᱥᱟᱯᱲᱟᱣ ᱱᱚᱛᱴ ᱥᱟᱞᱟᱜ ᱛᱮ ᱡᱷᱚᱛᱚ ᱠᱟᱰ ᱨᱮᱭᱟᱜ ᱡᱤᱱᱥᱚ ᱠᱚ ᱜᱮᱫ ᱜᱤᱰᱤᱭᱟ ᱥᱮ?\n%s ᱚᱞ ᱪᱚᱭᱚᱱ ᱢᱮ + Lookup text %1$s ᱨᱮ ᱧᱮᱞ ᱵᱤᱰᱟᱣ ᱢᱮ ᱠᱷᱟᱴᱚ ᱵᱤᱪᱟᱹᱨ ᱪᱤᱱᱦᱟᱹᱭ ᱢᱮ ᱠᱷᱟᱴᱚ ᱵᱤᱪᱟᱹᱨ ᱚ-ᱪᱤᱱᱦᱟᱹᱭ ᱢᱮ diff --git a/AnkiDroid/src/main/res/values-sk/01-core.xml b/AnkiDroid/src/main/res/values-sk/01-core.xml index f124b9a98ce2..86743da5d420 100644 --- a/AnkiDroid/src/main/res/values-sk/01-core.xml +++ b/AnkiDroid/src/main/res/values-sk/01-core.xml @@ -126,6 +126,7 @@ Editovať štítky Naozaj chcete odstrániť túto poznámku a všetky jej kartičky?\n%s Zvoliť text + Lookup text Vyhľadať v %1$s Označiť poznámku Odznačiť poznámku diff --git a/AnkiDroid/src/main/res/values-sl/01-core.xml b/AnkiDroid/src/main/res/values-sl/01-core.xml index e631d6559fc1..0503a79ebe38 100644 --- a/AnkiDroid/src/main/res/values-sl/01-core.xml +++ b/AnkiDroid/src/main/res/values-sl/01-core.xml @@ -126,6 +126,7 @@ Edit tags Ali res želite izbrisati ta zapisek in vse vezane kartice?\n%s Označi besedilo + Lookup text Poišči v %1$s Označi zapisek Odznači zapisek diff --git a/AnkiDroid/src/main/res/values-sq/01-core.xml b/AnkiDroid/src/main/res/values-sq/01-core.xml index 78e1f7a76cec..fafc57880e01 100644 --- a/AnkiDroid/src/main/res/values-sq/01-core.xml +++ b/AnkiDroid/src/main/res/values-sq/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-sr/01-core.xml b/AnkiDroid/src/main/res/values-sr/01-core.xml index f40c4fd5c174..8deafcf2f103 100644 --- a/AnkiDroid/src/main/res/values-sr/01-core.xml +++ b/AnkiDroid/src/main/res/values-sr/01-core.xml @@ -121,6 +121,7 @@ Edit tags Да ли заисте желите да избришете белешку и све повезане карте?\n%s Означи текст + Lookup text Потражи у %1$s Означи белешку Уклони ознаку белешке diff --git a/AnkiDroid/src/main/res/values-ss/01-core.xml b/AnkiDroid/src/main/res/values-ss/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-ss/01-core.xml +++ b/AnkiDroid/src/main/res/values-ss/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-sv/01-core.xml b/AnkiDroid/src/main/res/values-sv/01-core.xml index 2a8f486d219f..fbffbcfb433b 100644 --- a/AnkiDroid/src/main/res/values-sv/01-core.xml +++ b/AnkiDroid/src/main/res/values-sv/01-core.xml @@ -116,6 +116,7 @@ Redigera taggar Vill du verkligen ta bort den här noten och alla dess kort?\n%s Markera text + Lookup text Sökning i %1$s Markera not Avmarkera not diff --git a/AnkiDroid/src/main/res/values-sw/01-core.xml b/AnkiDroid/src/main/res/values-sw/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-sw/01-core.xml +++ b/AnkiDroid/src/main/res/values-sw/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ta/01-core.xml b/AnkiDroid/src/main/res/values-ta/01-core.xml index 6f54a7220b93..3199b3ac70b1 100644 --- a/AnkiDroid/src/main/res/values-ta/01-core.xml +++ b/AnkiDroid/src/main/res/values-ta/01-core.xml @@ -116,6 +116,7 @@ Edit tags உண்மையில் இந்த குறிப்பையும் அதன் அனைத்து அட்டைகளையும் நீக்கவா?\n%s உரை தேர்ந்தெடு + Lookup text இங்கே பாருங்கள் %1$s குறிப்பு குறிக்கவும் குறிப்பு குறிக்கப்படாது diff --git a/AnkiDroid/src/main/res/values-te/01-core.xml b/AnkiDroid/src/main/res/values-te/01-core.xml index d0063f534efd..f269a2998220 100644 --- a/AnkiDroid/src/main/res/values-te/01-core.xml +++ b/AnkiDroid/src/main/res/values-te/01-core.xml @@ -116,6 +116,7 @@ Edit tags ఈ గమనికను మరియు దాని అన్ని కార్డ్లను నిజంగా తొలగించాలా?\n%s వచనాన్ని ఎంచుకోండి + Lookup text లో చూడండి%1$s గమనికను గుర్తు పెట్టుకోండి గమనిక నుండి గుర్తు తీసివేయండి diff --git a/AnkiDroid/src/main/res/values-tg/01-core.xml b/AnkiDroid/src/main/res/values-tg/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-tg/01-core.xml +++ b/AnkiDroid/src/main/res/values-tg/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-tgl/01-core.xml b/AnkiDroid/src/main/res/values-tgl/01-core.xml index 557f43283716..6546bf034b74 100644 --- a/AnkiDroid/src/main/res/values-tgl/01-core.xml +++ b/AnkiDroid/src/main/res/values-tgl/01-core.xml @@ -126,6 +126,7 @@ Pinagaralang %1$d mga baraha %2$s ngayong araw Edit tags Burahin tagala ang paalalang ito at lahat ng baraha nito?\n%s Pumili ng text + Lookup text Hanapin sa %1$s Markahan ang paalala Tangalin ang marka ng paalala diff --git a/AnkiDroid/src/main/res/values-th/01-core.xml b/AnkiDroid/src/main/res/values-th/01-core.xml index c154eee85aef..ca9dba162d17 100644 --- a/AnkiDroid/src/main/res/values-th/01-core.xml +++ b/AnkiDroid/src/main/res/values-th/01-core.xml @@ -112,6 +112,7 @@ Edit tags คุณต้องการจะลบโน้ตนี้และการ์ดภายในทั้งหมดหรือไม่?\n%s เลือกข้อความ + Lookup text ค้นหาใน %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ti/01-core.xml b/AnkiDroid/src/main/res/values-ti/01-core.xml index 80311d6a979d..cf32af545971 100644 --- a/AnkiDroid/src/main/res/values-ti/01-core.xml +++ b/AnkiDroid/src/main/res/values-ti/01-core.xml @@ -116,6 +116,7 @@ Edit tags ናይባሓቂ ነዚ ጽሑፍ ከምኡ ውን ነዞም ካርድታትን ክትድምስስ ደሊኻ?\n%s ጽሑፍ ምረጽ + Lookup text ኣብ %1$s ርአ ጽሑፍ ምልክት ግበረሉ ምልክት ዝገበርካሉ ጽሑፍ ምልክቱ ደምስስ diff --git a/AnkiDroid/src/main/res/values-tn/01-core.xml b/AnkiDroid/src/main/res/values-tn/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-tn/01-core.xml +++ b/AnkiDroid/src/main/res/values-tn/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-tr/01-core.xml b/AnkiDroid/src/main/res/values-tr/01-core.xml index 5c356b95c637..bb84f803371d 100644 --- a/AnkiDroid/src/main/res/values-tr/01-core.xml +++ b/AnkiDroid/src/main/res/values-tr/01-core.xml @@ -116,6 +116,7 @@ Etiketleri düzenle Bu not ve bütün kartları gerçekten silinsin mi?\n%s Metni seç + Lookup text %1$s\'de Ara Notu işâretle İşâreti kaldır diff --git a/AnkiDroid/src/main/res/values-ts/01-core.xml b/AnkiDroid/src/main/res/values-ts/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-ts/01-core.xml +++ b/AnkiDroid/src/main/res/values-ts/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-tt/01-core.xml b/AnkiDroid/src/main/res/values-tt/01-core.xml index 88d6bd545ecc..d4d1043a02dd 100644 --- a/AnkiDroid/src/main/res/values-tt/01-core.xml +++ b/AnkiDroid/src/main/res/values-tt/01-core.xml @@ -111,6 +111,7 @@ Тегларны үзгәртү Бу язуны һәм бөтен аның кәртләрен бетерергәме?\n%s Текстны сайлау + Lookup text %1$sдә карарга Язуны билгеләү Билгеләүне бетерергә @@ -141,11 +142,11 @@ %.1f ай %.1f ел - Фильтрлы колода үзгәртелә + Rebuilding filtered deck… Яңадан тезү Бушату Колода эчендә колода ясау - Фильтрлы колода бушатыла + Фильтрлы колода бушатыла... Шәхси өйрәнү сессиясе Башта булган артык өйрәнү колода исемен үзгәртегез Бу колода буш diff --git a/AnkiDroid/src/main/res/values-tt/02-strings.xml b/AnkiDroid/src/main/res/values-tt/02-strings.xml index 387ce7242b9a..245b22208487 100644 --- a/AnkiDroid/src/main/res/values-tt/02-strings.xml +++ b/AnkiDroid/src/main/res/values-tt/02-strings.xml @@ -155,13 +155,13 @@ Импорт өчен файл сайлагыз Кушу Бу сезнең хәзерге коллекцияне бетерер hәм аны %s файлы белән алыштырыр - \"%s\" коллекциягә кушыргамы? Бу күп вакыт алырга мөмкин. + \"%s\" коллекциягә кушыргамы? Бу күп вакыт алырга мөмкин Бу эшли торган Anki файлы түгел Бу apkgны ачып булмады: %s apkgны %sга копияләп булмады apkgны тикшереп булмады Пакетны ачу өчен урын җитми. Кирәк: %1$d, бар: %2$d - Файлны импортланганда хата: бәлки, кэш чистартылган иде. Яңадан тырышып карагыз. + Файлны импортланганда хата: бәлки, кэш чистартылган иде.\nЯңадан тырышып карагыз Импортлау уңышлы үтте, чистарту - уңышсыз. Мәгълүмәт базасын тикшерегез. Төп сәбәп: %s Импортлау таләбен эшкәртеп булмый apkg файлы бозык. Зинhар, аны бетерегез hәм яңадан йөкләгез.\n\nТөп сәбәп: %s @@ -197,7 +197,7 @@ Көйләүләр Deck options Study options - Set TTS language + Текст телен көйләргә Тагын өйрәнү Күбрәк Бу - гадәттәгедән тыш күбрәк уку өчен колода. Кәртләрне карап чыккач, алар үзенең колодаларына кире кайтачаклар. Бу колоданы бетерү аның эчендәге кәртләрнең үзенең колодаларына кайтуына китерер. @@ -244,7 +244,7 @@ %2$d cards to review in %1$s - Error sharing apkg file + apkg файлын экспортлаганда хата This file requires a newer version of Anki. @@ -257,7 +257,7 @@ Recovered Cards - Background + Җирлек Choose an Image Restore Default diff --git a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml index abf9ca3c8b50..6fd389191d5c 100644 --- a/AnkiDroid/src/main/res/values-tt/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-tt/03-dialogs.xml @@ -59,17 +59,17 @@ help translate AnkiDroid, update the wiki, provide screenshots, or blog about AnkiDroid.]]> GNU-GPL v3 license and the source code is available on GitHub.]]> - Preview + Карап алу Reposition new card Start position: Only new cards can be repositioned %d cards repositioned - Reset card progress + Прогрессны бетерү This card will be placed at the end of the new card queue - %d cards reset + %d кәрт ясалган @@ -110,14 +110,14 @@ SD card almost full Restore backup? Your collection will be replaced with an older copy. Any unsaved progress will be lost. - Баш тарту + Cancel OK Юк Дәвам итү - Create + Ясарга Бетерү - Disable + Cүндерү Overwrite Бетерү Repair @@ -146,7 +146,7 @@ All cards Яңа - Вакыт + Due Finding empty cards… Do you want to cancel? @@ -178,7 +178,7 @@ Failed to process deck options: %s - Database Locked + Мәгълүматлар базасы бикләнгән The AnkiDroid database is in use by another application. Please close the other application then reopen AnkiDroid. Incompatible Database Version The database is a more advanced version than this version of AnkiDroid can work with. Upgrade AnkiDroid or downgrade the database to open it\n\nSupported version: %1$d\nDatabase version: %2$d\n\nThe following restore options will overwrite your current collection, possibly with a compatible database version: @@ -211,7 +211,7 @@ Mailing List Reddit Report a Bug - Support AnkiDroid + AnkiDroid ярдәме Donate Develop Rate diff --git a/AnkiDroid/src/main/res/values-tt/06-statistics.xml b/AnkiDroid/src/main/res/values-tt/06-statistics.xml index b54584040505..21248b2d211e 100644 --- a/AnkiDroid/src/main/res/values-tt/06-statistics.xml +++ b/AnkiDroid/src/main/res/values-tt/06-statistics.xml @@ -67,7 +67,7 @@ Сәгать Атна көне % correct - Reviews + Кабатлаулар Answer type Again count: <b>%d</b> (<b>%.1f%%</b> дөрес) diff --git a/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml b/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml index d04a3eaa6871..817b1d0a8938 100644 --- a/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml +++ b/AnkiDroid/src/main/res/values-tt/07-cardbrowser.xml @@ -88,16 +88,16 @@ Search all decks Unknown - %d cards deleted + %d кәрт ясалган - Added + Өстәлгән First Review Latest Review Интервал Ease Reviews - Lapses + Ялгышлар Average Time Total Time Card Type diff --git a/AnkiDroid/src/main/res/values-tt/08-widget.xml b/AnkiDroid/src/main/res/values-tt/08-widget.xml index 90d6a2b6b28c..1fc2c8916097 100644 --- a/AnkiDroid/src/main/res/values-tt/08-widget.xml +++ b/AnkiDroid/src/main/res/values-tt/08-widget.xml @@ -39,7 +39,7 @@ --> - Кәрт бүгенгә: + Кәрт бүгенгә %d AnkiDroid кәрте өйрәнелмәгән diff --git a/AnkiDroid/src/main/res/values-uk/01-core.xml b/AnkiDroid/src/main/res/values-uk/01-core.xml index 7b7ad0c73be3..1e869d5c5e75 100644 --- a/AnkiDroid/src/main/res/values-uk/01-core.xml +++ b/AnkiDroid/src/main/res/values-uk/01-core.xml @@ -126,6 +126,7 @@ Редагувати примітки Дійсно видалити цей запис разом з усіма його картками?\n%s Виділити текст + Lookup text Пошук у %1$s Позначити запис Зняти позначку запису diff --git a/AnkiDroid/src/main/res/values-ur/01-core.xml b/AnkiDroid/src/main/res/values-ur/01-core.xml index ad0bfe06a659..d541f11cac9e 100644 --- a/AnkiDroid/src/main/res/values-ur/01-core.xml +++ b/AnkiDroid/src/main/res/values-ur/01-core.xml @@ -116,6 +116,7 @@ Edit tags واقعی یہ نوٹ اور اس کے تمام کارڈز حذف کریں؟ \n%s متن کا انتخاب کریں + Lookup text %1$s میں تلاش کریں نوٹ کو نشان زد کریں نوٹ بے نشان کریں diff --git a/AnkiDroid/src/main/res/values-uz/01-core.xml b/AnkiDroid/src/main/res/values-uz/01-core.xml index 653a32783b29..8dd340219adb 100644 --- a/AnkiDroid/src/main/res/values-uz/01-core.xml +++ b/AnkiDroid/src/main/res/values-uz/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-ve/01-core.xml b/AnkiDroid/src/main/res/values-ve/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-ve/01-core.xml +++ b/AnkiDroid/src/main/res/values-ve/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-vi/01-core.xml b/AnkiDroid/src/main/res/values-vi/01-core.xml index f2859fce345a..b5a5011f0eab 100644 --- a/AnkiDroid/src/main/res/values-vi/01-core.xml +++ b/AnkiDroid/src/main/res/values-vi/01-core.xml @@ -111,6 +111,7 @@ Chỉnh sửa các tag Bạn thực sự muốn xoá ghi chú này cùng toàn bộ thẻ của nó?\n%s Chọn văn bản + Lookup text Tra cứu trong %1$s Đánh dấu ghi chú Bỏ đánh dấu ghi chú @@ -149,7 +150,7 @@ Phiên học tuỳ chọn Đổi tên bộ thẻ học tuỳ chọn hiện hữu trước nhất Bộ thẻ này rỗng - Deck Search + Tìm kiếm bộ thẻ Thêm thẻ Tên bộ thẻ không hợp lệ Bộ thẻ này rỗng. Bấm bút + để thêm nội dung mới. diff --git a/AnkiDroid/src/main/res/values-vi/02-strings.xml b/AnkiDroid/src/main/res/values-vi/02-strings.xml index 018d6a040035..3d58ae528e77 100644 --- a/AnkiDroid/src/main/res/values-vi/02-strings.xml +++ b/AnkiDroid/src/main/res/values-vi/02-strings.xml @@ -122,7 +122,7 @@ Đang sắp sếp lại các thẻ... %1$s (từ \"%2$s\") Bất kỳ thẻ nào không được gán sẽ bị xoá. Nếu một ghi chú không có thẻ nào kèm theo, nó sẽ bị mất. Có chắc là bạn muốn tiếp tục? - Timebox reached + Đã đạt đến giới hạn thời gian Đã học %1$d thẻ trong %2$s @@ -301,7 +301,7 @@ Lỗi hiện thị nội dung thẻ: Không thể mở ‘%s’ Lỗi hiện thị nội dung thẻ: Cần cập nhật media - Loading http resources is no longer supported + Tải tài nguyên http không còn được hỗ trợ Không thể mở bộ thẻ ‘%s’ Tìm kiếm bộ thẻ @@ -333,7 +333,7 @@ + - - Email address is required - Password is required - Press back again to exit + Bắt buộc có địa chỉ email + Bắt buộc có mật khẩu + Nhấn trở lại lần nữa để thoát ứng dụng diff --git a/AnkiDroid/src/main/res/values-vi/03-dialogs.xml b/AnkiDroid/src/main/res/values-vi/03-dialogs.xml index 35c0815299bb..ce39a0bf8804 100644 --- a/AnkiDroid/src/main/res/values-vi/03-dialogs.xml +++ b/AnkiDroid/src/main/res/values-vi/03-dialogs.xml @@ -221,8 +221,8 @@ Discord Facebook Twitter - Send troubleshooting report - Report already submitted + Gửi báo cáo khắc phục sự cố + Báo cáo đã được gửi Đồng bộ hóa tự động có thể được kích hoạt sau %d giây diff --git a/AnkiDroid/src/main/res/values-vi/10-preferences.xml b/AnkiDroid/src/main/res/values-vi/10-preferences.xml index 81b30bd4c361..6688664c8a0a 100644 --- a/AnkiDroid/src/main/res/values-vi/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-vi/10-preferences.xml @@ -151,8 +151,8 @@ XXX s Thời gian để hiển thị câu hỏi tiếp theo Chọn ngôn ngữ - Disable card hardware render - Hardware render is faster but may have problems, specifically on Android 8/8.1. If you cannot see parts of the card review user interface, try this setting. + Tắt kết xuất phần cứng thẻ + Kết xuất phần cứng nhanh hơn nhưng có thể gặp sự cố, cụ thể là trên Android 8 / 8.1. Nếu bạn không thể thấy các phần của giao diện người dùng xem xét thẻ, hãy thử cài đặt này. Chế độ an toàn màn hình Tắt tất cả hoạt ảnh và sử dụng phương pháp an toàn hơn để vẽ thẻ. Các thiết bị E-ink có thể yêu cầu điều này. Tắt chế độ chỉnh sửa trường đơn @@ -174,8 +174,8 @@ Tìm hiểu trước giới hạn Giới hạn thời gian của hộp thời gian XXX phút - New timezone handling - Start of next day (0 is midnight, 23 is 11PM) + Xử lý múi giờ mới + Bắt đầu ngày hôm sau (0 là nửa đêm, 23 là 11 giờ tối) XXX giờ quá nửa đêm Trình lập lịch V2 thử nghiệm Bật công cụ lập lịch thử nghiệm. Buộc các thay đổi theo một hướng trong lần đồng bộ hóa tiếp theo @@ -289,8 +289,8 @@ Trong Trình chỉnh sửa ghi chú, chuyển đổi thẻ <br> thành dòng mới khi chỉnh sửa thẻ. Định dạng câu trả lời đã nhập đơn giản Sửa lỗi ‘􏿾’ xuất hiện trong kết quả câu trả lời đã nhập - Focus ‘type in answer’ - Automatically focus the ‘type in answer’ field in the reviewer + Tập trung \'Nhập câu trả lời\' + Tự động tập trung vào trường \"nhập câu trả lời\" trong trình đánh giá - Open Changelog + Mở Changelog diff --git a/AnkiDroid/src/main/res/values-wo/01-core.xml b/AnkiDroid/src/main/res/values-wo/01-core.xml index 4088b283a29d..0683328796c3 100644 --- a/AnkiDroid/src/main/res/values-wo/01-core.xml +++ b/AnkiDroid/src/main/res/values-wo/01-core.xml @@ -111,6 +111,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-xh/01-core.xml b/AnkiDroid/src/main/res/values-xh/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-xh/01-core.xml +++ b/AnkiDroid/src/main/res/values-xh/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-yue/01-core.xml b/AnkiDroid/src/main/res/values-yue/01-core.xml index ae20a9067871..097066c50fe1 100644 --- a/AnkiDroid/src/main/res/values-yue/01-core.xml +++ b/AnkiDroid/src/main/res/values-yue/01-core.xml @@ -111,6 +111,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note diff --git a/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml b/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml index a73433c3b02d..ffa858828c68 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/01-core.xml @@ -111,6 +111,7 @@ 编辑标签​​​​​ 确定要删除此笔记及生成的卡片?\n%s 选择文本 + Lookup text 在%1$s中查找 标记笔记 取消标记 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 b0cef3cd78b3..64a90d19080d 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/02-strings.xml @@ -122,7 +122,7 @@ 重新排列卡片中... %1$s (从“%2$s”) 在笔记类型中没有相应模板的卡片将被删除。如果该笔记没有生成其他的卡片,则该笔记将会丢失。你确定要继续吗? - Timebox reached + 已到达时间盒 在%2$s内学习了%1$d张卡片 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 9b453f501672..6cfd1b0920fd 100644 --- a/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml +++ b/AnkiDroid/src/main/res/values-zh-rCN/10-preferences.xml @@ -289,8 +289,8 @@ 在笔记编辑器中,编辑卡片时将 <br> 的任何实例转换为换行。 简单输入的答案格式 修复在输入的答案结果中出现的“􏿾” - Focus ‘type in answer’ - Automatically focus the ‘type in answer’ field in the reviewer + 聚焦“输入答案” + 自动聚焦在复习者的“输入答案”字段 打开更新日志 diff --git a/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml b/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml index 5e8d2841fefd..1db6f93faa90 100644 --- a/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml +++ b/AnkiDroid/src/main/res/values-zh-rTW/01-core.xml @@ -112,6 +112,7 @@ 編輯標籤 真的要刪除這個筆記跟它的卡片嗎?\n%s 選擇文字 + Lookup text 在%1$s中查詢 標記筆記 取消標記筆記 diff --git a/AnkiDroid/src/main/res/values-zu/01-core.xml b/AnkiDroid/src/main/res/values-zu/01-core.xml index abd44cdb28e8..816208189f59 100644 --- a/AnkiDroid/src/main/res/values-zu/01-core.xml +++ b/AnkiDroid/src/main/res/values-zu/01-core.xml @@ -116,6 +116,7 @@ Edit tags Really delete this note and all its cards?\n%s Select text + Lookup text Lookup in %1$s Mark note Unmark note From 1a3361725ae3177d6c0ad659cb971381c7710d2a Mon Sep 17 00:00:00 2001 From: Nicola Dardanis Date: Tue, 13 Apr 2021 10:08:54 +0200 Subject: [PATCH 143/171] ankidroid: Add helper methods to UIUtils --- AnkiDroid/src/main/java/com/ichi2/anki/UIUtils.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/UIUtils.java b/AnkiDroid/src/main/java/com/ichi2/anki/UIUtils.java index 1a8e29c1f0ab..be02e1aafe2d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/UIUtils.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/UIUtils.java @@ -7,6 +7,7 @@ import com.google.android.material.snackbar.Snackbar; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; import androidx.core.content.ContextCompat; import android.view.View; import android.widget.TextView; @@ -26,6 +27,12 @@ public class UIUtils { public static void showThemedToast(Context context, String text, boolean shortLength) { Toast.makeText(context, text, shortLength ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show(); } + public static void showThemedToast(Context context, CharSequence text, boolean shortLength) { + UIUtils.showThemedToast(context, text.toString(), shortLength); + } + public static void showThemedToast(Context context, @StringRes int textResource, boolean shortLength) { + Toast.makeText(context, textResource, shortLength ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show(); + } /** From d0d87c2f71c6a9ec4e6107e6c34e4195e96b4fc5 Mon Sep 17 00:00:00 2001 From: Nicola Dardanis Date: Tue, 13 Apr 2021 10:12:42 +0200 Subject: [PATCH 144/171] ankidroid: Replace Toast#makeText with UIUtils#showThemedToast --- .../src/main/java/com/ichi2/anki/AnkiFont.java | 5 +---- .../src/main/java/com/ichi2/anki/DeckPicker.java | 5 ++--- .../main/java/com/ichi2/anki/ModelBrowser.java | 5 +---- .../src/main/java/com/ichi2/anki/Preferences.java | 10 ++++------ .../src/main/java/com/ichi2/anki/ReadText.java | 15 +++++++-------- .../activity/LoadPronounciationActivity.java | 10 +++------- .../activity/TranslationActivity.java | 10 +++------- .../fields/BasicTextFieldController.java | 6 ++---- .../com/ichi2/libanki/sched/AbstractSched.java | 5 ++--- .../ichi2/preferences/CustomDialogPreference.java | 6 ++---- 10 files changed, 27 insertions(+), 50 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFont.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFont.java index ffef0302789a..e1d978ab108c 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFont.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiFont.java @@ -6,8 +6,6 @@ import android.content.res.Resources; import android.graphics.Typeface; -import android.widget.Toast; - import com.ichi2.libanki.Utils; import java.io.File; @@ -158,8 +156,7 @@ public static Typeface getTypeface(Context ctx, String path) { // Show warning toast String name = new File(path).getName(); Resources res = AnkiDroidApp.getAppResources(); - Toast toast = Toast.makeText(ctx, res.getString(R.string.corrupt_font, name), Toast.LENGTH_LONG); - toast.show(); + UIUtils.showThemedToast(ctx, res.getString(R.string.corrupt_font, name), false); // Don't warn again in this session corruptFonts.add(path); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java index 06280a84fc77..726ad7f24d43 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.java @@ -80,7 +80,6 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anki.CollectionHelper.CollectionIntegrityStorageCheck; @@ -562,7 +561,7 @@ void handleStartupFailure(StartupFailure failure) { Timber.i("AnkiDroid directory inaccessible"); Intent i = Preferences.getPreferenceSubscreenIntent(this, "com.ichi2.anki.prefs.advanced"); startActivityForResultWithoutAnimation(i, REQUEST_PATH_UPDATE); - Toast.makeText(this, R.string.directory_inaccessible, Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(this, R.string.directory_inaccessible, false); break; case FUTURE_ANKIDROID_VERSION: Timber.i("Displaying database versioning"); @@ -940,7 +939,7 @@ public void onRequestPermissionsResult (int requestCode, @NonNull String[] permi handleStartup(); } else { // User denied access to file storage so show error toast and display "App Info" - Toast.makeText(this, R.string.startup_no_storage_permission, Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(this, R.string.startup_no_storage_permission, false); finishWithoutAnimation(); // Open the Android settings page for our app so that the user can grant the missing permission Intent intent = new Intent(); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java index b34bd685bfb7..317826079e3a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ModelBrowser.java @@ -35,7 +35,6 @@ import android.widget.ListView; import android.widget.Spinner; import android.widget.TextView; -import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anki.dialogs.ConfirmationDialog; @@ -541,9 +540,7 @@ private String randomizeName(String s) { private void showToast(CharSequence text) { - int duration = Toast.LENGTH_SHORT; - Toast toast = Toast.makeText(this, text, duration); - toast.show(); + UIUtils.showThemedToast(this, text, true); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java index 521913f51ee4..31d3a41a0029 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/Preferences.java @@ -26,7 +26,6 @@ import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; @@ -39,7 +38,6 @@ import android.view.MenuItem; import android.view.WindowManager.BadTokenException; import android.webkit.URLUtil; -import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.ichi2.anim.ActivityTransitionAnimation; @@ -210,8 +208,8 @@ private void initSubscreen(String action, PreferenceContext listener) { if (prefs.getBoolean("gestures", false) || !"2".equals(newValue)) { return true; } else { - Toast.makeText(getApplicationContext(), - R.string.full_screen_error_gestures, Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(getApplicationContext(), + R.string.full_screen_error_gestures, false); return false; } }); @@ -393,12 +391,12 @@ private void initSubscreen(String action, PreferenceContext listener) { fullSyncPreference.setDialogTitle(R.string.force_full_sync_title); fullSyncPreference.setOkHandler(() -> { if (getCol() == null) { - Toast.makeText(getApplicationContext(), R.string.directory_inaccessible, Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(getApplicationContext(), R.string.directory_inaccessible, false); return; } getCol().modSchemaNoCheck(); getCol().setMod(); - Toast.makeText(getApplicationContext(), android.R.string.ok, Toast.LENGTH_SHORT).show(); + UIUtils.showThemedToast(getApplicationContext(), android.R.string.ok, true); }); // Workaround preferences removeUnnecessaryAdvancedPrefs(screen); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java index cbf062d8d88e..fdc4db57a2f8 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ReadText.java @@ -25,7 +25,6 @@ import android.speech.tts.UtteranceProgressListener; import android.view.WindowManager; -import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.google.android.material.snackbar.Snackbar; @@ -59,8 +58,8 @@ public static Sound.SoundSide getmQuestionAnswer() { public static void speak(String text, String loc, int queueMode) { int result = mTts.setLanguage(localeFromStringIgnoringScriptAndExtensions(loc)); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { - Toast.makeText(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message) - + " (" + loc + ")", Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message) + + " (" + loc + ")", false); Timber.e("Error loading locale %s", loc); } else { if (mTts.isSpeaking() && queueMode == TextToSpeech.QUEUE_FLUSH) { @@ -213,8 +212,8 @@ private static void textToSpeech(String text, long did, int ord, Sound.SoundSide if (!originalLocaleCode.isEmpty()) { // (after notifying them first that no TTS voice was found for the locale // they originally requested) - Toast.makeText(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message) - + " (" + originalLocaleCode + ")", Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message) + + " (" + originalLocaleCode + ")", false); } selectTts(mTextToSpeak, mDid, mOrd, mQuestionAnswer); } @@ -277,7 +276,7 @@ public static void initializeTts(Context context, @NonNull ReadTextListener list Timber.d("TTS initialized and available languages found"); ((AbstractFlashcardViewer) mReviewer.get()).ttsInitialized(); } else { - Toast.makeText(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message), Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message), false); Timber.w("TTS initialized but no available languages found"); } mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() { @@ -303,12 +302,12 @@ public void onStart(String arg0) { } }); } else { - Toast.makeText(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message), Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(mReviewer.get(), mReviewer.get().getString(R.string.no_tts_available_message), false); Timber.w("TTS not successfully initialized"); } }); // Show toast that it's getting initialized, as it can take a while before the sound plays the first time - Toast.makeText(context, context.getString(R.string.initializing_tts), Toast.LENGTH_LONG).show(); + UIUtils.showThemedToast(context, context.getString(R.string.initializing_tts), false); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java index 913fff8846b7..a18e02912ae1 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/LoadPronounciationActivity.java @@ -31,9 +31,9 @@ import android.widget.Button; import android.widget.LinearLayout; import android.widget.Spinner; -import android.widget.Toast; import com.ichi2.anki.R; +import com.ichi2.anki.UIUtils; import com.ichi2.anki.multimediacard.beolingus.parsing.BeolingusParser; import com.ichi2.anki.multimediacard.language.LanguageListerBeolingus; import com.ichi2.anki.runtimetools.TaskOperations; @@ -402,16 +402,12 @@ private String computeAddressOfTranslationPage() { private void showToast(CharSequence text) { - int duration = Toast.LENGTH_SHORT; - Toast toast = Toast.makeText(this, text, duration); - toast.show(); + UIUtils.showThemedToast(this, text, true); } private void showToastLong(CharSequence text) { - int duration = Toast.LENGTH_LONG; - Toast toast = Toast.makeText(this, text, duration); - toast.show(); + UIUtils.showThemedToast(this, text, false); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java index a30edb64d95d..92ff0aa45d9e 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/activity/TranslationActivity.java @@ -36,11 +36,11 @@ import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; -import android.widget.Toast; import com.google.gson.Gson; import com.ichi2.anki.AnkiDroidApp; import com.ichi2.anki.R; +import com.ichi2.anki.UIUtils; import com.ichi2.anki.multimediacard.glosbe.json.Meaning; import com.ichi2.anki.multimediacard.glosbe.json.Phrase; import com.ichi2.anki.multimediacard.glosbe.json.Response; @@ -259,9 +259,7 @@ private String gtxt(int id) { private void showToastLong(CharSequence text) { - int duration = Toast.LENGTH_LONG; - Toast toast = Toast.makeText(this, text, duration); - toast.show(); + UIUtils.showThemedToast(this, text, false); } @@ -384,9 +382,7 @@ private void returnFailure(String explanation) { private void showToast(CharSequence text) { - int duration = Toast.LENGTH_SHORT; - Toast toast = Toast.makeText(this, text, duration); - toast.show(); + UIUtils.showThemedToast(this, text, true); } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicTextFieldController.java b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicTextFieldController.java index d84e72d9bdcf..b4094a6c3956 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicTextFieldController.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/multimediacard/fields/BasicTextFieldController.java @@ -30,9 +30,9 @@ import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import android.widget.TextView; -import android.widget.Toast; import com.ichi2.anki.R; +import com.ichi2.anki.UIUtils; import com.ichi2.anki.multimediacard.activity.LoadPronounciationActivity; import com.ichi2.anki.multimediacard.activity.PickStringDialogFragment; import com.ichi2.anki.multimediacard.activity.TranslationActivity; @@ -334,9 +334,7 @@ public void onClick(DialogInterface dialog, int which) { * @param text A short cut to show a toast */ private void showToast(CharSequence text) { - int duration = Toast.LENGTH_SHORT; - Toast toast = Toast.makeText(mActivity, text, duration); - toast.show(); + UIUtils.showThemedToast(mActivity, text, true); } diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.java b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.java index f78a39ac4600..d2bb139fa503 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.java +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/sched/AbstractSched.java @@ -4,10 +4,9 @@ import android.content.Context; import android.content.res.Resources; import android.util.Pair; -import android.widget.Toast; - import com.ichi2.anki.R; +import com.ichi2.anki.UIUtils; import com.ichi2.async.CancelListener; import com.ichi2.libanki.Card; import com.ichi2.libanki.Consts; @@ -484,7 +483,7 @@ protected static void leech(@NonNull Card card, @Nullable Activity activity) { } else { leechMessage = res.getString(R.string.leech_notification); } - activity.runOnUiThread(() -> Toast.makeText(activity, leechMessage, Toast.LENGTH_SHORT).show()); + activity.runOnUiThread(() -> UIUtils.showThemedToast(activity, leechMessage, true)); } else { Timber.w("LeechHook :: could not show leech toast as activity was null"); diff --git a/AnkiDroid/src/main/java/com/ichi2/preferences/CustomDialogPreference.java b/AnkiDroid/src/main/java/com/ichi2/preferences/CustomDialogPreference.java index 32c2bbf6c21e..e5b1f8bf9100 100644 --- a/AnkiDroid/src/main/java/com/ichi2/preferences/CustomDialogPreference.java +++ b/AnkiDroid/src/main/java/com/ichi2/preferences/CustomDialogPreference.java @@ -20,11 +20,11 @@ import android.content.DialogInterface; import android.content.SharedPreferences.Editor; import android.util.AttributeSet; -import android.widget.Toast; import com.ichi2.anki.AnkiDroidApp; import com.ichi2.anki.MetaDB; import com.ichi2.anki.R; +import com.ichi2.anki.UIUtils; @SuppressWarnings("deprecation") // TODO Tracked in https://github.com/ankidroid/Anki-Android/issues/5019 public class CustomDialogPreference extends android.preference.DialogPreference implements DialogInterface.OnClickListener { @@ -57,9 +57,7 @@ public void onClick(DialogInterface dialog, int which) { } else { // Main Preferences :: Reset Languages if (MetaDB.resetLanguages(mContext)) { - Toast successReport = Toast.makeText(this.getContext(), - AnkiDroidApp.getAppResources().getString(R.string.reset_confirmation), Toast.LENGTH_SHORT); - successReport.show(); + UIUtils.showThemedToast(this.getContext(), AnkiDroidApp.getAppResources().getString(R.string.reset_confirmation), true); } } } From f8135927d100f911b9fdd27690155cd8b2b128c0 Mon Sep 17 00:00:00 2001 From: Nicola Dardanis Date: Tue, 13 Apr 2021 10:14:32 +0200 Subject: [PATCH 145/171] lint: Add DirectToastMakeTextUsage rule --- .../com/ichi2/anki/lint/IssueRegistry.java | 2 + .../lint/rules/DirectToastMakeTextUsage.java | 90 ++++++++++++++++++ .../rules/DirectToastMakeTextUsageTest.java | 92 +++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 lint-rules/src/main/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsage.java create mode 100644 lint-rules/src/test/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsageTest.java diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java index 7bb0b1dc83c7..0eea093476a6 100644 --- a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java @@ -7,6 +7,7 @@ import com.ichi2.anki.lint.rules.DirectSystemCurrentTimeMillisUsage; import com.ichi2.anki.lint.rules.DirectDateInstantiation; import com.ichi2.anki.lint.rules.DirectGregorianInstantiation; +import com.ichi2.anki.lint.rules.DirectToastMakeTextUsage; import com.ichi2.anki.lint.rules.DuplicateCrowdInStrings; import com.ichi2.anki.lint.rules.DuplicateTextInPreferencesXml; import com.ichi2.anki.lint.rules.InconsistentAnnotationUsage; @@ -27,6 +28,7 @@ public List getIssues() { issues.add(DirectDateInstantiation.ISSUE); issues.add(DirectGregorianInstantiation.ISSUE); issues.add(DirectSystemTimeInstantiation.ISSUE); + issues.add(DirectToastMakeTextUsage.ISSUE); issues.add(InconsistentAnnotationUsage.ISSUE); issues.add(DuplicateTextInPreferencesXml.ISSUE); issues.add(DuplicateCrowdInStrings.ISSUE); diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsage.java b/lint-rules/src/main/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsage.java new file mode 100644 index 000000000000..afba235d75d4 --- /dev/null +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsage.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021 Nicola Dardanis + * + * 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.lint.rules; + +import com.android.tools.lint.client.api.JavaEvaluator; +import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Implementation; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.JavaContext; +import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.SourceCodeScanner; +import com.google.common.annotations.VisibleForTesting; +import com.ichi2.anki.lint.utils.Constants; +import com.ichi2.anki.lint.utils.LintUtils; +import com.intellij.psi.PsiMethod; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.UCallExpression; +import org.jetbrains.uast.UClass; + +import java.util.ArrayList; +import java.util.List; + +/** + * This custom Lint rules will raise an error if a developer uses the {android.widget.Toast#makeText(...)} method instead + * of using the method provided by the UIUtils class {com.ichi2.anki.UIUtils#showThemedToast(...)}. + */ +public class DirectToastMakeTextUsage extends Detector implements SourceCodeScanner { + + @VisibleForTesting + static final String ID = "DirectToastMakeTextUsage"; + @VisibleForTesting + static final String DESCRIPTION = "Use UIUtils.showThemedToast instead of Toast.makeText"; + private static final String EXPLANATION = "To improve code consistency within the codebase you should use UIUtils.showThemedToast in place" + + " of the library Toast.makeText(...).show(). This ensures also that the toast is actually displayed after being created"; + private static final Implementation implementation = new Implementation(DirectToastMakeTextUsage.class, Scope.JAVA_FILE_SCOPE); + public static final Issue ISSUE = Issue.create( + ID, + DESCRIPTION, + EXPLANATION, + Constants.ANKI_CODE_STYLE_CATEGORY, + Constants.ANKI_CODE_STYLE_PRIORITY, + Constants.ANKI_CODE_STYLE_SEVERITY, + implementation + ); + + public DirectToastMakeTextUsage() { + + } + + @Nullable + @Override + public List getApplicableMethodNames() { + List forbiddenToastMethods = new ArrayList<>(); + forbiddenToastMethods.add("makeText"); + return forbiddenToastMethods; + } + + + @Override + public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) { + super.visitMethodCall(context, node, method); + JavaEvaluator evaluator = context.getEvaluator(); + List foundClasses = context.getUastFile().getClasses(); + if (!LintUtils.isAnAllowedClass(foundClasses, "UIUtils") && evaluator.isMemberInClass(method, "android.widget.Toast")) { + context.report( + ISSUE, + node, + context.getCallLocation(node, true, true), + DESCRIPTION + ); + } + } + +} diff --git a/lint-rules/src/test/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsageTest.java b/lint-rules/src/test/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsageTest.java new file mode 100644 index 000000000000..af07e836b08d --- /dev/null +++ b/lint-rules/src/test/java/com/ichi2/anki/lint/rules/DirectToastMakeTextUsageTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2021 Nicola Dardanis + * + * 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.lint.rules; + +import org.intellij.lang.annotations.Language; +import org.junit.Test; + +import static com.android.tools.lint.checks.infrastructure.TestFile.JavaTestFile.create; +import static com.android.tools.lint.checks.infrastructure.TestLintTask.lint; +import static org.junit.Assert.assertTrue; + +public class DirectToastMakeTextUsageTest { + + @Language("JAVA") + private final String stubToast = " \n" + + "package android.widget; \n" + + "public class Toast { \n" + + " \n" + + " public static Toast makeText(Context context, \n" + + " String text, \n" + + " int duration) { \n" + + " // Stub \n" + + " } \n" + + "} \n"; + + @Language("JAVA") + private final String javaFileToBeTested = " \n" + + "package com.ichi2.anki.lint.rules; \n" + + " \n" + + "import android.widget.Toast; \n" + + " \n" + + "public class TestJavaClass { \n" + + " \n" + + " public static void main(String[] args) { \n" + + " Toast.makeText(); \n" + + " } \n" + + "} \n"; + + @Language("JAVA") + private final String javaFileWithUIUtils = " \n" + + "package com.ichi2.anki.lint.rules; \n" + + " \n" + + "import android.widget.Toast; \n" + + " \n" + + "public class UIUtils { \n" + + " \n" + + " public static void main(String[] args) { \n" + + " Toast.makeText(); \n" + + " } \n" + + "} \n"; + + + @Test + public void showsErrorsForInvalidUsage() { + lint() + .allowMissingSdk() + .allowCompilationErrors() + .files(create(stubToast), create(javaFileToBeTested)) + .issues(DirectToastMakeTextUsage.ISSUE) + .run() + .expectErrorCount(1) + .check(output -> { + assertTrue(output.contains(DirectToastMakeTextUsage.ID)); + assertTrue(output.contains(DirectToastMakeTextUsage.DESCRIPTION)); + }); + } + + @Test + public void allowsUsageForUIUtils() { + lint() + .allowMissingSdk() + .allowCompilationErrors() + .files(create(stubToast), create(javaFileWithUIUtils)) + .issues(DirectToastMakeTextUsage.ISSUE) + .run() + .expectClean(); + } +} From 901260bed656051b9a31f3f1a365d75c513adc0d Mon Sep 17 00:00:00 2001 From: Nicola Dardanis Date: Tue, 13 Apr 2021 10:18:09 +0200 Subject: [PATCH 146/171] lint: Reorder rules lexicographically in IssueRegistry --- .../src/main/java/com/ichi2/anki/lint/IssueRegistry.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java index 0eea093476a6..befa60c744b1 100644 --- a/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java +++ b/lint-rules/src/main/java/com/ichi2/anki/lint/IssueRegistry.java @@ -22,16 +22,17 @@ public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegist @NotNull @Override public List getIssues() { + // Keep this list lexicographically ordered. List issues = new ArrayList<>(); issues.add(DirectCalendarInstanceUsage.ISSUE); - issues.add(DirectSystemCurrentTimeMillisUsage.ISSUE); issues.add(DirectDateInstantiation.ISSUE); issues.add(DirectGregorianInstantiation.ISSUE); + issues.add(DirectSystemCurrentTimeMillisUsage.ISSUE); issues.add(DirectSystemTimeInstantiation.ISSUE); issues.add(DirectToastMakeTextUsage.ISSUE); - issues.add(InconsistentAnnotationUsage.ISSUE); - issues.add(DuplicateTextInPreferencesXml.ISSUE); issues.add(DuplicateCrowdInStrings.ISSUE); + issues.add(DuplicateTextInPreferencesXml.ISSUE); + issues.add(InconsistentAnnotationUsage.ISSUE); issues.add(PrintStackTraceUsage.ISSUE); return issues; } From 3c4f603b87b346dd042dca2d35b4e9d6326febc9 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 14 Apr 2021 02:27:37 +0100 Subject: [PATCH 147/171] fix: AnalyticsConstantsTest listOfConstantFields was initialized multiple times on certain runs This lead to the wrong counts being calculated --- .../analytics/AnalyticsConstantsTest.java | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java index 44b595b73f79..e2f7aed8f342 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java @@ -32,8 +32,38 @@ */ @RunWith(Enclosed.class) public class AnalyticsConstantsTest { - private static final List listOfConstantFields = new ArrayList<>(); - + private final static List listOfConstantFields = new ArrayList<>(); + + static { + listOfConstantFields.add("Opened HelpDialogBox"); + listOfConstantFields.add("Opened Using AnkiDroid"); + listOfConstantFields.add("Opened Get Help"); + listOfConstantFields.add("Opened Support AnkiDroid"); + listOfConstantFields.add("Opened Community"); + listOfConstantFields.add("Opened AnkiDroid Manual"); + listOfConstantFields.add("Opened Anki Manual"); + listOfConstantFields.add("Opened AnkiDroid FAQ"); + listOfConstantFields.add("Opened Mailing List"); + listOfConstantFields.add("Opened Report a Bug"); + listOfConstantFields.add("Opened Donate"); + listOfConstantFields.add("Opened Translate"); + listOfConstantFields.add("Opened Develop"); + listOfConstantFields.add("Opened Rate"); + listOfConstantFields.add("Opened Other"); + listOfConstantFields.add("Opened Send Feedback"); + listOfConstantFields.add("Opened Anki Forums"); + listOfConstantFields.add("Opened Reddit"); + listOfConstantFields.add("Opened Discord"); + listOfConstantFields.add("Opened Facebook"); + listOfConstantFields.add("Opened Twitter"); + listOfConstantFields.add("Exception Report"); + listOfConstantFields.add("aedict"); + listOfConstantFields.add("leo"); + listOfConstantFields.add("colordict"); + listOfConstantFields.add("fora"); + listOfConstantFields.add("nciku"); + listOfConstantFields.add("eijiro"); + } @RunWith(Parameterized.class) public static class AnalyticsConstantsFieldValuesTest { @@ -47,34 +77,6 @@ public AnalyticsConstantsFieldValuesTest(String analyticsString) { @Parameterized.Parameters public static List addAnalyticsConstants() { - listOfConstantFields.add("Opened HelpDialogBox"); - listOfConstantFields.add("Opened Using AnkiDroid"); - listOfConstantFields.add("Opened Get Help"); - listOfConstantFields.add("Opened Support AnkiDroid"); - listOfConstantFields.add("Opened Community"); - listOfConstantFields.add("Opened AnkiDroid Manual"); - listOfConstantFields.add("Opened Anki Manual"); - listOfConstantFields.add("Opened AnkiDroid FAQ"); - listOfConstantFields.add("Opened Mailing List"); - listOfConstantFields.add("Opened Report a Bug"); - listOfConstantFields.add("Opened Donate"); - listOfConstantFields.add("Opened Translate"); - listOfConstantFields.add("Opened Develop"); - listOfConstantFields.add("Opened Rate"); - listOfConstantFields.add("Opened Other"); - listOfConstantFields.add("Opened Send Feedback"); - listOfConstantFields.add("Opened Anki Forums"); - listOfConstantFields.add("Opened Reddit"); - listOfConstantFields.add("Opened Discord"); - listOfConstantFields.add("Opened Facebook"); - listOfConstantFields.add("Opened Twitter"); - listOfConstantFields.add("Exception Report"); - listOfConstantFields.add("aedict"); - listOfConstantFields.add("leo"); - listOfConstantFields.add("colordict"); - listOfConstantFields.add("fora"); - listOfConstantFields.add("nciku"); - listOfConstantFields.add("eijiro"); return listOfConstantFields; } From ddc07dd12e56ad16ab7baa96ffb2f7a2093db881 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Wed, 14 Apr 2021 02:34:52 +0100 Subject: [PATCH 148/171] nf: simplify AnalyticsConstantsTest * Extract stream * Invert if * Extract exception to throws * use .get(null) as it's a static call * new UsageAnalytics.Actions().getClass -> UsageAnalytics.Actions.class * Early return --- .../analytics/AnalyticsConstantsTest.java | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java index e2f7aed8f342..e86c03d6e754 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java +++ b/AnkiDroid/src/test/java/com/ichi2/anki/analytics/AnalyticsConstantsTest.java @@ -22,6 +22,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import androidx.annotation.NonNull; import static org.junit.Assert.assertEquals; @@ -65,6 +69,12 @@ public class AnalyticsConstantsTest { listOfConstantFields.add("eijiro"); } + @NonNull + protected static Stream getAnalyticsConstantFields() { + return Arrays.stream(UsageAnalytics.Actions.class.getDeclaredFields()) + .filter(x -> x.isAnnotationPresent(AnalyticsConstant.class)); + } + @RunWith(Parameterized.class) public static class AnalyticsConstantsFieldValuesTest { private final String analyticsString; @@ -87,29 +97,20 @@ public static List addAnalyticsConstants() { * the test failure. */ @Test - public void checkAnalyticsString() { + public void checkAnalyticsString() throws IllegalAccessException { assertEquals("Re-check if you renamed any string in the analytics string constants of Actions class or AnalyticsConstantsTest.listOfConstantFields. If so, revert them as those string constants must not change as they are compared in analytics.", analyticsString, getStringFromReflection(analyticsString)); } - public String getStringFromReflection(String analyticsStringToBeChecked) { - String reflectedString = null; - UsageAnalytics.Actions actions = new UsageAnalytics.Actions(); - Field[] field; - field = actions.getClass().getDeclaredFields(); - for (Field value : field) { - if (value.isAnnotationPresent(AnalyticsConstant.class)) { - try { - if (value.get(actions).equals(analyticsStringToBeChecked)) { - reflectedString = (String) value.get(actions); - } - } catch (IllegalAccessException e) { - throw new RuntimeException(); - } + public String getStringFromReflection(String analyticsStringToBeChecked) throws IllegalAccessException { + for (Field value : getAnalyticsConstantFields().collect(Collectors.toList())) { + Object reflectedValue = value.get(null); + if (reflectedValue.equals(analyticsStringToBeChecked)) { + return (String) reflectedValue; } } - return reflectedString; + return null; } } @@ -138,9 +139,7 @@ public void fieldSizeEqualsListOfConstantFields() { * class (listOfConstantFields) the test must fail. */ public static long getFieldSize() { - return Arrays.stream(UsageAnalytics.Actions.class.getDeclaredFields()) - .filter(x -> x.isAnnotationPresent(AnalyticsConstant.class)) - .count(); + return getAnalyticsConstantFields().count(); } From af92f54832afeed3aa5b29f9ee969f43b68d18e9 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Wed, 14 Apr 2021 16:57:50 +0000 Subject: [PATCH 149/171] Bumped version to 2.15alpha41 @branch-specific --- AnkiDroid/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 1b1b525b3930..36fafccfea76 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -50,8 +50,8 @@ 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=21500140 - versionName="2.15alpha40" + versionCode=21500141 + versionName="2.15alpha41" minSdkVersion 21 //noinspection OldTargetApi - also performed in api/build.fradle targetSdkVersion 29 // change .travis.yml platform download at same time From bce4afee5fa283ea90b52c5f7e3ba627de3adf60 Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Wed, 14 Apr 2021 10:50:01 -0500 Subject: [PATCH 150/171] Bump recyclerview to 1.2.0, forward-port deprecated APIs Previous `getAdapterPosition()` API was split to be non-ambiguous, and `getBindingAdapterPosition()` seems like the correct option to use between the two new APIs, as the listener appears to be viewed from the adapter context, not the RecyclerView context (where `getAbsoluteAdapterPosition()` would be correct) Additionally, `setHasFixed(true)` must not be used in layouts with wrap_content, as that implies that the content is not fixed - this is a new lint check. Uses of `setHasFixedSize(true)` have been removed in these contexts, with the downside being that RecyclerView cannot do some layout performance optimizations, as it needs to check adapter size now during layout. --- AnkiDroid/build.gradle | 2 +- .../java/com/ichi2/anki/dialogs/DeckSelectionDialog.java | 1 - .../java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java | 1 - .../src/main/java/com/ichi2/anki/dialogs/TagsDialog.java | 1 - AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java | 6 +++--- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 36fafccfea76..aba90d0ee8a1 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -236,7 +236,7 @@ dependencies { implementation 'androidx.fragment:fragment:1.3.2' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' implementation "androidx.preference:preference:1.1.1" - implementation 'androidx.recyclerview:recyclerview:1.1.0' + implementation 'androidx.recyclerview:recyclerview:1.2.0' implementation 'androidx.sqlite:sqlite-framework:2.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.viewpager2:viewpager2:1.0.0' diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java index 78d02058eb84..6544563d5a48 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/DeckSelectionDialog.java @@ -98,7 +98,6 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { RecyclerView recyclerView = dialogView.findViewById(R.id.deck_picker_dialog_list); recyclerView.requestFocus(); - recyclerView.setHasFixedSize(true); RecyclerView.LayoutManager deckLayoutManager = new LinearLayoutManager(requireActivity()); recyclerView.setLayoutManager(deckLayoutManager); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java index 601a1e607f6b..ec59bbb27745 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/LocaleSelectionDialog.java @@ -133,7 +133,6 @@ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { private void setupRecyclerView(@NonNull Activity activity, @NonNull View tagsDialogView, LocaleListAdapter adapter) { RecyclerView recyclerView = tagsDialogView.findViewById(R.id.locale_dialog_selection_list); recyclerView.requestFocus(); - recyclerView.setHasFixedSize(true); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(activity); recyclerView.setLayoutManager(layoutManager); recyclerView.setAdapter(adapter); diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TagsDialog.java b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TagsDialog.java index 831facf2b443..94f1fc9b8635 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TagsDialog.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/dialogs/TagsDialog.java @@ -132,7 +132,6 @@ public Dialog onCreateDialog(Bundle savedInstanceState) { View tagsDialogView = LayoutInflater.from(getActivity()).inflate(R.layout.tags_dialog, null, false); mTagsListRecyclerView = tagsDialogView.findViewById(R.id.tags_dialog_tags_list); mTagsListRecyclerView.requestFocus(); - mTagsListRecyclerView.setHasFixedSize(true); RecyclerView.LayoutManager tagsListLayout = new LinearLayoutManager(getActivity()); mTagsListRecyclerView.setLayoutManager(tagsListLayout); diff --git a/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java b/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java index 72a072b26880..b8bf10862a69 100644 --- a/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java +++ b/AnkiDroid/src/main/java/com/ichi2/ui/ButtonItemAdapter.java @@ -93,7 +93,7 @@ class ButtonVH extends RecyclerView.ViewHolder implements View.OnClickListener { private final ImageButton mButton; private final ButtonItemAdapter mAdapter; - ButtonVH(View itemView, ButtonItemAdapter adapter) { + private ButtonVH(View itemView, ButtonItemAdapter adapter) { super(itemView); mTitle = itemView.findViewById(R.id.card_browser_my_search_name_textview); mButton = itemView.findViewById(R.id.card_browser_my_search_remove_button); @@ -109,10 +109,10 @@ public void onClick(View view) { return; } if (view instanceof ImageButton) { - mAdapter.mButtonCallback.onButtonClicked(mItems.get(getAdapterPosition())); + mAdapter.mButtonCallback.onButtonClicked(mItems.get(getBindingAdapterPosition())); } else { - mAdapter.mItemCallback.onItemClicked(mItems.get(getAdapterPosition())); + mAdapter.mItemCallback.onItemClicked(mItems.get(getBindingAdapterPosition())); } } } From f9925c94438dbc1e98db0d134bc51d588dc65a5f Mon Sep 17 00:00:00 2001 From: Mike Hardy Date: Wed, 14 Apr 2021 12:18:14 -0500 Subject: [PATCH 151/171] Remove Travis CI config, we are 100% GitHub Actions now --- .travis.yml | 163 ---------------------------------------------------- 1 file changed, 163 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5853496867db..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,163 +0,0 @@ -language: bash -# ignored on non-linux platforms, but bionic is required for nested virtualization -dist: bionic - -stages: - - install - - unit_test # custom stage defined in jobs::include section - - test - - finalize_coverage # custom stage defined in jobs::include section - - cache - -env: - global: - - ABI=x86_64 - - ADB_INSTALL_TIMEOUT=8 - - ANDROID_HOME=${HOME}/android-sdk - - ANDROID_TOOLS_URL="https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip" - - EMU_FLAVOR=default # use google_apis flavor if no default flavor emulator - - LOCALE="NONE" - - GRAVIS_REPO="https://github.com/DanySK/Gravis-CI.git" - - GRAVIS="$HOME/gravis" - - JDK="1.8" - - TOOLS=${ANDROID_HOME}/tools - # PATH order is incredibly important. e.g. the 'emulator' script exists in more than one place! - - PATH=${ANDROID_HOME}:${ANDROID_HOME}/emulator:${TOOLS}:${TOOLS}/bin:${ANDROID_HOME}/platform-tools:${PATH} - - UNIT_TEST=FALSE # by default we don't run the unit tests, they are run only in specific builds - - LINT=FALSE # by default we don't lint, they are run only in specific builds - - FINALIZE_COVERAGE=FALSE # by default we don't finalize coverage, it is done in one specific build - - TERM=dumb - matrix: - - API=21 LOCALE="pl_PL" - - API=22 LOCALE="ja_JP" - - API=23 LOCALE="ru_RU" - - API=24 LOCALE="zh_CN" - - API=25 LOCALE="es_ES" - #- API=26 # Fails with unrecognized tests? orchestrator change or something? - #- API=27 LOCALE="fr_FR" # API27 has become very flaky recently, disabling 2020/08/20 - - API=28 # API28 does not handle locale changes for some reason, so it is our default locale test - - API=29 LOCALE="pt_BR" - - API=30 EMU_FLAVOR=google_apis LOCALE="de_DE" # This will be API30 when released, until now it is R, with intermittent failure - - API=24 JDK="1.11" # make sure we work in future dev environments - - API=24 JDK="1.14" # make sure we work in future dev environments - -jobs: - fast_finish: true # Report success without waiting for jobs that allowed to fail. - include: - # The "test" stage is implicit and gets the main matrix. This adds extra stages/jobs - - stage: unit_test - name: "Unit Tests - Windows" - env: UNIT_TEST=TRUE API=NONE - install: skip - os: windows - - stage: unit_test - name: "Unit Tests - macOS" - env: UNIT_TEST=TRUE API=NONE - install: skip - os: osx - - stage: unit_test - name: "Unit Tests - Linux" - env: UNIT_TEST=TRUE API=NONE - install: skip - os: linux - - stage: unit_test - name: "Lint - Release" - env: LINT=Release API=NONE - install: skip - os: linux - - stage: unit_test - name: "Lint - Debug (expected to fail - all errors since last baseline)" - env: LINT=Debug API=NONE - install: skip - os: linux - - stage: finalize_coverage - env: FINALIZE_COVERAGE=TRUE API=NONE - name: "Finalize Codacy Coverage Uploads" - install: skip - script: echo finalize codacy coverage uploads - allow_failures: - - env: API=30 EMU_FLAVOR=google_apis LOCALE="de_DE" # unreleased Android API30 is failing in local builds, let's expose that on Travis - - env: API=24 JDK="1.11" # non-default JDKs should not hold up success reporting - - env: API=24 JDK="1.14" # non-default JDKs should not hold up success reporting - - env: LINT=Debug API=NONE # we want to see the full lint report vs baseline but not fail on it - - env: FINALIZE_COVERAGE=TRUE API=NONE # finalizing coverage should not hold up success reporting - -before_install: - # This section may run on all platforms, and may run for unit tests or for coverage finalization - # It should not make assumptions about os platform or desired tool installation - - # Set up JDK 8 for Android SDK - Java is universally needed: codacy, unit tests, emulators - - travis_retry git clone --depth 1 $GRAVIS_REPO $GRAVIS - - export TARGET_JDK="${JDK}" - - JDK="adopt@1.8" - - source $GRAVIS/install-jdk - - # Set up Android SDK - this is needed everywhere but coverage finalization, so toggle on that - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then travis_retry wget -q "${ANDROID_TOOLS_URL}" -O android-sdk-tools.zip; fi - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then unzip -q android-sdk-tools.zip -d ${ANDROID_HOME}; fi - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then rm android-sdk-tools.zip; fi - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then mkdir ~/.android; fi # avoid harmless sdkmanager warning - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then echo 'count=0' > ~/.android/repositories.cfg; fi # avoid harmless sdkmanager warning - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then yes | travis_retry sdkmanager --licenses >/dev/null; fi # accept all sdkmanager warnings - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then echo y | travis_retry sdkmanager --no_https "platform-tools" >/dev/null; fi - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then echo y | travis_retry sdkmanager --no_https "tools" >/dev/null; fi # A second time per Travis docs, gets latest versions - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then echo y | travis_retry sdkmanager --no_https "build-tools;29.0.2" >/dev/null; fi # Implicit gradle dependency - gradle drives changes - - if [ "$FINALIZE_COVERAGE" = "FALSE" ]; then echo y | travis_retry sdkmanager --no_https "platforms;android-29" >/dev/null; fi # We need the API of the current compileSdkVersion from gradle.properties - -install: - # In our setup, install only runs on matrix entries we want full emulator tests on - # That only happens currently on linux, so this section can assume linux + emulator is desired - # Download required emulator tools - - echo y | travis_retry sdkmanager --no_https "platforms;android-$API" >/dev/null # We need the API of the emulator we will run - - echo y | travis_retry sdkmanager --no_https "emulator" >/dev/null - - echo y | travis_retry sdkmanager --no_https "system-images;android-$API;$EMU_FLAVOR;$ABI" >/dev/null # install our emulator - - # Set up KVM on linux for hardware acceleration. Manually here so it only happens for emulator tests, takes ~30s - - travis_retry sudo -E apt-get -yq --no-install-suggests --no-install-recommends install bridge-utils libpulse0 libvirt-bin qemu-kvm virtinst ubuntu-vm-builder - - sudo adduser $USER libvirt - - sudo adduser $USER kvm - - # Create an Android emulator - - echo no | avdmanager create avd --force -n test -k "system-images;android-$API;$EMU_FLAVOR;$ABI" -c 10M - - | - EMU_PARAMS="-verbose -no-snapshot -no-window -camera-back none -camera-front none -selinux permissive -qemu -m 2048" - EMU_COMMAND="emulator" - # This double "sudo" monstrosity is used to have Travis execute the - # emulator with its new group permissions and help preserve the rule - # of least privilege. - sudo -E sudo -u $USER -E bash -c "${ANDROID_HOME}/emulator/${EMU_COMMAND} -avd test ${AUDIO} ${EMU_PARAMS} &" - - # Wait for emulator to be ready - - ./tools/android-wait-for-emulator.sh - - adb shell input keyevent 82 & - - # Switch locale - - if [ "$LOCALE" != "NONE" ]; then adb shell am broadcast -a com.android.intent.action.SET_LOCALE --es com.android.intent.extra.LOCALE "$LOCALE" com.android.customlocale2; fi - - # Switch back to our target JDK version to build and run tests - - JDK="adopt@${TARGET_JDK}" - - source $GRAVIS/install-jdk - -script: - - if [ "$API" = "NONE" ]; then travis_retry ./gradlew :AnkiDroid:compileReleaseJavaWithJavac compileLint; fi # warm gradle w/travis_retry to handle network blips - - if [ "$UNIT_TEST" = "TRUE" ]; then travis_retry ./gradlew robolectricSdkDownload; fi # pre-download robolectric jars w/travis_retry to handle network blips - - if [ "$UNIT_TEST" = "TRUE" ]; then ./gradlew jacocoUnitTestReport; fi - - if [ "$LINT" != "FALSE" ]; then ./gradlew lint$LINT; fi - - if [ "$API" != "NONE" ]; then ./gradlew jacocoAndroidTestReport; fi - -after_success: - - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then travis_retry bash tools/upload-codacy-report.sh; fi - -#before_cache: -# - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock -# - $GRAVIS/clean-gradle-cache -# -#cache: -# directories: -# - $HOME/.gradle/caches/ -# - $HOME/.gradle/wrapper/ - -notifications: - email: - - github@mikehardy.net - From 717417950cb598a750f7c10d78d2a1ce9058bdb6 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison-1@users.noreply.github.com> Date: Sun, 28 Mar 2021 16:44:17 +0100 Subject: [PATCH 152/171] Project Formatting: default to 'm' prefix https://github.com/ankidroid/Anki-Android/wiki/Code-style#non-public-non-static-field-names-should-start-with-m --- .idea/codeStyles/Project.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index c5b11df4c01f..99b56059e7c0 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -25,6 +25,7 @@ +