mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 12:02:16 +02:00
Migrate SearchCards to coroutines
The preloading of the topmost cards has been moved into the browser, since it's not useful for the JS API, but this makes the searchCardsNumberOfResultCount() test harder to port. Since the CardCache should be removed in the future, I don't think it's worth the effort of getting the test working again. Closes #12223
This commit is contained in:
parent
3e8cfa12d4
commit
ee04b0c940
@ -24,17 +24,12 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.WebView
|
||||
import com.github.zafarkhaja.semver.Version
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.ichi2.anim.ActivityTransitionAnimation
|
||||
import com.ichi2.anki.UIUtils.showThemedToast
|
||||
import com.ichi2.anki.servicelayer.SearchService
|
||||
import com.ichi2.anki.snackbar.setMaxLines
|
||||
import com.ichi2.anki.snackbar.showSnackbar
|
||||
import com.ichi2.async.CollectionTask.SearchCards
|
||||
import com.ichi2.async.TaskListener
|
||||
import com.ichi2.async.TaskManager
|
||||
import com.ichi2.libanki.Card
|
||||
import com.ichi2.libanki.CardId
|
||||
import com.ichi2.libanki.Consts.CARD_QUEUE
|
||||
@ -44,6 +39,7 @@ import com.ichi2.libanki.SortOrder
|
||||
import com.ichi2.utils.JSONException
|
||||
import com.ichi2.utils.JSONObject
|
||||
import com.ichi2.utils.isActiveNetworkMetered
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
||||
open class AnkiDroidJsAPI(private val activity: AbstractFlashcardViewer) {
|
||||
@ -465,26 +461,19 @@ open class AnkiDroidJsAPI(private val activity: AbstractFlashcardViewer) {
|
||||
|
||||
@JavascriptInterface
|
||||
fun ankiSearchCardWithCallback(query: String) {
|
||||
val task = SearchCards(query, SortOrder.UseCollectionOrdering(), 0, 0, 0)
|
||||
val listener = SearchCardListener(activity.webView!!, context)
|
||||
activity.runOnUiThread {
|
||||
TaskManager.launchCollectionTask(task, listener)
|
||||
val cards = try {
|
||||
runBlocking {
|
||||
searchForCards(query, SortOrder.UseCollectionOrdering())
|
||||
}
|
||||
} catch (exc: Exception) {
|
||||
activity.webView!!.evaluateJavascript(
|
||||
"console.log('${context.getString(R.string.search_card_js_api_no_results)}')",
|
||||
null
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
class SearchCardListener(private val webView: WebView, private val context: Context) : TaskListener<List<CardBrowser.CardCache>, SearchService.SearchCardsResult?>() {
|
||||
override fun onPreExecute() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override fun onPostExecute(result: SearchService.SearchCardsResult?) {
|
||||
val searchResult: MutableList<String> = ArrayList()
|
||||
|
||||
if (result!!.result == null) {
|
||||
webView.evaluateJavascript("console.log('${context.getString(R.string.search_card_js_api_no_results)}')", null)
|
||||
}
|
||||
|
||||
for (s in result.result!!) {
|
||||
for (s in cards) {
|
||||
val jsonObject = JSONObject()
|
||||
val fieldsData = s.card.note().fields
|
||||
val fieldsName = s.card.model().fieldsNames
|
||||
@ -505,7 +494,8 @@ open class AnkiDroidJsAPI(private val activity: AbstractFlashcardViewer) {
|
||||
|
||||
// quote result to prevent JSON injection attack
|
||||
val jsonEncodedString = org.json.JSONObject.quote(searchResult.toString())
|
||||
webView.evaluateJavascript("ankiSearchCard($jsonEncodedString)", null)
|
||||
activity.runOnUiThread {
|
||||
activity.webView!!.evaluateJavascript("ankiSearchCard($jsonEncodedString)", null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ import com.ichi2.anim.ActivityTransitionAnimation
|
||||
import com.ichi2.anki.AnkiFont.Companion.getTypeface
|
||||
import com.ichi2.anki.CardUtils.getAllCards
|
||||
import com.ichi2.anki.CardUtils.getNotes
|
||||
import com.ichi2.anki.CollectionManager.withCol
|
||||
import com.ichi2.anki.UIUtils.saveCollectionInBackground
|
||||
import com.ichi2.anki.UIUtils.showThemedToast
|
||||
import com.ichi2.anki.dialogs.*
|
||||
@ -61,7 +62,6 @@ import com.ichi2.anki.servicelayer.SchedulerService.NextCard
|
||||
import com.ichi2.anki.servicelayer.SchedulerService.RepositionCards
|
||||
import com.ichi2.anki.servicelayer.SchedulerService.RescheduleCards
|
||||
import com.ichi2.anki.servicelayer.SchedulerService.ResetCards
|
||||
import com.ichi2.anki.servicelayer.SearchService.SearchCardsResult
|
||||
import com.ichi2.anki.servicelayer.UndoService.Undo
|
||||
import com.ichi2.anki.snackbar.showSnackbar
|
||||
import com.ichi2.anki.widgets.DeckDropDownAdapter.SubtitleListener
|
||||
@ -71,7 +71,6 @@ import com.ichi2.async.CollectionTask.CheckCardSelection
|
||||
import com.ichi2.async.CollectionTask.DeleteNoteMulti
|
||||
import com.ichi2.async.CollectionTask.MarkNoteMulti
|
||||
import com.ichi2.async.CollectionTask.RenderBrowserQA
|
||||
import com.ichi2.async.CollectionTask.SearchCards
|
||||
import com.ichi2.async.CollectionTask.SuspendCardMulti
|
||||
import com.ichi2.async.CollectionTask.UpdateMultipleNotes
|
||||
import com.ichi2.async.CollectionTask.UpdateNote
|
||||
@ -1470,7 +1469,6 @@ open class CardBrowser :
|
||||
}
|
||||
|
||||
private fun invalidate() {
|
||||
TaskManager.cancelAllTasks(SearchCards::class.java)
|
||||
TaskManager.cancelAllTasks(RenderBrowserQA::class.java)
|
||||
TaskManager.cancelAllTasks(CheckCardSelection::class.java)
|
||||
mCards.clear()
|
||||
@ -1482,7 +1480,7 @@ open class CardBrowser :
|
||||
searchCards()
|
||||
}
|
||||
|
||||
@KotlinCleanup("isNotEmpty()")
|
||||
@RustCleanup("remove card cache; switch to RecyclerView and browserRowForId (#11889)")
|
||||
private fun searchCards() {
|
||||
// cancel the previous search & render tasks if still running
|
||||
invalidate()
|
||||
@ -1495,23 +1493,49 @@ open class CardBrowser :
|
||||
} else {
|
||||
if ("" != mSearchTerms) "$mRestrictOnDeck($mSearchTerms)" else mRestrictOnDeck
|
||||
}
|
||||
if (colIsOpen() && mCardsAdapter != null) {
|
||||
// clear the existing card list
|
||||
mCards.reset()
|
||||
mCardsAdapter!!.notifyDataSetChanged()
|
||||
// estimate maximum number of cards that could be visible (assuming worst-case minimum row height of 20dp)
|
||||
// Perform database query to get all card ids
|
||||
TaskManager.launchCollectionTask(
|
||||
SearchCards(
|
||||
searchText!!,
|
||||
if (mOrder == CARD_ORDER_NONE) NoOrdering() else UseCollectionOrdering(),
|
||||
numCardsToRender(),
|
||||
mColumn1Index,
|
||||
mColumn2Index
|
||||
),
|
||||
mSearchCardsHandler
|
||||
)
|
||||
val query = searchText!!
|
||||
val order = if (mOrder == CARD_ORDER_NONE) NoOrdering() else UseCollectionOrdering()
|
||||
launchCatchingTask {
|
||||
val cards = withProgress { searchForCards(query, order) }
|
||||
// Render the first few items
|
||||
for (i in 0 until Math.min(numCardsToRender(), cards.size)) {
|
||||
cards[i].load(false, mColumn1Index, mColumn2Index)
|
||||
}
|
||||
redrawAfterSearch(cards)
|
||||
}
|
||||
}
|
||||
|
||||
fun redrawAfterSearch(cards: MutableList<CardCache>) {
|
||||
mCards.replaceWith(cards)
|
||||
Timber.i("CardBrowser:: Completed searchCards() Successfully")
|
||||
updateList()
|
||||
if (mSearchView == null || mSearchView!!.isIconified) {
|
||||
return
|
||||
}
|
||||
if (hasSelectedAllDecks()) {
|
||||
showSnackbar(subtitleText, Snackbar.LENGTH_SHORT)
|
||||
} else {
|
||||
// If we haven't selected all decks, allow the user the option to search all decks.
|
||||
val message = if (cardCount == 0) {
|
||||
getString(R.string.card_browser_no_cards_in_deck, selectedDeckNameForUi)
|
||||
} else {
|
||||
subtitleText
|
||||
}
|
||||
showSnackbar(message, Snackbar.LENGTH_INDEFINITE) {
|
||||
setAction(R.string.card_browser_search_all_decks) { searchAllDecks() }
|
||||
}
|
||||
}
|
||||
if (mShouldRestoreScroll) {
|
||||
mShouldRestoreScroll = false
|
||||
val newPosition = newPositionOfSelectedCard
|
||||
if (newPosition != CARD_NOT_AVAILABLE) {
|
||||
autoScrollTo(newPosition)
|
||||
}
|
||||
}
|
||||
updatePreviewMenuItem()
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@ -1820,66 +1844,6 @@ open class CardBrowser :
|
||||
}
|
||||
}
|
||||
|
||||
private val mSearchCardsHandler = SearchCardsHandler(this)
|
||||
|
||||
@VisibleForTesting
|
||||
internal inner class SearchCardsHandler(browser: CardBrowser) : ListenerWithProgressBar<List<CardCache>, SearchCardsResult?>(browser) {
|
||||
override fun actualOnProgressUpdate(context: CardBrowser, value: List<CardCache>) {
|
||||
// Need to copy the list into a new list, because the original list is modified, and
|
||||
// ListAdapter crash
|
||||
mCards.replaceWith(java.util.ArrayList(value))
|
||||
updateList()
|
||||
}
|
||||
|
||||
override fun actualOnPostExecute(context: CardBrowser, result: SearchCardsResult?) {
|
||||
if (result!!.hasResult) {
|
||||
mCards.replaceWith(result.result!!.toMutableList())
|
||||
updateList()
|
||||
handleSearchResult()
|
||||
}
|
||||
if (result.hasError) {
|
||||
showThemedToast(this@CardBrowser, result.error, true)
|
||||
}
|
||||
if (mShouldRestoreScroll) {
|
||||
mShouldRestoreScroll = false
|
||||
val newPosition = newPositionOfSelectedCard
|
||||
if (newPosition != CARD_NOT_AVAILABLE) {
|
||||
autoScrollTo(newPosition)
|
||||
}
|
||||
}
|
||||
updatePreviewMenuItem()
|
||||
hideProgressBar()
|
||||
}
|
||||
|
||||
private fun handleSearchResult() {
|
||||
Timber.i("CardBrowser:: Completed doInBackgroundSearchCards Successfully")
|
||||
updateList()
|
||||
if (mSearchView == null || mSearchView!!.isIconified) {
|
||||
return
|
||||
}
|
||||
|
||||
if (hasSelectedAllDecks()) {
|
||||
showSnackbar(subtitleText, Snackbar.LENGTH_SHORT)
|
||||
} else {
|
||||
// If we haven't selected all decks, allow the user the option to search all decks.
|
||||
val message = if (cardCount == 0) {
|
||||
getString(R.string.card_browser_no_cards_in_deck, selectedDeckNameForUi)
|
||||
} else {
|
||||
subtitleText
|
||||
}
|
||||
|
||||
showSnackbar(message, Snackbar.LENGTH_INDEFINITE) {
|
||||
setAction(R.string.card_browser_search_all_decks) { searchAllDecks() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun actualOnCancelled(context: CardBrowser) {
|
||||
super.actualOnCancelled(context)
|
||||
hideProgressBar()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveScrollingState(position: Int) {
|
||||
mOldCardId = mCards[position].id
|
||||
mOldCardTopOffset = calculateTopOffset(position)
|
||||
@ -2711,3 +2675,15 @@ open class CardBrowser :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchForCards(
|
||||
query: String,
|
||||
order: SortOrder
|
||||
): MutableList<CardBrowser.CardCache> {
|
||||
return withCol {
|
||||
findCards(query, order).asSequence()
|
||||
.mapIndexed { idx, cid ->
|
||||
CardBrowser.CardCache(cid, col, idx)
|
||||
}.toMutableList()
|
||||
}
|
||||
}
|
||||
|
@ -541,33 +541,6 @@ open class CollectionTask<Progress, Result>(val task: TaskDelegateBase<Progress,
|
||||
}
|
||||
}
|
||||
|
||||
class SearchCards(private val query: String, private val order: SortOrder, private val numCardsToRender: Int, private val column1Index: Int, private val column2Index: Int) : TaskDelegate<List<CardCache>, SearchCardsResult>() {
|
||||
override fun task(col: Collection, collectionTask: ProgressSenderAndCancelListener<List<CardCache>>): SearchCardsResult {
|
||||
Timber.d("doInBackgroundSearchCards")
|
||||
val searchResult: MutableList<CardCache> = ArrayList()
|
||||
val searchResult_: List<Long>
|
||||
searchResult_ = try {
|
||||
col.findCards(query, order)
|
||||
} catch (e: Exception) {
|
||||
// exception can occur via normal operation
|
||||
Timber.w(e)
|
||||
return SearchCardsResult.error(e)
|
||||
}
|
||||
Timber.d("The search found %d cards", searchResult_.size)
|
||||
var position = 0
|
||||
for (cid in searchResult_) {
|
||||
val card = CardCache(cid, col, position++)
|
||||
searchResult.add(card)
|
||||
}
|
||||
// Render the first few items
|
||||
for (i in 0 until Math.min(numCardsToRender, searchResult.size)) {
|
||||
searchResult[i].load(false, column1Index, column2Index)
|
||||
}
|
||||
// Finish off the task
|
||||
return SearchCardsResult.success(searchResult)
|
||||
}
|
||||
}
|
||||
|
||||
class SearchNotes(private val query: String, private val order: SortOrder, private val numCardsToRender: Int, private val column1Index: Int, private val column2Index: Int) : TaskDelegate<List<CardCache>, SearchCardsResult>() {
|
||||
override fun task(col: Collection, collectionTask: ProgressSenderAndCancelListener<List<CardCache>>): SearchCardsResult {
|
||||
Timber.d("doInBackgroundSearchCards")
|
||||
|
@ -28,12 +28,9 @@ import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.errorprone.annotations.CheckReturnValue
|
||||
import com.ichi2.anki.CardBrowser.CardCache
|
||||
import com.ichi2.async.CollectionTask.SearchCards
|
||||
import com.ichi2.async.TaskManager
|
||||
import com.ichi2.libanki.CardId
|
||||
import com.ichi2.libanki.Consts
|
||||
import com.ichi2.libanki.Note
|
||||
import com.ichi2.libanki.SortOrder.NoOrdering
|
||||
import com.ichi2.testutils.AnkiActivityUtils.getDialogFragment
|
||||
import com.ichi2.testutils.AnkiAssert
|
||||
import com.ichi2.testutils.IntentAssert
|
||||
@ -356,7 +353,7 @@ class CardBrowserTest : RobolectricTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tagWithBracketsDisplaysProperly() {
|
||||
fun tagWithBracketsDisplaysProperly() = runTest {
|
||||
val n = addNoteUsingBasicModel("Hello", "World")
|
||||
n.addTag("sketchy::(1)")
|
||||
n.flush()
|
||||
@ -369,7 +366,7 @@ class CardBrowserTest : RobolectricTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterByFlagDisplaysProperly() {
|
||||
fun filterByFlagDisplaysProperly() = runTest {
|
||||
val cardWithRedFlag = addNoteUsingBasicModel("Card with red flag", "Reverse")
|
||||
flagCardForNote(cardWithRedFlag, 1)
|
||||
|
||||
@ -583,7 +580,7 @@ class CardBrowserTest : RobolectricTest() {
|
||||
|
||||
/** 8027 */
|
||||
@Test
|
||||
fun checkSearchString() {
|
||||
fun checkSearchString() = runTest {
|
||||
addNoteUsingBasicModel("Hello", "John")
|
||||
val deck = addDeck("Deck 1")
|
||||
col.decks.select(deck)
|
||||
@ -641,7 +638,7 @@ class CardBrowserTest : RobolectricTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkIfSearchAllDecksWorks() {
|
||||
fun checkIfSearchAllDecksWorks() = runTest {
|
||||
addNoteUsingBasicModel("Hello", "World")
|
||||
val deck = addDeck("Test Deck")
|
||||
col.decks.select(deck)
|
||||
@ -776,21 +773,6 @@ class CardBrowserTest : RobolectricTest() {
|
||||
renderOnScroll.onScroll(cardBrowser.mCardsListView!!, 0, 0, 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun searchCardsNumberOfResultCount() {
|
||||
val cardsToRender = 1
|
||||
|
||||
val cardBrowser = getBrowserWithNotes(2, CardBrowserSizeOne::class.java)
|
||||
|
||||
val task = SearchCards("", NoOrdering(), cardsToRender, 0, 0)
|
||||
|
||||
TaskManager.launchCollectionTask(task, cardBrowser.SearchCardsHandler(cardBrowser))
|
||||
val cards = cardBrowser.mCards
|
||||
assertThat(2, equalTo(cards.size()))
|
||||
assertTrue(cards[0].isLoaded)
|
||||
assertFalse(cards[1].isLoaded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun truncateAndExpand() {
|
||||
val cardBrowser = getBrowserWithNotes(3)
|
||||
|
Loading…
Reference in New Issue
Block a user