diff --git a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt index ed9714e14a49..32edebe879e0 100644 --- a/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt +++ b/AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/ContentProviderTest.kt @@ -32,11 +32,11 @@ import com.ichi2.anki.testutil.grantPermissions import com.ichi2.libanki.* import com.ichi2.libanki.exception.ConfirmModSchemaException import com.ichi2.utils.KotlinCleanup +import com.ichi2.utils.emptyStringArray import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* import org.json.JSONObject import org.junit.* -import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue @@ -69,7 +69,7 @@ class ContentProviderTest : InstrumentedTest() { private val mTestDeckIds: MutableList = ArrayList(TEST_DECKS.size + 1) private lateinit var mCreatedNotes: ArrayList private var mModelId: Long = 0 - private var mDummyFields = arrayOfNulls(1) + private var mDummyFields = emptyStringArray(1) /** * Initially create one note for each model. @@ -106,12 +106,12 @@ class ContentProviderTest : InstrumentedTest() { * set-down, */ val did = col.decks.byName(partialName!!)?.id ?: col.decks.id(partialName) mTestDeckIds.add(did) - mCreatedNotes.add(setupNewNote(col, mModelId, did, mDummyFields.requireNoNulls(), TEST_TAG)) + mCreatedNotes.add(setupNewNote(col, mModelId, did, mDummyFields, TEST_TAG)) partialName += "::" } } // Add a note to the default deck as well so that testQueryNextCard() works - mCreatedNotes.add(setupNewNote(col, mModelId, 1, mDummyFields.requireNoNulls(), TEST_TAG)) + mCreatedNotes.add(setupNewNote(col, mModelId, 1, mDummyFields, TEST_TAG)) } /** @@ -208,10 +208,10 @@ class ContentProviderTest : InstrumentedTest() { assertNotNull("check note URI path", newNoteUri!!.lastPathSegment) val addedNote = Note(col, newNoteUri.lastPathSegment!!.toLong()) addedNote.load() - assertArrayEquals( + assertEquals( "Check that fields were set correctly", addedNote.fields, - TEST_NOTE_FIELDS + TEST_NOTE_FIELDS.toMutableList() ) assertEquals("Check that tag was set correctly", TEST_TAG, addedNote.tags[0]) val model: JSONObject? = col.notetypes.get(mModelId) @@ -495,8 +495,7 @@ class ContentProviderTest : InstrumentedTest() { dummyFields2[0] = TEST_FIELD_VALUE for (uri in mCreatedNotes) { // Update the flds - @Suppress("UNCHECKED_CAST") - cv.put(FlashCardsContract.Note.FLDS, Utils.joinFields(dummyFields2 as Array)) + cv.put(FlashCardsContract.Note.FLDS, Utils.joinFields(dummyFields2)) cr.update(uri, cv, null, null) cr.query(uri, FlashCardsContract.Note.DEFAULT_PROJECTION, null, null, null) .use { noteCursor -> @@ -513,10 +512,10 @@ class ContentProviderTest : InstrumentedTest() { val newFields = Utils.splitFields( noteCursor.getString(noteCursor.getColumnIndex(FlashCardsContract.Note.FLDS)) ) - assertArrayEquals( + assertEquals( "Check that the flds have been updated correctly", newFields, - dummyFields2 + dummyFields2.toMutableList() ) } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt index d2d658a46b0b..7df156b91b9d 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/CardBrowser.kt @@ -2053,7 +2053,7 @@ open class CardBrowser : Column.CARD -> if (inCardMode) card.template().optString("name") else "${card.note().numberOfCards()}" Column.DUE -> card.dueString Column.EASE -> if (inCardMode) getEaseForCards() else getAvgEaseForNotes() - Column.CHANGED -> LanguageUtil.getShortDateFormatFromS(if (inCardMode) card.mod else card.note().mod) + Column.CHANGED -> LanguageUtil.getShortDateFormatFromS(if (inCardMode) card.mod else card.note().mod.toLong()) Column.CREATED -> LanguageUtil.getShortDateFormatFromMs(card.note().id) Column.EDITED -> LanguageUtil.getShortDateFormatFromS(card.note().mod) Column.INTERVAL -> if (inCardMode) queryIntervalForCards() else queryAvgIntervalForNotes() diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt index 3b8e47678576..ae4e735f1a0b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/NoteEditor.kt @@ -98,6 +98,7 @@ import org.json.JSONObject import timber.log.Timber import java.util.* import java.util.function.Consumer +import kotlin.collections.ArrayList import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @@ -139,7 +140,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags /* Null if adding a new card. Presently NonNull if editing an existing note - but this is subject to change */ private var mCurrentEditedCard: Card? = null - private var mSelectedTags: ArrayList? = null + private var mSelectedTags: MutableList? = null @get:VisibleForTesting var deckId: DeckId = 0 @@ -383,7 +384,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags if (mSelectedTags == null) { mSelectedTags = ArrayList(0) } - savedInstanceState.putStringArrayList("tags", mSelectedTags) + savedInstanceState.putStringArrayList("tags", mSelectedTags?.let { ArrayList(it) }) } private val fieldsAsBundleForPreview: Bundle @@ -1524,7 +1525,7 @@ class NoteEditor : AnkiActivity(), DeckSelectionListener, SubtitleListener, Tags } private fun setEditFieldTexts(contents: String?) { - var fields: Array? = null + var fields: List? = null val len: Int if (contents == null) { len = 0 diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Note.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/Note.kt index 0763599ccd70..322a9cef9961 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Note.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Note.kt @@ -18,12 +18,11 @@ package com.ichi2.libanki import androidx.annotation.VisibleForTesting -import com.ichi2.libanki.exception.WrongId import com.ichi2.utils.KotlinCleanup import com.ichi2.utils.emptyStringArray +import com.ichi2.utils.emptyStringMutableList import net.ankiweb.rsdroid.RustCleanup import org.json.JSONObject -import timber.log.Timber import java.util.* import java.util.regex.Pattern @@ -43,17 +42,14 @@ class Note : Cloneable { var mid: Long = 0 private set - lateinit var tags: ArrayList + lateinit var tags: MutableList private set - lateinit var fields: Array + lateinit var fields: MutableList private set - private var mFlags = 0 - private var mData: String? = null private var mFMap: Map>? = null - private var mScm: Long = 0 var usn = 0 private set - var mod: Long = 0 + var mod: Int = 0 private set constructor(col: Collection, id: Long) { @@ -68,36 +64,27 @@ class Note : Cloneable { guId = Utils.guid64() this.notetype = notetype mid = notetype.getLong("id") - tags = ArrayList() - fields = Array(notetype.getJSONArray("flds").length()) { "" } - mFlags = 0 - mData = "" + tags = mutableListOf() + fields = emptyStringMutableList(notetype.getJSONArray("flds").length()) mFMap = Notetypes.fieldMap(this.notetype) - mScm = col.scm } fun load() { - Timber.d("load()") - col.db - .query( - "SELECT guid, mid, mod, usn, tags, flds, flags, data FROM notes WHERE id = ?", - this.id - ).use { cursor -> - if (!cursor.moveToFirst()) { - throw WrongId(this.id, "note") - } - guId = cursor.getString(0) - mid = cursor.getLong(1) - mod = cursor.getLong(2) - usn = cursor.getInt(3) - tags = ArrayList(col.tags.split(cursor.getString(4))) - fields = Utils.splitFields(cursor.getString(5)) - mFlags = cursor.getInt(6) - mData = cursor.getString(7) - notetype = col.notetypes.get(mid)!! - mFMap = Notetypes.fieldMap(notetype) - mScm = col.scm - } + val note = col.backend.getNote(this.id) + loadFromBackendNote(note) + } + + private fun loadFromBackendNote(note: anki.notes.Note) { + this.id = note.id + this.guId = note.guid + this.mid = note.notetypeId + this.notetype = col.notetypes.get(mid)!! // not in libAnki + this.mod = note.mtimeSecs + this.usn = note.usn + // the lists in the protobuf are NOT mutable, even though they cast to MutableList + this.tags = note.tagsList.toMutableList() + this.fields = note.fieldsList.toMutableList() + this.mFMap = Notetypes.fieldMap(notetype) } fun reloadModel() { @@ -149,7 +136,8 @@ class Note : Cloneable { return mFMap!!.keys.toTypedArray() } - fun values(): Array { + @KotlinCleanup("see if we can make this immutable") + fun values(): MutableList { return fields } @@ -203,7 +191,7 @@ class Note : Cloneable { } fun setTagsFromStr(str: String?) { - tags = ArrayList(col.tags.split(str!!)) + tags = col.tags.split(str!!) } fun delTag(tag: String?) { diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt index 7beba63a0baf..bf47a84f68ea 100644 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt +++ b/AnkiDroid/src/main/java/com/ichi2/libanki/Utils.kt @@ -199,9 +199,9 @@ object Utils { } // TODO ensure manual conversion is correct - fun splitFields(fields: String): Array { + fun splitFields(fields: String): MutableList { // -1 ensures that we don't drop empty fields at the ends - return fields.split(FIELD_SEPARATOR).toTypedArray() + return fields.split(FIELD_SEPARATOR).toMutableList() } /* @@ -250,7 +250,7 @@ object Utils { * @param sortIdx An index of the field * @return The field at sortIdx, without html media, and the csum of the first field. */ - fun sfieldAndCsum(fields: Array, sortIdx: Int): Pair { + fun sfieldAndCsum(fields: List, sortIdx: Int): Pair { val firstStripped = stripHTMLMedia(fields[0]) val sortStripped = if (sortIdx == 0) firstStripped else stripHTMLMedia(fields[sortIdx]) return Pair(sortStripped, fieldChecksumWithoutHtmlMedia(firstStripped)) diff --git a/AnkiDroid/src/main/java/com/ichi2/libanki/exception/WrongId.kt b/AnkiDroid/src/main/java/com/ichi2/libanki/exception/WrongId.kt deleted file mode 100644 index 70a991ab2e52..000000000000 --- a/AnkiDroid/src/main/java/com/ichi2/libanki/exception/WrongId.kt +++ /dev/null @@ -1,20 +0,0 @@ -/**************************************************************************************** - * Copyright (c) 2020 Arthur Milchior * - * * - * This program is free software; you can redistribute it and/or modify it under * - * the terms of the GNU General Public License as published by the Free Software * - * Foundation; either version 3 of the License, or (at your option) any later * - * version. * - * * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY * - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * - * PARTICULAR PURPOSE. See the GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License along with * - * this program. If not, see . * - ****************************************************************************************/ -package com.ichi2.libanki.exception - -import java.lang.RuntimeException - -class WrongId(id: Long, kind: String) : RuntimeException(" No $kind with id $id") diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt b/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt index 6e83c648bdee..bf8e0f00256b 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt +++ b/AnkiDroid/src/main/java/com/ichi2/utils/LanguageUtil.kt @@ -202,6 +202,9 @@ object LanguageUtil { return DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(Date(s * 1000L)) } + fun getShortDateFormatFromS(s: Int): String = + DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(Date(s * 1000L)) + fun getLocaleCompat(resources: Resources): Locale? { return ConfigurationCompat.getLocales(resources.configuration)[0] } diff --git a/AnkiDroid/src/main/java/com/ichi2/utils/StringUtil.kt b/AnkiDroid/src/main/java/com/ichi2/utils/StringUtil.kt index 20be9284e5d7..564de90a7791 100644 --- a/AnkiDroid/src/main/java/com/ichi2/utils/StringUtil.kt +++ b/AnkiDroid/src/main/java/com/ichi2/utils/StringUtil.kt @@ -42,4 +42,6 @@ fun String.lastIndexOfOrNull(c: Char): Int? = else -> index } +fun emptyStringMutableList(size: Int): MutableList = MutableList(size) { "" } + fun emptyStringArray(size: Int): Array = Array(size) { "" } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/RecursivePictureMenuTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/RecursivePictureMenuTest.kt index 64eba0491f22..7426739cb6f9 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/RecursivePictureMenuTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/dialogs/RecursivePictureMenuTest.kt @@ -15,12 +15,15 @@ */ package com.ichi2.anki.dialogs +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.ichi2.anki.R import com.ichi2.testutils.AnkiAssert import org.hamcrest.MatcherAssert import org.hamcrest.Matchers import org.junit.Test +import org.junit.runner.RunWith +@RunWith(AndroidJUnit4::class) class RecursivePictureMenuTest : RecursivePictureMenuUtilTest() { @Test fun removeChild() { diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt index c6030cc8cf50..07950835f338 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/ModelTest.kt @@ -143,8 +143,8 @@ class NotetypeTest : JvmTest() { // add a field var field: JSONObject? = col.notetypes.newField("foo") col.notetypes.addField(m, field!!) - assertArrayEquals( - arrayOf("1", "2", ""), + assertEquals( + listOf("1", "2", ""), col.getNote( col.notetypes.nids( m @@ -158,8 +158,8 @@ class NotetypeTest : JvmTest() { assertEquals("", col.getNote(col.notetypes.nids(m)[0]).getItem("bar")) // delete back col.notetypes.remField(m, m.getJSONArray("flds").getJSONObject(1)) - assertArrayEquals( - arrayOf("1", ""), + assertEquals( + listOf("1", ""), col.getNote( col.notetypes.nids( m @@ -168,8 +168,8 @@ class NotetypeTest : JvmTest() { ) // move 0 -> 1 col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(0), 1) - assertArrayEquals( - arrayOf("", "1"), + assertEquals( + listOf("", "1"), col.getNote( col.notetypes.nids( m @@ -178,8 +178,8 @@ class NotetypeTest : JvmTest() { ) // move 1 -> 0 col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(1), 0) - assertArrayEquals( - arrayOf("1", ""), + assertEquals( + listOf("1", ""), col.getNote( col.notetypes.nids( m @@ -192,8 +192,8 @@ class NotetypeTest : JvmTest() { note = col.getNote(col.notetypes.nids(m)[0]) note.setItem("baz", "2") note.flush() - assertArrayEquals( - arrayOf("1", "", "2"), + assertEquals( + listOf("1", "", "2"), col.getNote( col.notetypes.nids( m @@ -202,8 +202,8 @@ class NotetypeTest : JvmTest() { ) // move 2 -> 1 col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(2), 1) - assertArrayEquals( - arrayOf("1", "2", ""), + assertEquals( + listOf("1", "2", ""), col.getNote( col.notetypes.nids( m @@ -212,8 +212,8 @@ class NotetypeTest : JvmTest() { ) // move 0 -> 2 col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(0), 2) - assertArrayEquals( - arrayOf("2", "", "1"), + assertEquals( + listOf("2", "", "1"), col.getNote( col.notetypes.nids( m @@ -222,8 +222,8 @@ class NotetypeTest : JvmTest() { ) // move 0 -> 1 col.notetypes.moveField(m, m.getJSONArray("flds").getJSONObject(0), 1) - assertArrayEquals( - arrayOf("", "2", "1"), + assertEquals( + listOf("", "2", "1"), col.getNote( col.notetypes.nids( m diff --git a/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt b/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt index aad83f78f544..8f83839ad0ba 100644 --- a/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/libanki/UtilsTest.kt @@ -17,7 +17,6 @@ package com.ichi2.libanki import androidx.test.ext.junit.runners.AndroidJUnit4 -import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -28,8 +27,8 @@ class UtilsTest { @Test fun testSplit() { - Assert.assertArrayEquals(arrayOf("foo", "bar"), Utils.splitFields("foobar")) - Assert.assertArrayEquals(arrayOf("", "foo", "", "", ""), Utils.splitFields("foo")) + assertEquals(listOf("foo", "bar"), Utils.splitFields("foobar")) + assertEquals(listOf("", "foo", "", "", ""), Utils.splitFields("foo")) } @Test