From 472086fdba833d57fe4b8c31c5805963e9f52e77 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:24:29 +0000 Subject: [PATCH 1/2] refactor(card-browser): require all parameters for `SearchFilters` this avoids developer error when adding a new filter --- .../anki/browser/search/SearchFilters.kt | 34 +++++++++++++++---- .../anki/browser/search/SearchRequest.kt | 2 +- .../anki/browser/search/SearchFiltersTest.kt | 4 +-- .../anki/browser/search/SearchRequestTest.kt | 2 +- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt index 5f980af746b1..b5aed23f1ceb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt @@ -27,11 +27,11 @@ import com.ichi2.anki.libanki.NotetypeJson * As a UI model: the display names of the filters are provided, as well as IDs */ data class SearchFilters( - val decks: List = emptyList(), - val flags: List = emptyList(), - val tags: List = emptyList(), - val noteTypes: List = emptyList(), - val cardStates: List = emptyList(), + val decks: List, + val flags: List, + val tags: List, + val noteTypes: List, + val cardStates: List, ) { /** * A list of filters which are using non-default values @@ -49,6 +49,26 @@ data class SearchFilters( } } - // support extensions on the type - companion object + companion object { + /** An instance of [SearchFilters] with no [active filters][SearchFilters.activeFilters] */ + val EMPTY = partial() + + /** + * Creates a [SearchFilters] instance, providing only a subset of filters + */ + // exists so the primary constructor calls break if a parameter is added + fun partial( + decks: List = emptyList(), + flags: List = emptyList(), + tags: List = emptyList(), + noteTypes: List = emptyList(), + cardStates: List = emptyList(), + ) = SearchFilters( + decks = decks, + flags = flags, + tags = tags, + noteTypes = noteTypes, + cardStates = cardStates, + ) + } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchRequest.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchRequest.kt index fe0f1068e0a6..1ead6f47dfa9 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchRequest.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchRequest.kt @@ -29,7 +29,7 @@ import com.ichi2.anki.libanki.SearchJoiner */ data class SearchRequest( val query: String, - val filters: SearchFilters = SearchFilters(), + val filters: SearchFilters = SearchFilters.EMPTY, ) { /** * Syntactic sugar for using [copy], to modify [filters] diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchFiltersTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchFiltersTest.kt index 96fd8725b420..3101961b4111 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchFiltersTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchFiltersTest.kt @@ -26,13 +26,13 @@ import org.junit.Test class SearchFiltersTest { @Test fun `activeFilters is empty by default`() { - val filters = SearchFilters() + val filters = SearchFilters.EMPTY assertThat(filters.activeFilters, empty()) } @Test fun `activeFilters is non-empty if a filter is set`() { - val filters = SearchFilters(decks = listOf(DeckNameId("Default", 1))) + val filters = SearchFilters.partial(decks = listOf(DeckNameId("Default", 1))) assertThat(filters.activeFilters, not(empty())) } } diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt index b5745fc104e6..572924c7c28d 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt @@ -50,7 +50,7 @@ class SearchRequestTest : JvmTest() { @Test fun `search string generation - filter only`() { - val entry = SearchRequest(query = "", filters = SearchFilters(decks = col.decks.allNamesAndIds())) + val entry = SearchRequest(query = "", filters = SearchFilters.partial(decks = col.decks.allNamesAndIds())) assertThat(entry.toValidSearchString(), equalTo("deck:Default")) } From 64a29aa4d859022af7605d8a17e90fc9ff07dec7 Mon Sep 17 00:00:00 2001 From: David Allison <62114487+david-allison@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:27:37 +0000 Subject: [PATCH 2/2] refactor(card-browser): use libAnki NoteTypeNameID class --- .../ichi2/anki/browser/search/SearchFilters.kt | 17 +++++------------ .../anki/browser/search/SearchRequestTest.kt | 6 +++--- .../java/com/ichi2/anki/libanki/Notetypes.kt | 5 ++++- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt index b5aed23f1ceb..9685d0a3691a 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/browser/search/SearchFilters.kt @@ -18,7 +18,7 @@ package com.ichi2.anki.browser.search import com.ichi2.anki.Flag import com.ichi2.anki.libanki.DeckNameId -import com.ichi2.anki.libanki.NoteTypeId +import com.ichi2.anki.libanki.NoteTypeNameID import com.ichi2.anki.libanki.NotetypeJson /** @@ -30,7 +30,7 @@ data class SearchFilters( val decks: List, val flags: List, val tags: List, - val noteTypes: List, + val noteTypes: List, val cardStates: List, ) { /** @@ -40,15 +40,6 @@ data class SearchFilters( */ val activeFilters by lazy { listOf(decks, flags, tags, noteTypes, cardStates).filter { it.isNotEmpty() } } - data class NoteTypeNameId( - val name: String, - val id: NoteTypeId, - ) { - companion object { - fun fromNoteTypeJson(noteTypeJson: NotetypeJson) = NoteTypeNameId(noteTypeJson.name, noteTypeJson.id) - } - } - companion object { /** An instance of [SearchFilters] with no [active filters][SearchFilters.activeFilters] */ val EMPTY = partial() @@ -61,7 +52,7 @@ data class SearchFilters( decks: List = emptyList(), flags: List = emptyList(), tags: List = emptyList(), - noteTypes: List = emptyList(), + noteTypes: List = emptyList(), cardStates: List = emptyList(), ) = SearchFilters( decks = decks, @@ -72,3 +63,5 @@ data class SearchFilters( ) } } + +fun NoteTypeNameID.Companion.fromNoteTypeJson(noteTypeJson: NotetypeJson) = NoteTypeNameID(noteTypeJson.name, noteTypeJson.id) diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt index 572924c7c28d..c06451d7211d 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/browser/search/SearchRequestTest.kt @@ -19,8 +19,8 @@ package com.ichi2.anki.browser.search import androidx.test.ext.junit.runners.AndroidJUnit4 import com.ichi2.anki.Flag import com.ichi2.anki.browser.SearchHistory.SearchHistoryEntry -import com.ichi2.anki.browser.search.SearchFilters.NoteTypeNameId import com.ichi2.anki.libanki.DeckNameId +import com.ichi2.anki.libanki.NoteTypeNameID import com.ichi2.anki.libanki.exception.InvalidSearchException import com.ichi2.anki.libanki.testutils.AnkiTest import com.ichi2.testutils.EmptyApplication @@ -88,7 +88,7 @@ class SearchRequestTest : JvmTest() { decks = listOf(DeckNameId("Default", 1), DeckNameId("Custom", 2)), flags = listOf(Flag.RED, Flag.BLUE), tags = listOf("Hello::World", "tag"), - noteTypes = listOf(SearchFilters.NoteTypeNameId("Basic", 3), SearchFilters.NoteTypeNameId("Advanced", 4)), + noteTypes = listOf(NoteTypeNameID("Basic", 3), NoteTypeNameID("Advanced", 4)), cardStates = listOf( CardState.New, @@ -116,7 +116,7 @@ class SearchRequestTest : JvmTest() { listOf( col.notetypes.basic, col.notetypes.cloze, - ).map(NoteTypeNameId::fromNoteTypeJson), + ).map { NoteTypeNameID.fromNoteTypeJson(it) }, ) } } diff --git a/libanki/src/main/java/com/ichi2/anki/libanki/Notetypes.kt b/libanki/src/main/java/com/ichi2/anki/libanki/Notetypes.kt index 25e852aaacc8..7a08ec30eca7 100644 --- a/libanki/src/main/java/com/ichi2/anki/libanki/Notetypes.kt +++ b/libanki/src/main/java/com/ichi2/anki/libanki/Notetypes.kt @@ -67,7 +67,10 @@ import timber.log.Timber class NoteTypeNameID( val name: String, val id: NoteTypeId, -) +) { + // support extension + companion object +} class Notetypes( val col: Collection,