Search in sources :

Example 6 with Deck

use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.

the class ImportTest method testAnki2Mediadupes.

@Test
public void testAnki2Mediadupes() throws IOException, JSONException, ImportExportException {
    List<String> expected;
    List<String> actual;
    // add a note that references a sound
    Note n = testCol.newNote();
    n.setField(0, "[sound:foo.mp3]");
    long mid = n.model().getLong("id");
    testCol.addNote(n);
    // add that sound to the media folder
    FileOutputStream os;
    os = new FileOutputStream(new File(testCol.getMedia().dir(), "foo.mp3"), false);
    os.write("foo".getBytes());
    os.close();
    testCol.close();
    // it should be imported correctly into an empty deck
    Collection empty = Shared.getEmptyCol(InstrumentationRegistry.getInstrumentation().getTargetContext());
    Importer imp = new Anki2Importer(empty, testCol.getPath());
    imp.run();
    expected = Collections.singletonList("foo.mp3");
    actual = Arrays.asList(new File(empty.getMedia().dir()).list());
    actual.retainAll(expected);
    assertEquals(expected.size(), actual.size());
    // and importing again will not duplicate, as the file content matches
    empty.remCards(empty.getDb().queryLongList("select id from cards"));
    imp = new Anki2Importer(empty, testCol.getPath());
    imp.run();
    expected = Collections.singletonList("foo.mp3");
    actual = Arrays.asList(new File(empty.getMedia().dir()).list());
    actual.retainAll(expected);
    assertEquals(expected.size(), actual.size());
    n = empty.getNote(empty.getDb().queryLongScalar("select id from notes"));
    assertTrue(n.getFields()[0].contains("foo.mp3"));
    // if the local file content is different, and import should trigger a rename
    empty.remCards(empty.getDb().queryLongList("select id from cards"));
    os = new FileOutputStream(new File(empty.getMedia().dir(), "foo.mp3"), false);
    os.write("bar".getBytes());
    os.close();
    imp = new Anki2Importer(empty, testCol.getPath());
    imp.run();
    expected = Arrays.asList("foo.mp3", String.format("foo_%s.mp3", mid));
    actual = Arrays.asList(new File(empty.getMedia().dir()).list());
    actual.retainAll(expected);
    assertEquals(expected.size(), actual.size());
    n = empty.getNote(empty.getDb().queryLongScalar("select id from notes"));
    assertTrue(n.getFields()[0].contains("_"));
    // if the localized media file already exists, we rewrite the note and media
    empty.remCards(empty.getDb().queryLongList("select id from cards"));
    os = new FileOutputStream(new File(empty.getMedia().dir(), "foo.mp3"));
    os.write("bar".getBytes());
    os.close();
    imp = new Anki2Importer(empty, testCol.getPath());
    imp.run();
    expected = Arrays.asList("foo.mp3", String.format("foo_%s.mp3", mid));
    actual = Arrays.asList(new File(empty.getMedia().dir()).list());
    actual.retainAll(expected);
    assertEquals(expected.size(), actual.size());
    n = empty.getNote(empty.getDb().queryLongScalar("select id from notes"));
    assertTrue(n.getFields()[0].contains("_"));
    empty.close();
}
Also used : Anki2Importer(com.ichi2.libanki.importer.Anki2Importer) Note(com.ichi2.libanki.Note) FileOutputStream(java.io.FileOutputStream) Collection(com.ichi2.libanki.Collection) File(java.io.File) Anki2Importer(com.ichi2.libanki.importer.Anki2Importer) Importer(com.ichi2.libanki.importer.Importer) AnkiPackageImporter(com.ichi2.libanki.importer.AnkiPackageImporter) NoteImporter(com.ichi2.libanki.importer.NoteImporter) TextImporter(com.ichi2.libanki.importer.TextImporter) Test(org.junit.Test)

Example 7 with Deck

use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.

the class ImportTest method testDupeIgnore.

/**
 * Custom tests for AnkiDroid.
 */
@Test
public void testDupeIgnore() throws IOException, ImportExportException {
    // create a new empty deck
    String tmp = Shared.getTestFilePath(InstrumentationRegistry.getInstrumentation().getTargetContext(), "update1.apkg");
    AnkiPackageImporter imp = new AnkiPackageImporter(testCol, tmp);
    imp.run();
    tmp = Shared.getTestFilePath(InstrumentationRegistry.getInstrumentation().getTargetContext(), "update3.apkg");
    imp = new AnkiPackageImporter(testCol, tmp);
    imp.run();
    // there is a dupe, but it was ignored
    assertEquals(1, imp.getDupes());
    assertEquals(0, imp.getAdded());
    assertEquals(0, imp.getUpdated());
}
Also used : AnkiPackageImporter(com.ichi2.libanki.importer.AnkiPackageImporter) Test(org.junit.Test)

Example 8 with Deck

use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.

the class ImportTest method testAnki2Updates.

@Test
public void testAnki2Updates() throws IOException, ImportExportException {
    // create a new empty deck
    String tmp = Shared.getTestFilePath(InstrumentationRegistry.getInstrumentation().getTargetContext(), "update1.apkg");
    AnkiPackageImporter imp = new AnkiPackageImporter(testCol, tmp);
    imp.run();
    assertEquals(0, imp.getDupes());
    assertEquals(1, imp.getAdded());
    assertEquals(0, imp.getUpdated());
    // importing again should be idempotent
    imp = new AnkiPackageImporter(testCol, tmp);
    imp.run();
    assertEquals(1, imp.getDupes());
    assertEquals(0, imp.getAdded());
    assertEquals(0, imp.getUpdated());
    // importing a newer note should update
    assertEquals(1, testCol.noteCount());
    assertTrue(testCol.getDb().queryString("select flds from notes").startsWith("hello"));
    tmp = Shared.getTestFilePath(InstrumentationRegistry.getInstrumentation().getTargetContext(), "update2.apkg");
    imp = new AnkiPackageImporter(testCol, tmp);
    imp.run();
    assertEquals(1, imp.getDupes());
    assertEquals(0, imp.getAdded());
    assertEquals(1, imp.getUpdated());
    assertTrue(testCol.getDb().queryString("select flds from notes").startsWith("goodbye"));
}
Also used : AnkiPackageImporter(com.ichi2.libanki.importer.AnkiPackageImporter) Test(org.junit.Test)

Example 9 with Deck

use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.

the class NoteEditor method saveNote.

@VisibleForTesting
void saveNote() {
    final Resources res = getResources();
    if (mSelectedTags == null) {
        mSelectedTags = new ArrayList<>(0);
    }
    // treat add new note and edit existing note independently
    if (mAddNote) {
        // DEFECT: This does not block addition if cloze transpositions are in non-cloze fields.
        if (isClozeType() && !hasClozeDeletions()) {
            displayErrorSavingNote();
            return;
        }
        // load all of the fields into the note
        for (FieldEditText f : mEditFields) {
            updateField(f);
        }
        // Save deck to model
        mEditorNote.model().put("did", mCurrentDid);
        // Save tags to model
        mEditorNote.setTagsFromStr(tagsAsString(mSelectedTags));
        JSONArray tags = new JSONArray();
        for (String t : mSelectedTags) {
            tags.put(t);
        }
        getCol().getModels().current().put("tags", tags);
        getCol().getModels().setChanged();
        mReloadRequired = true;
        CollectionTask.launchCollectionTask(ADD_NOTE, saveNoteHandler(), new TaskData(mEditorNote));
    } else {
        // Check whether note type has been changed
        final Model newModel = getCurrentlySelectedModel();
        final Model oldModel = (mCurrentEditedCard == null) ? null : mCurrentEditedCard.model();
        File target = new File(FileUtil.createTmpDir(this), mCurrentEditedCard.getId() + ".wav");
        if (target.exists()) {
            Timber.i("editing card audio is exists,delete it");
            target.delete();
        }
        if (!newModel.equals(oldModel)) {
            mReloadRequired = true;
            if (mModelChangeCardMap.size() < mEditorNote.numberOfCards() || mModelChangeCardMap.containsValue(null)) {
                // If cards will be lost via the new mapping then show a confirmation dialog before proceeding with the change
                ConfirmationDialog dialog = new ConfirmationDialog();
                dialog.setArgs(res.getString(R.string.confirm_map_cards_to_nothing));
                Runnable confirm = () -> {
                    // Bypass the check once the user confirms
                    changeNoteTypeWithErrorHandling(oldModel, newModel);
                };
                dialog.setConfirm(confirm);
                showDialogFragment(dialog);
            } else {
                // Otherwise go straight to changing note type
                changeNoteTypeWithErrorHandling(oldModel, newModel);
            }
            return;
        }
        // Regular changes in note content
        boolean modified = false;
        // changed did? this has to be done first as remFromDyn() involves a direct write to the database
        if (mCurrentEditedCard != null && mCurrentEditedCard.getDid() != mCurrentDid) {
            mReloadRequired = true;
            // remove card from filtered deck first (if relevant)
            getCol().getSched().remFromDyn(new long[] { mCurrentEditedCard.getId() });
            // refresh the card object to reflect the database changes in remFromDyn()
            mCurrentEditedCard.load();
            // also reload the note object
            mEditorNote = mCurrentEditedCard.note();
            // then set the card ID to the new deck
            mCurrentEditedCard.setDid(mCurrentDid);
            modified = true;
        }
        // now load any changes to the fields from the form
        for (FieldEditText f : mEditFields) {
            modified = modified | updateField(f);
        }
        // added tag?
        for (String t : mSelectedTags) {
            modified = modified || !mEditorNote.hasTag(t);
        }
        // removed tag?
        modified = modified || mEditorNote.getTags().size() > mSelectedTags.size();
        if (modified) {
            mEditorNote.setTagsFromStr(tagsAsString(mSelectedTags));
            mChanged = true;
        }
        closeNoteEditor();
    }
}
Also used : JSONArray(com.ichi2.utils.JSONArray) Model(com.ichi2.libanki.Model) Resources(android.content.res.Resources) File(java.io.File) TaskData(com.ichi2.async.TaskData) ConfirmationDialog(com.ichi2.anki.dialogs.ConfirmationDialog) VisibleForTesting(androidx.annotation.VisibleForTesting)

Example 10 with Deck

use of com.ichi2.libanki.Deck in project AnkiChinaAndroid by ankichinateam.

the class DeckPickerFragment method onCreateView.

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    if (mRoot != null) {
        return mRoot;
    }
    Timber.i("on create view in deck picker fragment");
    mRoot = inflater.inflate(R.layout.deck_picker, container, false);
    mToolbar = mRoot.findViewById(R.id.toolbar);
    mPullToSyncWrapper = mRoot.findViewById(R.id.pull_to_sync_wrapper);
    if (!Permissions.hasStorageAccessPermission(getAnkiActivity())) {
        mPullToSyncWrapper.setVisibility(View.GONE);
        mToolbar.setVisibility(View.GONE);
        mRoot.findViewById(R.id.no_permission_layout).setVisibility(View.VISIBLE);
        mRoot.findViewById(R.id.hint_button).setOnClickListener(v -> ActivityCompat.requestPermissions(getAnkiActivity(), new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, REQUEST_STORAGE_PERMISSION));
    // return mRoot;
    }
    if (mToolbar != null) {
        setHasOptionsMenu(true);
        mToolbar.setNavigationIcon(null);
        mToolbar.setTitle("");
        mTabLayout = mToolbar.findViewById(R.id.tab_layout);
        mVipLogo = mToolbar.findViewById(R.id.vip_logo);
        mVipLogo.setOnClickListener(v -> onNavigationPressed());
        TabLayout.Tab tab = mTabLayout.newTab();
        View view = getLayoutInflater().inflate(R.layout.item_deckpicker_tab, null);
        ((TextView) view.findViewById(R.id.name)).setText("学习");
        tab.setCustomView(view);
        mTabLayout.addTab(tab);
        TabLayout.Tab tab2 = mTabLayout.newTab();
        View view2 = getLayoutInflater().inflate(R.layout.item_deckpicker_tab, null);
        ((TextView) view2.findViewById(R.id.name)).setText("笔记");
        tab2.setCustomView(view2);
        mTabLayout.addTab(tab2);
        mTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {

            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                if (tab == tab2) {
                    getAnkiActivity().openCardBrowser(ALL_DECKS_ID);
                }
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
            }
        });
    }
    // check, if tablet layout
    mStudyoptionsFrame = mRoot.findViewById(R.id.studyoptions_fragment);
    if (mFragmented) {
        loadStudyOptionsFragment(false);
    }
    mRecyclerView = mRoot.findViewById(R.id.files);
    mRecyclerViewLayoutManager = new LinearLayoutManager(getAnkiActivity());
    mRecyclerView.setLayoutManager(mRecyclerViewLayoutManager);
    View view = mFragmented ? mRoot.findViewById(R.id.deckpicker_view) : mRoot.findViewById(R.id.root_layout);
    boolean hasDeckPickerBackground = false;
    try {
        hasDeckPickerBackground = applyDeckPickerBackground(view);
    } catch (OutOfMemoryError e) {
        // 6608 - OOM should be catchable here.
        Timber.w(e, "Failed to apply background - OOM");
        UIUtils.showThemedToast(getAnkiActivity(), getString(R.string.background_image_too_large), false);
    } catch (Exception e) {
        Timber.w(e, "Failed to apply background");
        UIUtils.showThemedToast(getAnkiActivity(), getString(R.string.failed_to_apply_background_image, e.getLocalizedMessage()), false);
    }
    // create and set an adapter for the RecyclerView
    mDeckListAdapter = new DeckAdapter(getLayoutInflater(), getAnkiActivity());
    mDeckListAdapter.setDeckClickListener(mDeckClickListener);
    mDeckListAdapter.setCountsClickListener(mCountsClickListener);
    mDeckListAdapter.setDeckExpanderClickListener(mDeckExpanderClickListener);
    mDeckListAdapter.setDeckLongClickListener(mDeckLongClickListener);
    mDeckListAdapter.enablePartialTransparencyForBackground(hasDeckPickerBackground);
    mDeckListAdapter.setMarketClickListener(v -> getAnkiActivity().openSourceMarket());
    mRecyclerView.setAdapter(mDeckListAdapter);
    mPullToSyncWrapper.setDistanceToTriggerSync(SWIPE_TO_SYNC_TRIGGER_DISTANCE);
    mPullToSyncWrapper.setOnRefreshListener(() -> {
        Timber.i("Pull to Sync: Syncing");
        mPullToSyncWrapper.setRefreshing(false);
        updateDeckList();
    // getAnkiActivity().sync();
    });
    mPullToSyncWrapper.getViewTreeObserver().addOnScrollChangedListener(() -> mPullToSyncWrapper.setEnabled(mRecyclerViewLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));
    // Setup the FloatingActionButtons, should work everywhere with min API >= 15
    mActionsMenu = mRoot.findViewById(R.id.add_content_menu);
    mActionsMenu.findViewById(R.id.fab_expand_menu_button).setContentDescription(getString(R.string.menu_add));
    // configureFloatingActionsMenu();
    mReviewSummaryTextView = mRoot.findViewById(R.id.today_stats_text_view);
    // mRoot.findViewById(R.id.tv_resource).setOnClickListener(v -> getAnkiActivity().openSourceMarket());
    mShortAnimDuration = getResources().getInteger(android.R.integer.config_shortAnimTime);
    fetchAds();
    return mRoot;
}
Also used : DeckAdapter(com.ichi2.anki.widgets.DeckAdapter) TabLayout(com.google.android.material.tabs.TabLayout) TextView(android.widget.TextView) LinearLayoutManager(androidx.recyclerview.widget.LinearLayoutManager) ImageView(android.widget.ImageView) View(android.view.View) RecyclerView(androidx.recyclerview.widget.RecyclerView) TextView(android.widget.TextView) ActionMenuView(android.widget.ActionMenuView) IOException(java.io.IOException) SQLException(android.database.SQLException) Nullable(androidx.annotation.Nullable)

Aggregations

Deck (com.ichi2.libanki.Deck)100 Collection (com.ichi2.libanki.Collection)97 JSONObject (com.ichi2.utils.JSONObject)88 Test (org.junit.Test)80 JSONArray (com.ichi2.utils.JSONArray)55 Card (com.ichi2.libanki.Card)53 Note (com.ichi2.libanki.Note)50 ArrayList (java.util.ArrayList)47 RobolectricTest (com.ichi2.anki.RobolectricTest)44 DeckConfig (com.ichi2.libanki.DeckConfig)37 JSONException (com.ichi2.utils.JSONException)34 NonNull (androidx.annotation.NonNull)30 HashMap (java.util.HashMap)29 Model (com.ichi2.libanki.Model)23 Map (java.util.Map)22 Intent (android.content.Intent)21 Resources (android.content.res.Resources)18 TextView (android.widget.TextView)18 SharedPreferences (android.content.SharedPreferences)17 Cursor (android.database.Cursor)17