diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt index 3fd85b345df7..fcdfe70af170 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt @@ -56,6 +56,7 @@ import com.ichi2.anki.reviewreminders.ScheduleRemindersDestination import com.ichi2.anki.settings.Prefs import com.ichi2.anki.syncAuth import com.ichi2.anki.utils.Destination +import com.ichi2.anki.utils.ext.allDecksCountsUncapped import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow @@ -166,10 +167,14 @@ class DeckPickerViewModel : tree.onlyHasDefaultDeck() && noCards }.stateIn(viewModelScope, SharingStarted.Eagerly, initialValue = null) + // The deck-tree protobuf clamps each node's new/review/learn counts at 9999 + // (backend behavior, shared with Anki Desktop), so summing the root's three + // fields underreports for large collections — see issue #17605. Route through + // `allDecksCountsUncapped` (uncapped, limit-aware via getQueuedCards) instead. val flowOfCardsDue = combine(flowOfDeckDueTree, flowOfDeckListInInitialState) { tree, inInitialState -> if (tree == null || inInitialState != false) return@combine null - tree.newCount + tree.revCount + tree.lrnCount + withCol { sched.allDecksCountsUncapped().count() } } /** "Studied N cards in 0 seconds today */ diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Scheduler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Scheduler.kt index 233d81e9b2bc..f56a8c341c07 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Scheduler.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/utils/ext/Scheduler.kt @@ -45,3 +45,30 @@ fun Scheduler.allDecksCounts(): Counts { } return total } + +/** + * Like [allDecksCounts] but uncapped: per-node counts on the deck-tree protobuf + * are clamped at 9999 by the backend, so summing them produces a wrong total + * for large collections (issue #17605). This iterates top-level decks and pulls + * each subtree's count from [Scheduler.counts] instead, which sources from + * `getQueuedCards` and is both limit-aware and unclamped — the same source + * `StudyOptionsFragment` uses to display per-deck counts. + * + * Side effect: temporarily mutates and restores `decks.selected()`. + */ +fun Scheduler.allDecksCountsUncapped(): Counts { + val total = Counts() + val previouslySelected = col.decks.selected() + try { + for (node in deckDueTree().children) { + col.decks.select(node.did) + val c = counts() + total.addNew(c.new) + total.addLrn(c.lrn) + total.addRev(c.rev) + } + } finally { + col.decks.select(previouslySelected) + } + return total +}