-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Refactor: Move deck operations business logic from DeckPicker to DeckPickerViewModel #19245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Uff, I'll resolve this tomorrow |
60bf12c to
11bd7f9
Compare
11bd7f9 to
b629a0b
Compare
AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt
Outdated
Show resolved
Hide resolved
|
Replying to a comment |
@david-allison Not sure I get this — looks like you haven’t replied to the comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... This should have submitted them
|
|
||
| fun onFocusedDeckChanged(deckId: DeckId?) { | ||
| val position = deckId?.let { findDeckPosition(it) } ?: 0 | ||
| val position = deckId?.let { viewModel.findDeckPosition(it, deckListAdapter.currentList) } ?: 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally the viewModel should be the source of truth for the collection of decks
| // This code should not be reachable with lower versions | ||
| val shortcut = | ||
| ShortcutInfoCompat | ||
| .Builder(this, did.toString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a unit test in libanki on DeckId.toString being correct.
It's subject to being broken if we move to a value class
| /** | ||
| * Gets the current name of a deck | ||
| */ | ||
| suspend fun getDeckName(deckId: DeckId): String = withCol { decks.name(deckId) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Todo to remove this or make it private, fine for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably remove, that's just a backend call.
| fun onSubDeckCreated() = | ||
| viewModelScope.launch { | ||
| flowOfSubDeckCreated.emit(Unit) | ||
| flowOfDeckCountsChanged.emit(Unit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Todo: this should be using ChangeManager
| fun DeckNode.onlyHasDefaultDeck() = children.singleOrNull()?.did == DEFAULT_DECK_ID | ||
|
|
||
| /** Data for creating a deck shortcut */ | ||
| data class ShortcutData( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe comment an example of the long/short values
b629a0b to
1040c94
Compare
| val isEmpty = | ||
| withCol { | ||
| val node = sched.deckDueTree().find(did) | ||
| node?.all { decks.isEmpty(it.did) } ?: true | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| val isEmpty = | |
| withCol { | |
| val node = sched.deckDueTree().find(did) | |
| node?.all { decks.isEmpty(it.did) } ?: true | |
| } | |
| val isEmpty = withCol { decks.cardCount(did, includeSubdecks = true) |
| val deck = getNodeByDid(did) | ||
| if (deck.hasCardsReadyToStudy()) { | ||
|
|
||
| val deck = withCol { sched.deckDueTree().find(did) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| val deck = withCol { sched.deckDueTree().find(did) } | |
| val deck = withCol { decks.getLegacy(did) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that decks.getLegacy(did) returns a Deck object, which doesn’t provide the hasCardsReadyToStudy() method. On the other hand, sched.deckDueTree().find(did) returns a DeckNode, and this one does have hasCardsReadyToStudy().
Because of this, I think your suggestion may not fully apply in this context — we specifically need the hasCardsReadyToStudy() functionality, which is only available via DeckNode
| } | ||
|
|
||
| if (!deck.filtered && isDeckAndSubdeckEmpty(did)) { | ||
| val isFiltered = withCol { decks.isFiltered(did) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have the deck instance from above and we can use its isFiltered property, so there's no need for this backend cal:
deck?.isFiltered.
| viewModel.flowOfCreateShortcut.test { | ||
| supportFragmentManager.selectContextMenuOption(DeckPickerContextMenuOption.CREATE_SHORTCUT, didA) | ||
| advanceUntilIdle() | ||
| awaitItem() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My comment was about the two lines below:
| viewModel.flowOfCreateShortcut.test { | |
| supportFragmentManager.selectContextMenuOption(DeckPickerContextMenuOption.CREATE_SHORTCUT, didA) | |
| advanceUntilIdle() | |
| awaitItem() | |
| } | |
| supportFragmentManager.selectContextMenuOption(DeckPickerContextMenuOption.CREATE_SHORTCUT, didA) | |
| advanceUntilIdle() |
being enough for the test to pass. If we require test {} then there's something wrong in the code.
| hasSubDecks = tree.children.any { it.children.any() }, | ||
| ) | ||
| } | ||
| }.stateIn(viewModelScope, SharingStarted.Eagerly, initialValue = FlattenedDeckList.empty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why stateIn?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using stateIn with SharingStarted.Eagerly is doing a few things:
Keeps state – Any new UI part (like a screen) directly gets the latest deck list without waiting.
Shares result – If many parts of the UI are watching, they all use the same result instead of redoing the work.
But yes, Eagerly can be wasteful since it starts the flow even if no UI is looking at it.
Other choices:
-
Use
WhileSubscribed()so it runs only when some UI is observing. -
Remove
stateInif normally only one UI part collects it. -
Keep Eagerly if calculation is heavy and we want to keep it cached always.
I would prefer WhileSubscribed() unless we really need eager caching.
1040c94 to
29b594c
Compare
29b594c to
b943542
Compare
b943542 to
08ed8b9
Compare
- Move 8 deck-related functions from DeckPicker to DeckPickerViewModel - Add reactive flows for UI communication (export, shortcuts, subdeck creation) - Update DeckPicker to use ViewModel methods instead of direct collection access
08ed8b9 to
e755821
Compare
Purpose / Description
This PR moves 8 deck-related business logic functions from DeckPicker.kt to DeckPickerViewModel.kt to improve code architecture and follow MVVM pattern properly.
Fixes
Approach
Moved business logic functions to ViewModel:
1.
getDeckName()- Gets deck name for rename operations2.
onSubDeckCreated()- Notifies when subdeck is created3.
rebuildFilteredDeck()- Rebuilds filtered deck with current settings4.
exportDeck()- Handles deck export requests5.
createIcon()- Prepares data for deck shortcut creation6.
disableDeckAndChildrenShortcuts()- Gets deck IDs for disabling shortcuts7.
findDeckPosition()- Finds deck position in flattened list8.
isDeckAndSubdeckEmpty()- Checks if deck and subdecks are emptyAdded reactive flows for UI communication:
-
flowOfExportDeck- For export requests-
flowOfCreateShortcut- For shortcut creation with ShortcutData-
flowOfDisableShortcuts- For shortcut disabling-
flowOfSubDeckCreated- For subdeck creation notificationsUpdated DeckPicker.kt:
- Modified methods to use ViewModel instead of direct collection access
- Added flow listeners for reactive UI updates
How Has This Been Tested?
Physical Device (Redmi)
Checklist
Please, go through these checks before submitting the PR.