Search in sources :

Example 31 with UNDO

use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.

the class CollectionTask method nonTaskUndo.

@VisibleForTesting
public static Card nonTaskUndo(Collection col) {
    AbstractSched sched = col.getSched();
    Card card = col.undo();
    if (card == null) {
        /* multi-card action undone, no action to take here */
        Timber.d("Multi-select undo succeeded");
    } else {
        // cid is actually a card id.
        // a review was undone,
        /* card review undone, set up to review that card again */
        Timber.d("Single card review undo succeeded");
        card.startTimer();
        col.reset();
        sched.deferReset(card);
    }
    return card;
}
Also used : AbstractSched(com.ichi2.libanki.sched.AbstractSched) Card(com.ichi2.libanki.Card) VisibleForTesting(androidx.annotation.VisibleForTesting)

Example 32 with UNDO

use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.

the class Connection method doInBackgroundSync.

/**
 * In the payload, success means that the sync did occur correctly and that a change did occur.
 * So success can be false without error, if no change occurred at all.
 */
private Payload doInBackgroundSync(Payload data) {
    sIsCancellable = true;
    Timber.d("doInBackgroundSync()");
    // Block execution until any previous background task finishes, or timeout after 5s
    boolean ok = TaskManager.waitToFinish(5);
    // Unique key allowing to identify the user to AnkiWeb without password
    String hkey = (String) data.data[0];
    // Whether media should be synced too
    boolean media = (Boolean) data.data[1];
    // If normal sync can't occur, what to do
    ConflictResolution conflictResolution = (ConflictResolution) data.data[2];
    // A number AnkiWeb told us to send back. Probably to choose the best server for the user
    HostNum hostNum = (HostNum) data.data[3];
    // Use safe version that catches exceptions so that full sync is still possible
    Collection col = CollectionHelper.getInstance().getColSafe(AnkiDroidApp.getInstance());
    boolean colCorruptFullSync = false;
    if (!CollectionHelper.getInstance().colIsOpen() || !ok) {
        if (FULL_DOWNLOAD == conflictResolution) {
            colCorruptFullSync = true;
        } else {
            return returnGenericError(data);
        }
    }
    try {
        CollectionHelper.getInstance().lockCollection();
        RemoteServer remoteServer = new RemoteServer(this, hkey, hostNum);
        Syncer client = new Syncer(col, remoteServer, hostNum);
        // run sync and check state
        boolean noChanges = false;
        if (conflictResolution == null) {
            Timber.i("Sync - starting sync");
            publishProgress(R.string.sync_prepare_syncing);
            Pair<ConnectionResultType, Object> ret = client.sync(this);
            data.message = client.getSyncMsg();
            if (ret == null) {
                return returnGenericError(data);
            }
            if (NO_CHANGES != ret.first && SUCCESS != ret.first) {
                data.success = false;
                data.resultType = ret.first;
                data.result = new Object[] { ret.second };
                // Check if there was a sanity check error
                if (SANITY_CHECK_ERROR == ret.first) {
                    // Force full sync next time
                    col.modSchemaNoCheck();
                    col.save();
                }
                return data;
            }
            // save and note success state
            if (NO_CHANGES == ret.first) {
                // publishProgress(R.string.sync_no_changes_message);
                noChanges = true;
            }
        } else {
            try {
                // Disable sync cancellation for full-sync
                sIsCancellable = false;
                FullSyncer fullSyncServer = new FullSyncer(col, hkey, this, hostNum);
                switch(conflictResolution) {
                    case FULL_UPLOAD:
                        {
                            Timber.i("Sync - fullsync - upload collection");
                            publishProgress(R.string.sync_preparing_full_sync_message);
                            Pair<ConnectionResultType, Object[]> ret = fullSyncServer.upload();
                            col.reopen();
                            if (ret == null) {
                                return returnGenericError(data);
                            }
                            if (ret.first == ARBITRARY_STRING && !ret.second[0].equals(HttpSyncer.ANKIWEB_STATUS_OK)) {
                                data.success = false;
                                data.resultType = ret.first;
                                data.result = ret.second;
                                return data;
                            }
                            break;
                        }
                    case FULL_DOWNLOAD:
                        {
                            Timber.i("Sync - fullsync - download collection");
                            publishProgress(R.string.sync_downloading_message);
                            ConnectionResultType ret = fullSyncServer.download();
                            if (ret == null) {
                                Timber.w("Sync - fullsync - unknown error");
                                return returnGenericError(data);
                            }
                            if (SUCCESS == ret) {
                                data.success = true;
                                col.reopen();
                            }
                            if (SUCCESS != ret) {
                                Timber.w("Sync - fullsync - download failed");
                                data.success = false;
                                data.resultType = ret;
                                if (!colCorruptFullSync) {
                                    col.reopen();
                                }
                                return data;
                            }
                            break;
                        }
                    default:
                }
            } catch (OutOfMemoryError e) {
                Timber.w(e);
                AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
                data.success = false;
                data.resultType = OUT_OF_MEMORY_ERROR;
                data.result = new Object[0];
                return data;
            } catch (RuntimeException e) {
                Timber.w(e);
                if (timeoutOccurred(e)) {
                    data.resultType = CONNECTION_ERROR;
                } else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
                    data.resultType = USER_ABORTED_SYNC;
                } else {
                    AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync-fullSync");
                    data.resultType = IO_EXCEPTION;
                }
                data.result = new Object[] { e };
                data.success = false;
                return data;
            }
        }
        // clear undo to avoid non syncing orphans (because undo resets usn too
        if (!noChanges) {
            col.clearUndo();
        }
        // then move on to media sync
        sIsCancellable = true;
        boolean noMediaChanges = false;
        String mediaError = null;
        if (media) {
            RemoteMediaServer mediaServer = new RemoteMediaServer(col, hkey, this, hostNum);
            MediaSyncer mediaClient = new MediaSyncer(col, mediaServer, this);
            Pair<ConnectionResultType, String> ret;
            try {
                Timber.i("Sync - Performing media sync");
                ret = mediaClient.sync();
                if (ret == null || ret.first == null) {
                    mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_error);
                } else {
                    if (CORRUPT == ret.first) {
                        mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_db_error);
                        noMediaChanges = true;
                    }
                    if (NO_CHANGES == ret.first) {
                        publishProgress(R.string.sync_media_no_changes);
                        noMediaChanges = true;
                    }
                    if (MEDIA_SANITY_FAILED == ret.first) {
                        mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_sanity_failed);
                    } else {
                        publishProgress(R.string.sync_media_success);
                    }
                }
            } catch (RuntimeException e) {
                Timber.w(e);
                if (timeoutOccurred(e)) {
                    data.resultType = CONNECTION_ERROR;
                    data.result = new Object[] { e };
                } else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
                    data.resultType = USER_ABORTED_SYNC;
                    data.result = new Object[] { e };
                }
                int downloadedCount = mediaClient.getDownloadCount();
                int uploadedCount = mediaClient.getUploadCount();
                if (downloadedCount == 0 && uploadedCount == 0) {
                    mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_error) + "\n\n" + e.getLocalizedMessage();
                } else {
                    mediaError = AnkiDroidApp.getAppResources().getString(R.string.sync_media_partial_updated, downloadedCount, uploadedCount) + "\n\n" + e.getLocalizedMessage();
                }
            }
        }
        if (noChanges && (!media || noMediaChanges)) {
            // This means that there is no change at all, neither media nor collection. Not that there was an error.
            data.success = false;
            data.resultType = NO_CHANGES;
            data.result = new Object[0];
        } else {
            data.success = true;
            data.data = new Object[] { conflictResolution, col, mediaError };
        }
        return data;
    } catch (MediaSyncException e) {
        Timber.e("Media sync rejected by server");
        data.success = false;
        data.resultType = MEDIA_SYNC_SERVER_ERROR;
        data.result = new Object[] { e };
        AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync");
        return data;
    } catch (UnknownHttpResponseException e) {
        Timber.e(e, "doInBackgroundSync -- unknown response code error");
        data.success = false;
        int code = e.getResponseCode();
        String msg = e.getLocalizedMessage();
        data.resultType = ERROR;
        data.result = new Object[] { code, msg };
        return data;
    } catch (Exception e) {
        // Global error catcher.
        // Try to give a human readable error, otherwise print the raw error message
        Timber.e(e, "doInBackgroundSync error");
        data.success = false;
        if (timeoutOccurred(e)) {
            data.resultType = CONNECTION_ERROR;
            data.result = new Object[] { e };
        } else if (USER_ABORTED_SYNC.toString().equals(e.getMessage())) {
            data.resultType = USER_ABORTED_SYNC;
            data.result = new Object[] { e };
        } else {
            AnkiDroidApp.sendExceptionReport(e, "doInBackgroundSync");
            data.resultType = ARBITRARY_STRING;
            data.result = new Object[] { e.getLocalizedMessage(), e };
        }
        return data;
    } finally {
        Timber.i("Sync Finished - Closing Collection");
        // don't bump mod time unless we explicitly save
        if (col != null) {
            col.close(false);
        }
        CollectionHelper.getInstance().unlockCollection();
    }
}
Also used : ConnectionResultType(com.ichi2.libanki.sync.Syncer.ConnectionResultType) HostNum(com.ichi2.libanki.sync.HostNum) FullSyncer(com.ichi2.libanki.sync.FullSyncer) UnknownHttpResponseException(com.ichi2.anki.exception.UnknownHttpResponseException) CustomSyncServerUrlException(com.ichi2.libanki.sync.CustomSyncServerUrlException) JSONException(com.ichi2.utils.JSONException) MediaSyncException(com.ichi2.anki.exception.MediaSyncException) IOException(java.io.IOException) UnknownHttpResponseException(com.ichi2.anki.exception.UnknownHttpResponseException) HttpSyncer(com.ichi2.libanki.sync.HttpSyncer) FullSyncer(com.ichi2.libanki.sync.FullSyncer) Syncer(com.ichi2.libanki.sync.Syncer) MediaSyncer(com.ichi2.libanki.sync.MediaSyncer) MediaSyncException(com.ichi2.anki.exception.MediaSyncException) Collection(com.ichi2.libanki.Collection) MediaSyncer(com.ichi2.libanki.sync.MediaSyncer) JSONObject(com.ichi2.utils.JSONObject) ConflictResolution(com.ichi2.async.Connection.ConflictResolution) RemoteServer(com.ichi2.libanki.sync.RemoteServer) RemoteMediaServer(com.ichi2.libanki.sync.RemoteMediaServer) Pair(android.util.Pair)

Example 33 with UNDO

use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.

the class CardBrowser method onCreateOptionsMenu.

@Override
public boolean onCreateOptionsMenu(final Menu menu) {
    Timber.d("onCreateOptionsMenu()");
    mActionBarMenu = menu;
    if (!mInMultiSelectMode) {
        // restore drawer click listener and icon
        restoreDrawerIcon();
        getMenuInflater().inflate(R.menu.card_browser, menu);
        mSaveSearchItem = menu.findItem(R.id.action_save_search);
        // the searchview's query always starts empty.
        mSaveSearchItem.setVisible(false);
        mMySearchesItem = menu.findItem(R.id.action_list_my_searches);
        JSONObject savedFiltersObj = getCol().get_config("savedFilters", (JSONObject) null);
        mMySearchesItem.setVisible(savedFiltersObj != null && savedFiltersObj.length() > 0);
        mSearchItem = menu.findItem(R.id.action_search);
        mSearchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {

            @Override
            public boolean onMenuItemActionExpand(MenuItem item) {
                return true;
            }

            @Override
            public boolean onMenuItemActionCollapse(MenuItem item) {
                // SearchView doesn't support empty queries so we always reset the search when collapsing
                mSearchTerms = "";
                mSearchView.setQuery(mSearchTerms, false);
                searchCards();
                // invalidate options menu so that disappeared icons would appear again
                supportInvalidateOptionsMenu();
                mTempSearchQuery = null;
                return true;
            }
        });
        mSearchView = (CardBrowserSearchView) mSearchItem.getActionView();
        mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {

            @Override
            public boolean onQueryTextChange(String newText) {
                if (mSearchView.shouldIgnoreValueChange()) {
                    return true;
                }
                mSaveSearchItem.setVisible(!TextUtils.isEmpty(newText));
                mTempSearchQuery = newText;
                return true;
            }

            @Override
            public boolean onQueryTextSubmit(String query) {
                onSearch();
                mSearchView.clearFocus();
                return true;
            }
        });
        // Fixes #9010 - consistent search after drawer change calls supportInvalidateOptionsMenu (mTempSearchQuery)
        if (!TextUtils.isEmpty(mTempSearchQuery) || !TextUtils.isEmpty(mSearchTerms)) {
            // This calls mSearchView.setOnSearchClickListener
            mSearchItem.expandActionView();
            String toUse = !TextUtils.isEmpty(mTempSearchQuery) ? mTempSearchQuery : mSearchTerms;
            mSearchView.setQuery(toUse, false);
        }
        mSearchView.setOnSearchClickListener(v -> {
            // Provide SearchView with the previous search terms
            mSearchView.setQuery(mSearchTerms, false);
        });
    } else {
        // multi-select mode
        getMenuInflater().inflate(R.menu.card_browser_multiselect, menu);
        showBackIcon();
    }
    if (mActionBarMenu != null && mActionBarMenu.findItem(R.id.action_undo) != null) {
        MenuItem undo = mActionBarMenu.findItem(R.id.action_undo);
        undo.setVisible(getCol().undoAvailable());
        undo.setTitle(getResources().getString(R.string.studyoptions_congrats_undo, getCol().undoName(getResources())));
    }
    // Maybe we were called from ACTION_PROCESS_TEXT.
    // In that case we already fill in the search.
    Intent intent = getIntent();
    Compat compat = CompatHelper.getCompat();
    if (compat.ACTION_PROCESS_TEXT.equals(intent.getAction())) {
        CharSequence search = intent.getCharSequenceExtra(compat.EXTRA_PROCESS_TEXT);
        if (search != null && search.length() != 0) {
            Timber.i("CardBrowser :: Called with search intent: %s", search.toString());
            mSearchView.setQuery(search, true);
            intent.setAction(Intent.ACTION_DEFAULT);
        }
    }
    mPreviewItem = menu.findItem(R.id.action_preview);
    onSelectionChanged();
    updatePreviewMenuItem();
    return super.onCreateOptionsMenu(menu);
}
Also used : JSONObject(com.ichi2.utils.JSONObject) SearchView(androidx.appcompat.widget.SearchView) CardBrowserSearchView(com.ichi2.ui.CardBrowserSearchView) LanguageUtil.getLocaleCompat(com.ichi2.utils.LanguageUtil.getLocaleCompat) Compat(com.ichi2.compat.Compat) MenuItem(android.view.MenuItem) Intent(android.content.Intent)

Example 34 with UNDO

use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.

the class CardBrowser method onOptionsItemSelected.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (getDrawerToggle().onOptionsItemSelected(item)) {
        return true;
    }
    // (when another operation will be performed on the model, it will undo the latest operation)
    if (mUndoSnackbar != null && mUndoSnackbar.isShown())
        mUndoSnackbar.dismiss();
    int itemId = item.getItemId();
    if (itemId == android.R.id.home) {
        endMultiSelectMode();
        return true;
    } else if (itemId == R.id.action_add_note_from_card_browser) {
        addNoteFromCardBrowser();
        return true;
    } else if (itemId == R.id.action_save_search) {
        String searchTerms = mSearchView.getQuery().toString();
        showDialogFragment(CardBrowserMySearchesDialog.newInstance(null, mMySearchesDialogListener, searchTerms, CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_SAVE));
        return true;
    } else if (itemId == R.id.action_list_my_searches) {
        JSONObject savedFiltersObj = getCol().get_config("savedFilters", (JSONObject) null);
        HashMap<String, String> savedFilters;
        if (savedFiltersObj != null) {
            savedFilters = HashUtil.HashMapInit(savedFiltersObj.length());
            for (String searchName : savedFiltersObj) {
                savedFilters.put(searchName, savedFiltersObj.optString(searchName));
            }
        } else {
            savedFilters = HashUtil.HashMapInit(0);
        }
        showDialogFragment(CardBrowserMySearchesDialog.newInstance(savedFilters, mMySearchesDialogListener, "", CardBrowserMySearchesDialog.CARD_BROWSER_MY_SEARCHES_TYPE_LIST));
        return true;
    } else if (itemId == R.id.action_sort_by_size) {
        showDialogFragment(CardBrowserOrderDialog.newInstance(mOrder, mOrderAsc, mOrderDialogListener));
        return true;
    } else if (itemId == R.id.action_show_marked) {
        mSearchTerms = "tag:marked";
        mSearchView.setQuery("", false);
        mSearchView.setQueryHint(getResources().getString(R.string.card_browser_show_marked));
        searchCards();
        return true;
    } else if (itemId == R.id.action_show_suspended) {
        mSearchTerms = "is:suspended";
        mSearchView.setQuery("", false);
        mSearchView.setQueryHint(getResources().getString(R.string.card_browser_show_suspended));
        searchCards();
        return true;
    } else if (itemId == R.id.action_search_by_tag) {
        showFilterByTagsDialog();
        return true;
    } else if (itemId == R.id.action_flag_zero) {
        flagTask(0);
        return true;
    } else if (itemId == R.id.action_flag_one) {
        flagTask(1);
        return true;
    } else if (itemId == R.id.action_flag_two) {
        flagTask(2);
        return true;
    } else if (itemId == R.id.action_flag_three) {
        flagTask(3);
        return true;
    } else if (itemId == R.id.action_flag_four) {
        flagTask(4);
        return true;
    } else if (itemId == R.id.action_flag_five) {
        flagTask(5);
        return true;
    } else if (itemId == R.id.action_flag_six) {
        flagTask(6);
        return true;
    } else if (itemId == R.id.action_flag_seven) {
        flagTask(7);
        return true;
    } else if (itemId == R.id.action_select_flag_zero) {
        selectionWithFlagTask(0);
        return true;
    } else if (itemId == R.id.action_select_flag_one) {
        selectionWithFlagTask(1);
        return true;
    } else if (itemId == R.id.action_select_flag_two) {
        selectionWithFlagTask(2);
        return true;
    } else if (itemId == R.id.action_select_flag_three) {
        selectionWithFlagTask(3);
        return true;
    } else if (itemId == R.id.action_select_flag_four) {
        selectionWithFlagTask(4);
        return true;
    } else if (itemId == R.id.action_select_flag_five) {
        selectionWithFlagTask(5);
        return true;
    } else if (itemId == R.id.action_select_flag_six) {
        selectionWithFlagTask(6);
        return true;
    } else if (itemId == R.id.action_select_flag_seven) {
        selectionWithFlagTask(7);
        return true;
    } else if (itemId == R.id.action_delete_card) {
        deleteSelectedNote();
        return true;
    } else if (itemId == R.id.action_mark_card) {
        toggleMark();
        return true;
    } else if (itemId == R.id.action_suspend_card) {
        TaskManager.launchCollectionTask(new CollectionTask.SuspendCardMulti(getSelectedCardIds()), suspendCardHandler());
        return true;
    } else if (itemId == R.id.action_change_deck) {
        showChangeDeckDialog();
        return true;
    } else if (itemId == R.id.action_undo) {
        Timber.w("CardBrowser:: Undo pressed");
        onUndo();
        return true;
    } else if (itemId == R.id.action_select_none) {
        onSelectNone();
        return true;
    } else if (itemId == R.id.action_select_all) {
        onSelectAll();
        return true;
    } else if (itemId == R.id.action_preview) {
        onPreview();
        return true;
    } else if (itemId == R.id.action_reset_cards_progress) {
        Timber.i("NoteEditor:: Reset progress button pressed");
        onResetProgress();
        return true;
    } else if (itemId == R.id.action_reschedule_cards) {
        Timber.i("CardBrowser:: Reschedule button pressed");
        rescheduleSelectedCards();
        return true;
    } else if (itemId == R.id.action_reposition_cards) {
        Timber.i("CardBrowser:: Reposition button pressed");
        // Only new cards may be repositioned
        List<Long> cardIds = getSelectedCardIds();
        for (long cardId : cardIds) {
            if (getCol().getCard(cardId).getQueue() != Consts.QUEUE_TYPE_NEW) {
                SimpleMessageDialog dialog = SimpleMessageDialog.newInstance(getString(R.string.vague_error), getString(R.string.reposition_card_not_new_error), false);
                showDialogFragment(dialog);
                return false;
            }
        }
        IntegerDialog repositionDialog = new IntegerDialog();
        repositionDialog.setArgs(getString(R.string.reposition_card_dialog_title), getString(R.string.reposition_card_dialog_message), 5);
        repositionDialog.setCallbackRunnable(position -> repositionCardsNoValidation(cardIds, position));
        showDialogFragment(repositionDialog);
        return true;
    } else if (itemId == R.id.action_edit_note) {
        openNoteEditorForCurrentlySelectedNote();
        return super.onOptionsItemSelected(item);
    } else if (itemId == R.id.action_view_card_info) {
        List<Long> selectedCardIds = getSelectedCardIds();
        if (!selectedCardIds.isEmpty()) {
            Intent intent = new Intent(this, CardInfo.class);
            intent.putExtra("cardId", selectedCardIds.get(0));
            startActivityWithAnimation(intent, FADE);
        }
        return true;
    } else if (itemId == R.id.action_edit_tags) {
        showEditTagsDialog();
    }
    return super.onOptionsItemSelected(item);
}
Also used : SimpleMessageDialog(com.ichi2.anki.dialogs.SimpleMessageDialog) JSONObject(com.ichi2.utils.JSONObject) HashMap(java.util.HashMap) IntegerDialog(com.ichi2.anki.dialogs.IntegerDialog) Intent(android.content.Intent) CollectionTask(com.ichi2.async.CollectionTask)

Example 35 with UNDO

use of com.ichi2.async.CollectionTask.TASK_TYPE.UNDO in project Anki-Android by ankidroid.

the class ReviewerTest method testLrnQueueAfterUndo.

@Test
public void testLrnQueueAfterUndo() {
    Collection col = getCol();
    JSONObject nw = col.getDecks().confForDid(1).getJSONObject("new");
    MockTime time = (MockTime) col.getTime();
    nw.put("delays", new JSONArray(new int[] { 1, 10, 60, 120 }));
    Card[] cards = new Card[4];
    cards[0] = addRevNoteUsingBasicModelDueToday("1", "bar").firstCard();
    cards[1] = addNoteUsingBasicModel("2", "bar").firstCard();
    cards[2] = addNoteUsingBasicModel("3", "bar").firstCard();
    waitForAsyncTasksToComplete();
    Reviewer reviewer = startReviewer();
    waitForAsyncTasksToComplete();
    equalFirstField(cards[0], reviewer.mCurrentCard);
    reviewer.answerCard(Consts.BUTTON_ONE);
    waitForAsyncTasksToComplete();
    equalFirstField(cards[1], reviewer.mCurrentCard);
    reviewer.answerCard(Consts.BUTTON_ONE);
    waitForAsyncTasksToComplete();
    undo(reviewer);
    waitForAsyncTasksToComplete();
    equalFirstField(cards[1], reviewer.mCurrentCard);
    reviewer.answerCard(getCol().getSched().getGoodNewButton());
    waitForAsyncTasksToComplete();
    equalFirstField(cards[2], reviewer.mCurrentCard);
    time.addM(2);
    reviewer.answerCard(getCol().getSched().getGoodNewButton());
    advanceRobolectricLooperWithSleep();
    // This failed in #6898 because this card was not in the queue
    equalFirstField(cards[0], reviewer.mCurrentCard);
}
Also used : JSONObject(com.ichi2.utils.JSONObject) JSONArray(com.ichi2.utils.JSONArray) Collection(com.ichi2.libanki.Collection) MockTime(com.ichi2.testutils.MockTime) Card(com.ichi2.libanki.Card) Test(org.junit.Test)

Aggregations

Collection (com.ichi2.libanki.Collection)22 Card (com.ichi2.libanki.Card)15 JSONObject (com.ichi2.utils.JSONObject)13 Intent (android.content.Intent)8 JSONException (com.ichi2.utils.JSONException)7 ArrayList (java.util.ArrayList)7 MenuItem (android.view.MenuItem)6 View (android.view.View)6 VisibleForTesting (androidx.annotation.VisibleForTesting)6 Context (android.content.Context)5 SharedPreferences (android.content.SharedPreferences)5 Bundle (android.os.Bundle)5 Menu (android.view.Menu)5 WindowManager (android.view.WindowManager)5 TextView (android.widget.TextView)5 NonNull (androidx.annotation.NonNull)5 Nullable (androidx.annotation.Nullable)5 SearchView (androidx.appcompat.widget.SearchView)5 MaterialDialog (com.afollestad.materialdialogs.MaterialDialog)5 Snackbar (com.google.android.material.snackbar.Snackbar)5