Skip to content

Commit 2891b8f

Browse files
committed
refactor(deck-picker): inline result classes
Now we have UiEvent, we have no need for these classes Assisted-by: Claude Opus 4.6
1 parent 1ffadf4 commit 2891b8f

3 files changed

Lines changed: 55 additions & 78 deletions

File tree

AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,11 @@ import com.ichi2.anki.databinding.IncludeDeckPickerBinding
105105
import com.ichi2.anki.databinding.IncludeFloatingAddButtonBinding
106106
import com.ichi2.anki.deckpicker.BITMAP_BYTES_PER_PIXEL
107107
import com.ichi2.anki.deckpicker.BackgroundImage
108-
import com.ichi2.anki.deckpicker.DeckDeletionResult
109108
import com.ichi2.anki.deckpicker.DeckPickerViewModel
110109
import com.ichi2.anki.deckpicker.DeckPickerViewModel.AnkiDroidEnvironment
111110
import com.ichi2.anki.deckpicker.DeckPickerViewModel.FlattenedDeckList
112111
import com.ichi2.anki.deckpicker.DeckPickerViewModel.StartupResponse
113-
import com.ichi2.anki.deckpicker.EmptyCardsResult
114112
import com.ichi2.anki.deckpicker.OptionsMenuState
115-
import com.ichi2.anki.deckpicker.ShortcutData
116113
import com.ichi2.anki.deckpicker.SyncIconState
117114
import com.ichi2.anki.deckpicker.UiEvent
118115
import com.ichi2.anki.dialogs.AsyncDialogFragment
@@ -654,14 +651,14 @@ open class DeckPicker :
654651

655652
@Suppress("UNUSED_PARAMETER")
656653
private fun setupFlows() {
657-
fun onDeckDeleted(result: DeckDeletionResult) {
658-
showSnackbar(result.toHumanReadableString(), Snackbar.LENGTH_SHORT) {
654+
fun onDeckDeleted(event: UiEvent.DeckDeleted) {
655+
showSnackbar(event.toHumanReadableString(), Snackbar.LENGTH_SHORT) {
659656
setAction(R.string.undo) { undo() }
660657
}
661658
}
662659

663-
fun onCardsEmptied(result: EmptyCardsResult) {
664-
showSnackbar(result.toHumanReadableString(), Snackbar.LENGTH_SHORT) {
660+
fun onCardsEmptied(event: UiEvent.EmptyCardsDeleted) {
661+
showSnackbar(event.toHumanReadableString(), Snackbar.LENGTH_SHORT) {
665662
setAction(R.string.undo) { undo() }
666663
}
667664
}
@@ -836,12 +833,12 @@ open class DeckPicker :
836833

837834
viewModel.uiEvents.launchCollectionInLifecycleScope { event ->
838835
when (event) {
839-
is UiEvent.DeckDeleted -> onDeckDeleted(event.result)
840-
is UiEvent.EmptyCardsDeleted -> onCardsEmptied(event.result)
836+
is UiEvent.DeckDeleted -> onDeckDeleted(event)
837+
is UiEvent.EmptyCardsDeleted -> onCardsEmptied(event)
841838
is UiEvent.Navigate -> onDestinationChanged(event.destination)
842839
is UiEvent.ShowError -> onError(event.message)
843840
is UiEvent.ExportDeck -> onExportDeck(event.deckId)
844-
is UiEvent.CreateShortcut -> createIcon(event.data)
841+
is UiEvent.CreateShortcut -> createIcon(event)
845842
is UiEvent.DisableShortcuts -> disableDeckAndChildrenShortcuts(event.deckIds)
846843
UiEvent.PromptUpdateScheduler -> onPromptUserToUpdateScheduler(Unit)
847844
UiEvent.UndoUpdated -> onUndoUpdated(Unit)
@@ -2121,16 +2118,16 @@ open class DeckPicker :
21212118
}
21222119
}
21232120

2124-
private fun createIcon(shortcutData: ShortcutData) {
2121+
private fun createIcon(event: UiEvent.CreateShortcut) {
21252122
// This code should not be reachable with lower versions
21262123
val shortcut =
21272124
ShortcutInfoCompat
2128-
.Builder(this, shortcutData.deckId.toString())
2125+
.Builder(this, event.deckId.toString())
21292126
.setIntent(
2130-
intentToReviewDeckFromShortcuts(this, shortcutData.deckId),
2127+
intentToReviewDeckFromShortcuts(this, event.deckId),
21312128
).setIcon(IconCompat.createWithResource(this, R.mipmap.ic_launcher))
2132-
.setShortLabel(shortcutData.shortLabel)
2133-
.setLongLabel(shortcutData.longLabel)
2129+
.setShortLabel(event.shortLabel)
2130+
.setLongLabel(event.longLabel)
21342131
.build()
21352132
try {
21362133
val success = ShortcutManagerCompat.requestPinShortcut(this, shortcut, null)

AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt

Lines changed: 38 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import androidx.lifecycle.viewModelScope
2525
import anki.card_rendering.EmptyCardsReport
2626
import anki.collection.OpChanges
2727
import anki.decks.SetDeckCollapsedRequest
28-
import anki.i18n.GeneratedTranslations
2928
import anki.sync.SyncStatusResponse
3029
import com.ichi2.anki.CollectionManager
3130
import com.ichi2.anki.CollectionManager.TR
@@ -203,11 +202,7 @@ class DeckPickerViewModel :
203202
// to match and avoid unnecessary scrolls in `renderPage()`.
204203
focusedDeck = Consts.DEFAULT_DECK_ID
205204

206-
events.emit(
207-
UiEvent.DeckDeleted(
208-
DeckDeletionResult(deckName = deckName, cardsDeleted = changes.count),
209-
),
210-
)
205+
events.emit(UiEvent.DeckDeleted(deckName = deckName, cardsDeleted = changes.count))
211206
}
212207

213208
/**
@@ -245,7 +240,7 @@ class DeckPickerViewModel :
245240
}
246241
}
247242
val result = undoableOp { removeCardsAndOrphanedNotes(toDelete) }
248-
events.emit(UiEvent.EmptyCardsDeleted(EmptyCardsResult(cardsDeleted = result.count)))
243+
events.emit(UiEvent.EmptyCardsDeleted(cardsDeleted = result.count))
249244
}
250245

251246
// TODO: move withProgress to the ViewModel, so we don't return 'Job'
@@ -447,11 +442,9 @@ class DeckPickerViewModel :
447442
}
448443
events.emit(
449444
UiEvent.CreateShortcut(
450-
ShortcutData(
451-
deckId = deckId,
452-
shortLabel = shortLabel,
453-
longLabel = longLabel,
454-
),
445+
deckId = deckId,
446+
shortLabel = shortLabel,
447+
longLabel = longLabel,
455448
),
456449
)
457450
}
@@ -608,46 +601,8 @@ class DeckPickerViewModel :
608601
}
609602
}
610603

611-
/** Result of [DeckPickerViewModel.deleteDeck] */
612-
data class DeckDeletionResult(
613-
val deckName: String,
614-
val cardsDeleted: Int,
615-
) {
616-
/**
617-
* @see GeneratedTranslations.browsingCardsDeletedWithDeckname
618-
*/
619-
// TODO: Somewhat questionable meaning: {count} cards deleted from {deck_name}.
620-
@CheckResult
621-
fun toHumanReadableString() =
622-
TR.browsingCardsDeletedWithDeckname(
623-
count = cardsDeleted,
624-
deckName = deckName,
625-
)
626-
}
627-
628-
/** Result of [DeckPickerViewModel.deleteEmptyCards] */
629-
data class EmptyCardsResult(
630-
val cardsDeleted: Int,
631-
) {
632-
/**
633-
* @see GeneratedTranslations.emptyCardsDeletedCount */
634-
@CheckResult
635-
fun toHumanReadableString() = TR.emptyCardsDeletedCount(cardsDeleted)
636-
}
637-
638604
fun DeckNode.onlyHasDefaultDeck() = children.singleOrNull()?.did == DEFAULT_DECK_ID
639605

640-
/**
641-
* Data for creating a deck shortcut
642-
* @param shortLabel the basename of the deck (e.g., "Verbs" for "Language::English::Verbs")
643-
* @param longLabel the full deck name (e.g., "Language::English::Verbs")
644-
*/
645-
data class ShortcutData(
646-
val deckId: DeckId,
647-
val shortLabel: String,
648-
val longLabel: String,
649-
)
650-
651606
enum class SyncIconState {
652607
Normal,
653608
PendingChanges,
@@ -656,18 +611,36 @@ enum class SyncIconState {
656611
}
657612

658613
/**
659-
* One-shot UI events emitted by [DeckPickerViewModel].
660-
*
661-
* @see com.ichi2.anki.ui.UiEventHost
614+
* One-shot UI events emitted by [DeckPickerViewModel] over a single channel and
615+
* collected by [DeckPicker]. Use for navigation, snackbars, dialogs, errors —
616+
* not for ongoing state (use a `StateFlow` for that).
662617
*/
663618
sealed interface UiEvent {
619+
/** A deck (and possibly child decks) was deleted. @see DeckPickerViewModel.deleteDeck */
664620
data class DeckDeleted(
665-
val result: DeckDeletionResult,
666-
) : UiEvent
621+
val deckName: String,
622+
val cardsDeleted: Int,
623+
) : UiEvent {
624+
/**
625+
* @see anki.i18n.GeneratedTranslations.browsingCardsDeletedWithDeckname
626+
*/
627+
// TODO: Somewhat questionable meaning: {count} cards deleted from {deck_name}.
628+
@CheckResult
629+
fun toHumanReadableString() =
630+
TR.browsingCardsDeletedWithDeckname(
631+
count = cardsDeleted,
632+
deckName = deckName,
633+
)
634+
}
667635

636+
/** Empty cards were removed. @see DeckPickerViewModel.deleteEmptyCards */
668637
data class EmptyCardsDeleted(
669-
val result: EmptyCardsResult,
670-
) : UiEvent
638+
val cardsDeleted: Int,
639+
) : UiEvent {
640+
/** @see anki.i18n.GeneratedTranslations.emptyCardsDeletedCount */
641+
@CheckResult
642+
fun toHumanReadableString() = TR.emptyCardsDeletedCount(cardsDeleted)
643+
}
671644

672645
data class Navigate(
673646
val destination: Destination,
@@ -681,8 +654,15 @@ sealed interface UiEvent {
681654
val deckId: DeckId,
682655
) : UiEvent
683656

657+
/**
658+
* Request to create a launcher shortcut for a deck.
659+
* @param shortLabel the basename of the deck (e.g., "Verbs" for "Language::English::Verbs")
660+
* @param longLabel the full deck name (e.g., "Language::English::Verbs")
661+
*/
684662
data class CreateShortcut(
685-
val data: ShortcutData,
663+
val deckId: DeckId,
664+
val shortLabel: String,
665+
val longLabel: String,
686666
) : UiEvent
687667

688668
data class DisableShortcuts(

AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,22 @@ class DeckPickerViewModelTest : RobolectricTest() {
5353
viewModel.deleteEmptyCards(cardsToEmpty).join()
5454

5555
expectMostRecentItem().also {
56-
assertThat("cards deleted", it.result.cardsDeleted, equalTo(EXPECTED_CARDS))
56+
assertThat("cards deleted", it.cardsDeleted, equalTo(EXPECTED_CARDS))
5757
}
5858

5959
// ensure a duplicate output is displayed to the user
6060
val newCardsToEmpty = createEmptyCards()
6161
viewModel.deleteEmptyCards(newCardsToEmpty).join()
6262

6363
expectMostRecentItem().also {
64-
assertThat("cards deleted: duplicate output", it.result.cardsDeleted, equalTo(EXPECTED_CARDS))
64+
assertThat("cards deleted: duplicate output", it.cardsDeleted, equalTo(EXPECTED_CARDS))
6565
}
6666

6767
// test an empty list: a no-op should inform the user, rather than do nothing
6868
viewModel.deleteEmptyCards(emptyCardsReport { }).join()
6969

7070
expectMostRecentItem().also {
71-
assertThat("'no cards deleted' is notified", it.result.cardsDeleted, equalTo(0))
71+
assertThat("'no cards deleted' is notified", it.cardsDeleted, equalTo(0))
7272
}
7373
}
7474
}
@@ -96,13 +96,13 @@ class DeckPickerViewModelTest : RobolectricTest() {
9696
viewModel.deleteEmptyCards(emptyCardsReport, preserveNotes = true).join()
9797

9898
expectMostRecentItem().also {
99-
assertThat("note is retained", it.result.cardsDeleted, equalTo(EXPECTED_CARDS - 1))
99+
assertThat("note is retained", it.cardsDeleted, equalTo(EXPECTED_CARDS - 1))
100100
}
101101

102102
viewModel.deleteEmptyCards(emptyCardsReport, preserveNotes = false).join()
103103

104104
expectMostRecentItem().also {
105-
assertThat("note is deleted", it.result.cardsDeleted, equalTo(1))
105+
assertThat("note is deleted", it.cardsDeleted, equalTo(1))
106106
}
107107
}
108108
}

0 commit comments

Comments
 (0)