Skip to content

Commit 9794870

Browse files
committed
refactor(card-browser): move options to repository
* Simplifies the ViewModel, it's getting large Assisted-by: Claude Opus 4.7 - all
1 parent 9e745d6 commit 9794870

2 files changed

Lines changed: 81 additions & 36 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (c) 2026 David Allison <davidallisongithub@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
11+
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License along with
14+
* this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package com.ichi2.anki.browser
18+
19+
import android.content.SharedPreferences
20+
import androidx.core.content.edit
21+
import com.ichi2.anki.CollectionManager.withCol
22+
import com.ichi2.anki.model.CardsOrNotes
23+
import com.ichi2.anki.utils.ext.ignoreAccentsInSearch
24+
import kotlinx.coroutines.flow.MutableStateFlow
25+
import kotlinx.coroutines.flow.StateFlow
26+
import timber.log.Timber
27+
28+
/**
29+
* Source of truth for the values controlled by [com.ichi2.anki.dialogs.BrowserOptionsDialog]
30+
*/
31+
class BrowserOptionsRepository(
32+
private val sharedPrefs: SharedPreferences,
33+
) {
34+
val cardsOrNotes: StateFlow<CardsOrNotes>
35+
field = MutableStateFlow(CardsOrNotes.CARDS)
36+
37+
val isTruncated: StateFlow<Boolean>
38+
field = MutableStateFlow(sharedPrefs.getBoolean(PREF_IS_TRUNCATED, false))
39+
40+
val ignoreAccentsInSearch: StateFlow<Boolean>
41+
field = MutableStateFlow(false)
42+
43+
/** Reads persisted values into the flows. Call once during ViewModel init. */
44+
suspend fun load() {
45+
cardsOrNotes.value = withCol { CardsOrNotes.fromCollection(this) }
46+
ignoreAccentsInSearch.value = withCol { config.ignoreAccentsInSearch }
47+
}
48+
49+
suspend fun setCardsOrNotes(value: CardsOrNotes) {
50+
Timber.i("setting cards/notes mode to %s", value)
51+
withCol { value.saveToCollection(this) }
52+
cardsOrNotes.value = value
53+
}
54+
55+
fun setIsTruncated(value: Boolean) {
56+
Timber.d("setting truncated to %s", value)
57+
sharedPrefs.edit { putBoolean(PREF_IS_TRUNCATED, value) }
58+
isTruncated.value = value
59+
}
60+
61+
suspend fun setIgnoreAccentsInSearch(value: Boolean) {
62+
Timber.d("setting ignore accents in search to %s", value)
63+
withCol { config.ignoreAccentsInSearch = value }
64+
ignoreAccentsInSearch.value = value
65+
}
66+
67+
companion object {
68+
private const val PREF_IS_TRUNCATED = "isTruncated"
69+
}
70+
}

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

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import android.os.Bundle
2020
import android.os.Parcel
2121
import android.os.Parcelable
2222
import androidx.annotation.CheckResult
23-
import androidx.core.content.edit
2423
import androidx.core.os.BundleCompat
2524
import androidx.core.os.bundleOf
2625
import androidx.lifecycle.SavedStateHandle
@@ -82,7 +81,6 @@ import com.ichi2.anki.settings.Prefs
8281
import com.ichi2.anki.settings.PrefsRepository
8382
import com.ichi2.anki.utils.ext.currentCardBrowse
8483
import com.ichi2.anki.utils.ext.getCardOrNull
85-
import com.ichi2.anki.utils.ext.ignoreAccentsInSearch
8684
import com.ichi2.anki.utils.ext.setUserFlagForCards
8785
import kotlinx.coroutines.Deferred
8886
import kotlinx.coroutines.Job
@@ -141,6 +139,8 @@ class CardBrowserViewModel(
141139
preferences: SharedPreferencesProvider,
142140
val isFragmented: Boolean,
143141
val savedStateHandle: SavedStateHandle,
142+
private val browserOptionsRepository: BrowserOptionsRepository =
143+
BrowserOptionsRepository(preferences.sharedPrefs()),
144144
private val manualInit: Boolean = false,
145145
) : ViewModel(),
146146
SharedPreferencesProvider by preferences {
@@ -168,8 +168,8 @@ class CardBrowserViewModel(
168168
* Whether the browser is working in Cards mode or Notes mode.
169169
* default: [CARDS]
170170
* */
171-
private val flowOfCardsOrNotes = MutableStateFlow(CARDS)
172-
val cardsOrNotes get() = flowOfCardsOrNotes.value
171+
val flowOfCardsOrNotes: StateFlow<CardsOrNotes> = browserOptionsRepository.cardsOrNotes
172+
val cardsOrNotes: CardsOrNotes get() = flowOfCardsOrNotes.value
173173

174174
// card that was clicked (not marked)
175175
var currentCardId: CardId? = null
@@ -234,11 +234,10 @@ class CardBrowserViewModel(
234234
.map { it?.isNotEmpty() == true }
235235
.stateIn(viewModelScope, SharingStarted.Eagerly, initialValue = false)
236236

237-
val flowOfIsTruncated: MutableStateFlow<Boolean> =
238-
MutableStateFlow(sharedPrefs().getBoolean("isTruncated", false))
239-
val isTruncated get() = flowOfIsTruncated.value
237+
val flowOfIsTruncated: StateFlow<Boolean> = browserOptionsRepository.isTruncated
238+
val isTruncated: Boolean get() = flowOfIsTruncated.value
240239

241-
var shouldIgnoreAccents: Boolean = false
240+
val shouldIgnoreAccents: Boolean get() = browserOptionsRepository.ignoreAccentsInSearch.value
242241

243242
private val _selectedRows: MutableSet<CardOrNoteId> = Collections.synchronizedSet(LinkedHashSet())
244243

@@ -499,16 +498,13 @@ class CardBrowserViewModel(
499498
}.launchIn(viewModelScope)
500499

501500
viewModelScope.launch {
502-
shouldIgnoreAccents = withCol { config.ignoreAccentsInSearch }
501+
browserOptionsRepository.load()
503502

504503
val initialDeckId = if (selectAllDecks) SelectableDeck.AllDecks else getInitialDeck()
505504
// PERF: slightly inefficient if the source was lastDeckId
506505
setSelectedDeck(initialDeckId)
507506
refreshBackendColumns()
508507

509-
val cardsOrNotes = withCol { CardsOrNotes.fromCollection(this@withCol) }
510-
flowOfCardsOrNotes.update { cardsOrNotes }
511-
512508
withCol {
513509
sortTypeFlow.update { LegacySortType.fromCol(config, cardsOrNotes, prefs) }
514510
reverseDirectionFlow.update { ReverseDirection.fromConfig(config) }
@@ -691,32 +687,11 @@ class CardBrowserViewModel(
691687
}
692688
}
693689

694-
fun setCardsOrNotes(newValue: CardsOrNotes) =
695-
viewModelScope.launch {
696-
Timber.i("setting mode to %s", newValue)
697-
withCol {
698-
// Change this to only change the preference on a state change
699-
newValue.saveToCollection(this@withCol)
700-
}
701-
flowOfCardsOrNotes.update { newValue }
702-
}
690+
fun setCardsOrNotes(newValue: CardsOrNotes) = viewModelScope.launch { browserOptionsRepository.setCardsOrNotes(newValue) }
703691

704-
fun setTruncated(value: Boolean) {
705-
viewModelScope.launch {
706-
flowOfIsTruncated.emit(value)
707-
}
708-
sharedPrefs().edit {
709-
putBoolean("isTruncated", value)
710-
}
711-
}
692+
fun setTruncated(value: Boolean) = viewModelScope.launch { browserOptionsRepository.setIsTruncated(value) }
712693

713-
fun setIgnoreAccents(value: Boolean) {
714-
Timber.d("Setting ignore accent in search to: $value")
715-
viewModelScope.launch {
716-
shouldIgnoreAccents = value
717-
withCol { config.ignoreAccentsInSearch = value }
718-
}
719-
}
694+
fun setIgnoreAccents(value: Boolean) = viewModelScope.launch { browserOptionsRepository.setIgnoreAccentsInSearch(value) }
720695

721696
fun selectAll(): Job? {
722697
if (!_selectedRows.addAll(cards)) return null

0 commit comments

Comments
 (0)