Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.ichi2.anki.launchCatchingTask
import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.showThemedToast
import com.ichi2.anki.snackbar.showSnackbar
import com.ichi2.anki.utils.ext.defaultConfig
import com.ichi2.anki.withProgress
import com.ichi2.preferences.IncrementerNumberRangePreferenceCompat
import com.ichi2.utils.show
Expand Down Expand Up @@ -97,7 +98,7 @@ class DevOptionsFragment : SettingsFragment() {
Timber.w("Corrupting FSRS parameters for default deck config")
launchCatchingTask {
withCol {
val defaultConfig = decks.getConfig(1)
val defaultConfig = decks.defaultConfig
val invalidFsrsConfig =
JSONArray().apply {
put(0.4197)
Expand Down
22 changes: 22 additions & 0 deletions AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Decks.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 David Allison <[email protected]>
*
* 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 <http://www.gnu.org/licenses/>.
*/

package com.ichi2.anki.utils.ext

import com.ichi2.anki.libanki.Decks

val Decks.defaultConfig
get() = getConfig(confId = 1)!!
3 changes: 2 additions & 1 deletion AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.ichi2.anki.libanki.DeckId
import com.ichi2.anki.preferences.sharedPrefs
import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.utils.Destination
import com.ichi2.anki.utils.ext.defaultConfig
import com.ichi2.anki.utils.ext.dismissAllDialogFragments
import com.ichi2.testutils.BackendEmulatingOpenConflict
import com.ichi2.testutils.BackupManagerTestUtilities
Expand Down Expand Up @@ -182,7 +183,7 @@ class DeckPickerTest : RobolectricTest() {
@Test
fun limitAppliedAfterReview() {
val sched = col.sched
val dconf = col.decks.getConfig(1)
val dconf = col.decks.defaultConfig
assertNotNull(dconf)
dconf.new.perDay = 10
col.decks.save(dconf)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,23 @@
package com.ichi2.anki.utils.ext
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.ichi2.anki.common.utils.ext.getStringOrNull
import com.ichi2.anki.common.utils.ext.jsonObjectIterable
import com.ichi2.testutils.AndroidTest
import com.ichi2.testutils.EmptyApplication
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.empty
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasSize
import org.intellij.lang.annotations.Language
import org.json.JSONException
import org.json.JSONObject
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertNull

// TODO: move this to the common module
@RunWith(AndroidJUnit4::class) // This is necessary, android and JVM differ on JSONObject.NULL
@Config(application = EmptyApplication::class)
class JSONObjectTest : AndroidTest {
Expand All @@ -43,4 +50,20 @@ class JSONObjectTest : AndroidTest {
assertNotNull(test("null"), message = """{ test: "null" }""")
assertNotNull(test("1"), message = """{ test: "1" }""")
}

@Test
fun `test jsonObjectIterable`() {
fun jsonObjectIterable(
@Language("JSON") json: String,
) = JSONObject(json).jsonObjectIterable().toList()

assertThat(jsonObjectIterable("{}"), empty())

with(jsonObjectIterable("""{"1": {"name": "hello"}}""")) {
assertThat(this, hasSize(1))
assertThat(this[0].getString("name"), equalTo("hello"))
}

assertFailsWith<JSONException> { jsonObjectIterable("") }
}
}
12 changes: 12 additions & 0 deletions common/src/main/java/com/ichi2/anki/common/utils/ext/JSONObject.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,15 @@ fun JSONObject.getStringOrNull(key: String): String? {
null
}
}

/**
* Returns a [Sequence] of all values in the [JSONObject], assuming each value is a [JSONObject]
*
* @throws org.json.JSONException if any value is not a [JSONObject].
*/
fun JSONObject.jsonObjectIterable(): Iterable<JSONObject> =
this
.keys()
.asSequence()
.map { getJSONObject(it) }
.asIterable()
12 changes: 11 additions & 1 deletion libanki/src/main/java/com/ichi2/anki/libanki/Deck.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.ichi2.anki.libanki

import anki.decks.Deck.Filtered.SearchTerm.Order
import com.ichi2.anki.common.utils.ext.deepClonedInto
import com.ichi2.anki.libanki.utils.NotInLibAnki
import net.ankiweb.rsdroid.Translations
import org.json.JSONObject

Expand Down Expand Up @@ -74,7 +75,7 @@ class Deck : JSONObject {
put("id", value)
}

var conf: Long
var conf: DeckConfigId
get() {
val value = optLong("conf")
return if (value > 0) value else 1
Expand Down Expand Up @@ -136,3 +137,12 @@ fun Order.toDisplayString(translations: Translations) =
Order.RETRIEVABILITY_DESCENDING -> translations.deckConfigSortOrderRetrievabilityDescending()
Order.UNRECOGNIZED -> throw IllegalArgumentException("Can't display an unknown enum value.")
}

@NotInLibAnki
internal fun Deck.confOrNull(): DeckConfigId? =
try {
val value = getLong("conf")
if (value > 0) value else null
} catch (e: Exception) {
null
}
Loading