Skip to content

Commit

Permalink
refactor(deck-picker): emptyFiltered -> ViewModel
Browse files Browse the repository at this point in the history
  • Loading branch information
david-allison committed Jan 16, 2025
1 parent 9c8e6d8 commit 89e0006
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 7 deletions.
15 changes: 8 additions & 7 deletions AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ open class DeckPicker :
setupFlows()
}

@Suppress("UNUSED_PARAMETER")
private fun setupFlows() {
fun onDeckDeleted(result: DeckDeletionResult) {
showSnackbar(result.toHumanReadableString(), Snackbar.LENGTH_SHORT) {
Expand All @@ -636,8 +637,14 @@ open class DeckPicker :
}
}

fun onDeckCountsChanged(unit: Unit) {
updateDeckList()
if (fragmented) loadStudyOptionsFragment(false)
}

viewModel.deckDeletedNotification.launchCollectionInLifecycleScope(::onDeckDeleted)
viewModel.emptyCardsNotification.launchCollectionInLifecycleScope(::onCardsEmptied)
viewModel.flowOfDeckCountsChanged.launchCollectionInLifecycleScope(::onDeckCountsChanged)
}

private val onReceiveContentListener =
Expand Down Expand Up @@ -2474,16 +2481,10 @@ open class DeckPicker :
}

private fun emptyFiltered(did: DeckId) {
getColUnsafe.decks.select(did)
launchCatchingTask {
withProgress {
withCol {
Timber.d("doInBackgroundEmptyCram")
sched.emptyDyn(decks.selected())
}
viewModel.emptyFilteredDeck(did).join()
}
updateDeckList()
if (fragmented) loadStudyOptionsFragment(false)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import com.ichi2.libanki.CardId
import com.ichi2.libanki.Consts
import com.ichi2.libanki.DeckId
import com.ichi2.libanki.undoableOp
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import timber.log.Timber

/** @see [DeckPicker] */
class DeckPickerViewModel : ViewModel() {
Expand All @@ -39,6 +41,12 @@ class DeckPickerViewModel : ViewModel() {
val deckDeletedNotification = MutableSharedFlow<DeckDeletionResult>()
val emptyCardsNotification = MutableSharedFlow<EmptyCardsResult>()

/**
* A notification that the counts of
*/
// TODO: most of the recalculation should be moved inside the ViewModel
val flowOfDeckCountsChanged = MutableSharedFlow<Unit>()

/**
* Keep track of which deck was last given focus in the deck list. If we find that this value
* has changed between deck list refreshes, we need to recenter the deck list to the new current
Expand Down Expand Up @@ -92,6 +100,16 @@ class DeckPickerViewModel : ViewModel() {
val result = undoableOp { removeCardsAndOrphanedNotes(emptyCards) }
emptyCardsNotification.emit(EmptyCardsResult(cardsDeleted = result.count))
}

// TODO: move withProgress to the ViewModel, so we don't return 'Job'
// TODO: undoableOp { } on emptyDyn
fun emptyFilteredDeck(deckId: DeckId): Job =
viewModelScope.launch {
Timber.i("empty filtered deck %s", deckId)
withCol { decks.select(deckId) }
withCol { sched.emptyDyn(decks.selected()) }
flowOfDeckCountsChanged.emit(Unit)
}
}

/** Result of [DeckPickerViewModel.deleteDeck] */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.turbine.test
import com.ichi2.anki.RobolectricTest
import com.ichi2.libanki.CardId
import com.ichi2.libanki.Consts
import com.ichi2.libanki.DeckId
import com.ichi2.libanki.Note
import com.ichi2.libanki.undoStatus
import com.ichi2.testutils.ensureOpsExecuted
import org.hamcrest.CoreMatchers.not
Expand Down Expand Up @@ -89,6 +92,30 @@ class DeckPickerViewModelTest : RobolectricTest() {
assertThat("col undo status", col.undoStatus().undo, equalTo("Empty Cards"))
}

@Test
fun `empty filtered - functionality`() {
runTest {
val note = addBasicNote("To", "Filtered")
val filteredDeckId = moveAllCardsToFilteredDeck(assertOn = note)

viewModel.emptyFilteredDeck(filteredDeckId).join()

assertThat("deck was reset", note.firstCard().did, equalTo(Consts.DEFAULT_DECK_ID))
}
}

@Test
fun `empty filtered - flows`() {
runTest {
viewModel.flowOfDeckCountsChanged.test {
val filteredDeckId = moveAllCardsToFilteredDeck()
expectNoEvents()
viewModel.emptyFilteredDeck(filteredDeckId).join()
awaitItem()
}
}
}

@CheckResult
private suspend fun createEmptyCards(): List<CardId> {
addNoteUsingNoteTypeName("Cloze", "{{c1::Hello}} {{c2::World}}", "").apply {
Expand All @@ -103,4 +130,16 @@ class DeckPickerViewModelTest : RobolectricTest() {

/** test helper to use [deleteEmptyCards] without an [EmptyCards] instance */
private fun DeckPickerViewModel.deleteEmptyCards(list: List<CardId>) = deleteEmptyCards(EmptyCards(list))

/**
* Moves all cards to a deck named "Filtered"
*
* If there are no notes, one is created
* @return The [DeckId] of the filtered deck
*/
private fun moveAllCardsToFilteredDeck(assertOn: Note = addBasicNote("To", "Filtered")): DeckId =
addDynamicDeck("Filtered", "").also { did ->
assertThat("filter - did", assertOn.firstCard().did, equalTo(did))
assertThat("filter - odid", assertOn.firstCard().oDid, equalTo(Consts.DEFAULT_DECK_ID))
}
}

0 comments on commit 89e0006

Please sign in to comment.