Skip to content

Commit b96f255

Browse files
committed
feat(card-browser): add filters to SearchHistory
The intention of Search History is that it will pre-populate chips in the UI with the previously selected values These IDs are the planned 'first stage' of chips and match Anki Desktop's filters Issue 18709
1 parent e862657 commit b96f255

2 files changed

Lines changed: 97 additions & 2 deletions

File tree

AnkiDroid/src/main/java/com/ichi2/anki/browser/SearchHistory.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,23 @@
1616

1717
package com.ichi2.anki.browser
1818

19+
import com.ichi2.anki.Flag
1920
import com.ichi2.anki.R
21+
import com.ichi2.anki.browser.search.CardState
22+
import com.ichi2.anki.libanki.DeckId
23+
import com.ichi2.anki.libanki.NoteTypeId
2024
import com.ichi2.anki.settings.Prefs
2125
import com.ichi2.anki.settings.PrefsRepository
26+
import kotlinx.serialization.EncodeDefault
27+
import kotlinx.serialization.ExperimentalSerializationApi
2228
import kotlinx.serialization.SerialName
2329
import kotlinx.serialization.Serializable
30+
import kotlinx.serialization.Transient
2431
import kotlinx.serialization.json.Json
2532
import timber.log.Timber
2633

34+
private typealias Tag = String
35+
2736
/**
2837
* The user's past searches in the Card Browser.
2938
*
@@ -88,21 +97,48 @@ class SearchHistory(
8897
/**
8998
* An entry in the history of the card browser.
9099
* This is user-supplied, so may contain PII.
100+
*
101+
* Contains the minimal values needed for persistent serialization:
102+
* Deck IDs are stored, rather than deck names. See [deckIds]
103+
*
91104
* @see SearchHistory
92105
*/
93106
// !! When updating this, consider equality in addRecent
107+
// TODO: opt-in may no longer be needed in kotlinx-serialization 1.10.0
108+
@OptIn(ExperimentalSerializationApi::class)
94109
@Serializable
95110
data class SearchHistoryEntry(
96111
@SerialName("q")
97112
val query: String,
113+
// Use IDs so we can handle a rename.
114+
// Tradeoff: a query to get the deck names is needed to produce a search string or display
115+
// the selected deck name in the UI
116+
@SerialName("did")
117+
@EncodeDefault(EncodeDefault.Mode.NEVER)
118+
val deckIds: List<DeckId> = emptyList(),
119+
@SerialName("f")
120+
@EncodeDefault(EncodeDefault.Mode.NEVER)
121+
val flags: List<Flag> = emptyList(),
122+
@SerialName("t")
123+
@EncodeDefault(EncodeDefault.Mode.NEVER)
124+
val tags: List<Tag> = emptyList(),
125+
@SerialName("ntid")
126+
@EncodeDefault(EncodeDefault.Mode.NEVER)
127+
val noteTypes: List<NoteTypeId> = emptyList(),
128+
@SerialName("s")
129+
@EncodeDefault(EncodeDefault.Mode.NEVER)
130+
val cardStates: List<CardState> = emptyList(),
98131
) {
132+
@Transient
133+
private val allFilters = listOf(deckIds, flags, tags, noteTypes, cardStates)
134+
99135
override fun toString() = query
100136

101137
/**
102138
* Whether there is no set search - effectively a search for the default search:
103139
* `deck:*`
104140
*/
105-
fun isSearchEmpty() = query.isBlank()
141+
fun isSearchEmpty() = query.isBlank() && allFilters.all { it.isEmpty() }
106142
}
107143

108144
companion object {

AnkiDroid/src/test/java/com/ichi2/anki/browser/SearchHistoryTest.kt

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ package com.ichi2.anki.browser
1818

1919
import androidx.annotation.CheckResult
2020
import androidx.test.ext.junit.runners.AndroidJUnit4
21+
import com.ichi2.anki.Flag
2122
import com.ichi2.anki.R
2223
import com.ichi2.anki.RobolectricTest
2324
import com.ichi2.anki.browser.SearchHistory.SearchHistoryEntry
25+
import com.ichi2.anki.browser.search.CardState
26+
import com.ichi2.anki.libanki.DeckId
27+
import com.ichi2.anki.libanki.NoteTypeId
2428
import com.ichi2.anki.settings.Prefs
2529
import com.ichi2.testutils.getString
2630
import org.hamcrest.MatcherAssert.assertThat
@@ -118,7 +122,62 @@ class SearchHistoryTest : RobolectricTest() {
118122
// additional properties will be added; make sure we don't corrupt past entries
119123
addNumberedEntries(1)
120124
assertThat(readSearchHistoryRaw(), equalTo("""[{"q":"1"}]"""))
121-
assertThat(history.entries.single().query, equalTo("1"))
125+
with(history.entries.single()) {
126+
assertThat(query, equalTo("1"))
127+
assertThat(deckIds, empty())
128+
assertThat(flags, empty())
129+
assertThat(tags, empty())
130+
assertThat(noteTypes, empty())
131+
assertThat(cardStates, empty())
132+
}
133+
}
134+
135+
@Test
136+
fun `v2 serialization is unchanged`() {
137+
// additional properties will be added; make sure we don't corrupt past entries
138+
history.addRecent(
139+
SearchHistoryEntry(
140+
query = "1",
141+
deckIds = listOf(1, 2),
142+
flags = listOf(Flag.RED, Flag.BLUE),
143+
tags = listOf("Hello::World", "tag"),
144+
noteTypes = listOf(3, 4),
145+
cardStates =
146+
listOf(
147+
CardState.New,
148+
CardState.Learning,
149+
CardState.Review,
150+
CardState.Buried,
151+
CardState.Suspended,
152+
),
153+
),
154+
)
155+
assertThat(
156+
readSearchHistoryRaw(),
157+
equalTo(
158+
"""[{"q":"1","did":[1,2],"f":[1,4],"t":["Hello::World","tag"],"ntid":[3,4],"s":[0,1,2,3,4]}]""",
159+
),
160+
)
161+
162+
with(history.entries.single()) {
163+
assertThat(query, equalTo("1"))
164+
assertThat(deckIds, equalTo(listOf<DeckId>(1, 2)))
165+
assertThat(flags, equalTo(listOf(Flag.RED, Flag.BLUE)))
166+
assertThat(tags, equalTo(listOf("Hello::World", "tag")))
167+
assertThat(noteTypes, equalTo(listOf<NoteTypeId>(3, 4)))
168+
assertThat(
169+
cardStates,
170+
equalTo(
171+
listOf(
172+
CardState.New,
173+
CardState.Learning,
174+
CardState.Review,
175+
CardState.Buried,
176+
CardState.Suspended,
177+
),
178+
),
179+
)
180+
}
122181
}
123182

124183
@Test

0 commit comments

Comments
 (0)