Skip to content

Commit dabe866

Browse files
david-allisonBrayanDSO
authored andcommitted
refactor(card-browser): initial selected deck
This should be a responsibility of the browser, not the destination Assisted-by: Claude Opus 4.7
1 parent 7defb30 commit dabe866

3 files changed

Lines changed: 100 additions & 15 deletions

File tree

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package com.ichi2.anki.browser
1818

1919
import android.content.Context
2020
import android.content.Intent
21-
import com.ichi2.anki.AnkiDroidApp
2221
import com.ichi2.anki.CardBrowser
2322
import com.ichi2.anki.libanki.CardId
2423
import com.ichi2.anki.libanki.DeckId
@@ -31,10 +30,10 @@ sealed interface BrowserDestination : Destination {
3130
data class ToDeck(
3231
val deckId: DeckId,
3332
) : BrowserDestination {
34-
override fun toIntent(context: Context): Intent {
35-
AnkiDroidApp.instance.sharedPrefsLastDeckIdRepository.lastDeckId = deckId
36-
return Intent(context, CardBrowser::class.java)
37-
}
33+
override fun toIntent(context: Context): Intent =
34+
Intent(context, CardBrowser::class.java).apply {
35+
putExtra(CardBrowserViewModel.EXTRA_DECK_ID, deckId)
36+
}
3837
}
3938

4039
/**
@@ -45,12 +44,11 @@ sealed interface BrowserDestination : Destination {
4544
val deckId: DeckId,
4645
val cardId: CardId,
4746
) : BrowserDestination {
48-
override fun toIntent(context: Context): Intent {
49-
AnkiDroidApp.instance.sharedPrefsLastDeckIdRepository.lastDeckId = deckId
50-
return Intent(context, CardBrowser::class.java).apply {
47+
override fun toIntent(context: Context): Intent =
48+
Intent(context, CardBrowser::class.java).apply {
49+
putExtra(CardBrowserViewModel.EXTRA_DECK_ID, deckId)
5150
putExtra(EXTRA_CARD_ID_KEY, cardId)
5251
}
53-
}
5452

5553
companion object {
5654
const val EXTRA_CARD_ID_KEY = "cardId"

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,17 @@ class CardBrowserViewModel(
395395
suspend fun queryDataForCardEdit(id: CardOrNoteId): CardId? = id.toCardId(cardsOrNotes)
396396

397397
private suspend fun getInitialDeck(): SelectableDeck {
398-
// TODO: Handle the launch intent
398+
suspend fun consumeIntentDeck(): SelectableDeck.Deck? {
399+
if (savedStateHandle.get<Boolean>(STATE_LAUNCH_INTENT_CONSUMED) == true) return null
400+
savedStateHandle[STATE_LAUNCH_INTENT_CONSUMED] = true
401+
val deckId = savedStateHandle.get<Long>(EXTRA_DECK_ID) ?: return null
402+
val name = withCol { decks.nameIfExists(deckId) } ?: return null
403+
return SelectableDeck.Deck(deckId = deckId, name = name)
404+
}
405+
406+
// Intent-supplied deck takes precedence, but only on the first launch
407+
consumeIntentDeck()?.let { deck -> return deck }
408+
399409
val lastDeckId = lastDeckId
400410
if (lastDeckId == ALL_DECKS_ID) {
401411
return SelectableDeck.AllDecks
@@ -1417,6 +1427,12 @@ class CardBrowserViewModel(
14171427
suspend fun getAvailableDecks(): List<SelectableDeck.Deck> = SelectableDeck.fromCollection(includeFiltered = false)
14181428

14191429
companion object {
1430+
/** Intent extra carrying the [DeckId] the browser should open scoped to. */
1431+
const val EXTRA_DECK_ID = "deckId"
1432+
1433+
/** Prevents one-shot extras from being re-applied after process death. */
1434+
private const val STATE_LAUNCH_INTENT_CONSUMED = "launchIntentConsumed"
1435+
14201436
const val STATE_MULTISELECT = "multiselect"
14211437
const val STATE_MULTISELECT_VALUES = "multiselect_values"
14221438

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

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,77 @@ class CardBrowserViewModelTest : JvmTest() {
403403
}
404404
}
405405

406+
@Test
407+
fun `EXTRA_DECK_ID intent opens the specified deck`() =
408+
runTest {
409+
val deckId = addDeck("New")
410+
val savedState = SavedStateHandle(mapOf(CardBrowserViewModel.EXTRA_DECK_ID to deckId))
411+
viewModel(savedStateHandle = savedState).apply {
412+
assertThat("intent deck is selected", deckId, equalTo(this.deckId))
413+
}
414+
}
415+
416+
@Test
417+
fun `EXTRA_DECK_ID intent persists deck to lastDeckIdRepository`() =
418+
runTest {
419+
val deckId = addDeck("New")
420+
val savedState = SavedStateHandle(mapOf(CardBrowserViewModel.EXTRA_DECK_ID to deckId))
421+
viewModel(savedStateHandle = savedState).apply {
422+
assertThat("deck persisted for next launch", lastDeckId, equalTo(deckId))
423+
}
424+
}
425+
426+
@Test
427+
fun `no EXTRA_DECK_ID falls back to lastDeckIdRepository`() =
428+
runTest {
429+
val deckId = addDeck("Persisted")
430+
viewModel(lastDeckId = deckId).apply {
431+
assertThat("repository value is used", deckId, equalTo(this.deckId))
432+
}
433+
}
434+
435+
@Test
436+
fun `EXTRA_DECK_ID for unknown deck falls back to lastDeckIdRepository`() =
437+
runTest {
438+
val persisted = addDeck("Persisted")
439+
val unknownDeckId: DeckId = 9_999_999_999L
440+
val savedState = SavedStateHandle(mapOf(CardBrowserViewModel.EXTRA_DECK_ID to unknownDeckId))
441+
viewModel(lastDeckId = persisted, savedStateHandle = savedState).apply {
442+
assertThat("unknown intent deck is ignored", persisted, equalTo(this.deckId))
443+
}
444+
}
445+
446+
@Test
447+
fun `user deck change survives process death`() =
448+
runTest {
449+
val intentDeckId = addDeck("From intent")
450+
val userDeckId = addDeck("User selection")
451+
val savedState = SavedStateHandle(mapOf(CardBrowserViewModel.EXTRA_DECK_ID to intentDeckId))
452+
453+
val persistentRepo =
454+
object : LastDeckIdRepository {
455+
override var lastDeckId: DeckId? = null
456+
}
457+
458+
// setup: initial launch + select new deck
459+
viewModel(
460+
savedStateHandle = savedState,
461+
lastDeckIdRepository = persistentRepo,
462+
).apply {
463+
assertThat("intent honored on first launch", deckId, equalTo(intentDeckId))
464+
setSelectedDeck(SelectableDeck.Deck(deckId = userDeckId, name = "User selection"))
465+
assertThat("user choice persisted to repository", persistentRepo.lastDeckId, equalTo(userDeckId))
466+
}
467+
468+
// intent does not override user selection
469+
viewModel(
470+
savedStateHandle = savedState,
471+
lastDeckIdRepository = persistentRepo,
472+
).apply {
473+
assertThat("user choice survives recreation", deckId, equalTo(userDeckId))
474+
}
475+
}
476+
406477
@Test
407478
fun `sort order from notes is selected - 16514`() {
408479
col.config.set("sortType", "noteCrt")
@@ -1710,12 +1781,12 @@ class CardBrowserViewModelTest : JvmTest() {
17101781
lastDeckId: DeckId? = null,
17111782
intent: CardBrowserLaunchOptions? = null,
17121783
mode: CardsOrNotes = CardsOrNotes.CARDS,
1713-
): CardBrowserViewModel {
1714-
val lastDeckIdRepository =
1784+
savedStateHandle: SavedStateHandle = SavedStateHandle(),
1785+
lastDeckIdRepository: LastDeckIdRepository =
17151786
object : LastDeckIdRepository {
17161787
override var lastDeckId: DeckId? = lastDeckId
1717-
}
1718-
1788+
},
1789+
): CardBrowserViewModel {
17191790
// default is CARDS, do nothing in this case
17201791
if (mode == CardsOrNotes.NOTES) {
17211792
CollectionManager.withCol { mode.saveToCollection(this@withCol) }
@@ -1728,7 +1799,7 @@ class CardBrowserViewModelTest : JvmTest() {
17281799
options = intent,
17291800
isFragmented = false,
17301801
preferences = AnkiDroidApp.sharedPreferencesProvider,
1731-
savedStateHandle = SavedStateHandle(),
1802+
savedStateHandle = savedStateHandle,
17321803
).apply {
17331804
invokeInitialSearch()
17341805
}

0 commit comments

Comments
 (0)