Skip to content

Commit 8d32bd5

Browse files
committed
feat(card-browser): enable new sort order bottom sheet
And remove old implementation Fixes 17732
1 parent f7c4f47 commit 8d32bd5

8 files changed

Lines changed: 86 additions & 435 deletions

File tree

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

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

1919
import android.content.Context
20-
import android.content.DialogInterface
2120
import android.graphics.Color
2221
import android.os.Bundle
2322
import android.text.Spannable
@@ -97,14 +96,14 @@ import com.ichi2.anki.browser.search.CardStateBottomSheetFragment
9796
import com.ichi2.anki.browser.search.FlagsBottomSheetFragment
9897
import com.ichi2.anki.browser.search.SearchRequest
9998
import com.ichi2.anki.browser.search.SearchString
99+
import com.ichi2.anki.browser.search.SortOrderBottomSheetFragment
100100
import com.ichi2.anki.browser.search.StandardSearchFragment
101101
import com.ichi2.anki.browser.search.formatChipDescription
102102
import com.ichi2.anki.browser.search.iconRes
103103
import com.ichi2.anki.browser.search.savedFilters
104104
import com.ichi2.anki.common.annotations.NeedsTest
105105
import com.ichi2.anki.common.utils.annotation.KotlinCleanup
106106
import com.ichi2.anki.dialogs.BrowserOptionsDialog
107-
import com.ichi2.anki.dialogs.CardBrowserOrderDialog
108107
import com.ichi2.anki.dialogs.DeckSelectionDialog
109108
import com.ichi2.anki.dialogs.DeckSelectionDialog.DeckSelectionListener
110109
import com.ichi2.anki.dialogs.SimpleMessageDialog
@@ -119,7 +118,6 @@ import com.ichi2.anki.libanki.undoAvailable
119118
import com.ichi2.anki.libanki.undoLabel
120119
import com.ichi2.anki.model.CardStateFilter
121120
import com.ichi2.anki.model.CardsOrNotes.CARDS
122-
import com.ichi2.anki.model.LegacySortType
123121
import com.ichi2.anki.model.SelectableDeck
124122
import com.ichi2.anki.observability.ChangeManager
125123
import com.ichi2.anki.observability.undoableOp
@@ -1007,11 +1005,11 @@ class CardBrowserFragment :
10071005
searchViewModel.syncState(search)
10081006
}
10091007

1010-
fun reverseDirectionChanged(direction: ReverseDirection) {
1011-
sortChip?.scaleY = if (!direction.orderAsc) 1.0f else -1.0f
1008+
fun reverseDirectionChanged(reverse: ReverseDirection?) {
1009+
sortChip?.scaleY = if (reverse == false || reverse == null) 1.0f else -1.0f
10121010
}
10131011

1014-
activityViewModel.reverseDirectionFlow.launchCollectionInLifecycleScope(::reverseDirectionChanged)
1012+
activityViewModel.flowOfReverseDirection.launchCollectionInLifecycleScope(::reverseDirectionChanged)
10151013
activityViewModel.flowOfIsTruncated.launchCollectionInLifecycleScope(::onIsTruncatedChanged)
10161014
activityViewModel.flowOfSelectedRows.launchCollectionInLifecycleScope(::onSelectedRowsChanged)
10171015
activityViewModel.flowOfActiveColumns.launchCollectionInLifecycleScope(::onColumnsChanged)
@@ -1400,15 +1398,12 @@ class CardBrowserFragment :
14001398
}
14011399
}
14021400

1403-
fun changeDisplayOrder() {
1404-
showDialogFragment(
1405-
// TODO: move this into the ViewModel
1406-
CardBrowserOrderDialog.newInstance { dialog: DialogInterface, which: Int ->
1407-
dialog.dismiss()
1408-
activityViewModel.changeCardOrder(LegacySortType.fromCardBrowserLabelIndex(which))
1409-
},
1410-
)
1411-
}
1401+
fun changeDisplayOrder() =
1402+
launchCatchingTask {
1403+
SortOrderBottomSheetFragment
1404+
.createInstance(cardsOrNotes = activityViewModel.cardsOrNotes)
1405+
.show(childFragmentManager)
1406+
}
14121407

14131408
fun updateFlagForSelectedRows(flag: Flag) =
14141409
launchCatchingTask {

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

Lines changed: 21 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,12 @@ import com.ichi2.anki.model.CardStateFilter
7070
import com.ichi2.anki.model.CardsOrNotes
7171
import com.ichi2.anki.model.CardsOrNotes.CARDS
7272
import com.ichi2.anki.model.CardsOrNotes.NOTES
73-
import com.ichi2.anki.model.LegacySortType
7473
import com.ichi2.anki.model.SelectableDeck
7574
import com.ichi2.anki.model.SortType
7675
import com.ichi2.anki.observability.ChangeManager
7776
import com.ichi2.anki.observability.undoableOp
7877
import com.ichi2.anki.pages.CardInfoDestination
7978
import com.ichi2.anki.preferences.SharedPreferencesProvider
80-
import com.ichi2.anki.settings.Prefs
81-
import com.ichi2.anki.settings.PrefsRepository
8279
import com.ichi2.anki.utils.ext.currentCardBrowse
8380
import com.ichi2.anki.utils.ext.getCardOrNull
8481
import com.ichi2.anki.utils.ext.ignoreAccentsInSearch
@@ -94,7 +91,6 @@ import kotlinx.coroutines.flow.SharingStarted
9491
import kotlinx.coroutines.flow.StateFlow
9592
import kotlinx.coroutines.flow.combine
9693
import kotlinx.coroutines.flow.combineTransform
97-
import kotlinx.coroutines.flow.filter
9894
import kotlinx.coroutines.flow.flattenMerge
9995
import kotlinx.coroutines.flow.flowOf
10096
import kotlinx.coroutines.flow.launchIn
@@ -117,6 +113,11 @@ import java.util.Collections
117113
import kotlin.math.max
118114
import kotlin.math.min
119115

116+
/**
117+
* Whether the current sort is reversed (descending). `true` if reversed.
118+
*/
119+
typealias ReverseDirection = Boolean
120+
120121
// TODO: move the tag computation to ViewModel
121122

122123
/**
@@ -129,7 +130,6 @@ import kotlin.math.min
129130
* @param isFragmented `true` if a NoteEditor side panel is displayed (x-large displays)
130131
* @param manualInit test-only: defer `initCompleted` until `manualInit()` is called
131132
*/
132-
@NeedsTest("reverseDirectionFlow/sortTypeFlow are not updated on .launch { }")
133133
@NeedsTest("columIndex1/2 config is not not updated on init")
134134
@NeedsTest("13442: selected deck is not changed, as this affects the reviewer")
135135
@NeedsTest("search is called after launch()")
@@ -143,8 +143,6 @@ class CardBrowserViewModel(
143143
private val manualInit: Boolean = false,
144144
) : ViewModel(),
145145
SharedPreferencesProvider by preferences {
146-
private val prefs: PrefsRepository = Prefs
147-
148146
// TODO: abstract so we can use a `Context` and `pref_display_filenames_in_browser_key`
149147
val showMediaFilenames = sharedPrefs().getBoolean("card_browser_show_media_filenames", false)
150148

@@ -193,11 +191,12 @@ class CardBrowserViewModel(
193191

194192
val flowOfScrollRequest = MutableSharedFlow<RowSelection>()
195193

196-
private val sortTypeFlow = MutableStateFlow(LegacySortType.NO_SORTING)
197-
val order get() = sortTypeFlow.value
198-
199-
val reverseDirectionFlow = MutableStateFlow(ReverseDirection(orderAsc = false))
200-
val orderAsc get() = reverseDirectionFlow.value.orderAsc
194+
/**
195+
* Whether the current sort is reversed (descending).
196+
*
197+
* `null` when no sort is applied ([SortType.NoOrdering]).
198+
*/
199+
val flowOfReverseDirection: MutableStateFlow<ReverseDirection?> = MutableStateFlow(null)
201200

202201
/**
203202
* A map from column backend key to backend column definition
@@ -498,16 +497,6 @@ class CardBrowserViewModel(
498497
launchSearchForCards(cardOrNoteIdsToSelect = ids)
499498
}.launchIn(viewModelScope)
500499

501-
reverseDirectionFlow
502-
.ignoreValuesFromViewModelLaunch()
503-
.onEach { newValue -> withCol { newValue.updateConfig(config) } }
504-
.launchIn(viewModelScope)
505-
506-
sortTypeFlow
507-
.ignoreValuesFromViewModelLaunch()
508-
.onEach { sortType -> withCol { sortType.save(config, prefs) } }
509-
.launchIn(viewModelScope)
510-
511500
flowOfCardsOrNotes
512501
.onEach { cardsOrNotes ->
513502
Timber.d("loading columns for %s mode", cardsOrNotes)
@@ -525,10 +514,8 @@ class CardBrowserViewModel(
525514
val cardsOrNotes = withCol { CardsOrNotes.fromCollection(this@withCol) }
526515
flowOfCardsOrNotes.update { cardsOrNotes }
527516

528-
withCol {
529-
sortTypeFlow.update { LegacySortType.fromCol(config, cardsOrNotes, prefs) }
530-
reverseDirectionFlow.update { ReverseDirection.fromConfig(config) }
531-
}
517+
flowOfReverseDirection.update { (SortType.build(cardsOrNotes) as? SortType.CollectionOrdering)?.reverse }
518+
532519
Timber.i("initCompleted")
533520

534521
if (!manualInit) {
@@ -869,41 +856,17 @@ class CardBrowserViewModel(
869856
viewModelScope.launch {
870857
Timber.i("setting sort type: %s", sortType)
871858

872-
// Temporarily update legacy flows
873-
sortTypeFlow.update { sortType.toLegacy() }
874-
sortType.toLegacyReverse()?.let { newValue ->
875-
reverseDirectionFlow.update { newValue }
876-
}
877-
878859
sortType.save(cardsOrNotes)
879860

880-
launchSearchForCards()
881-
}
882-
883-
fun changeCardOrder(which: LegacySortType) {
884-
val changeType =
885-
when {
886-
which != order -> ChangeCardOrder.OrderChange(which)
887-
// if the same element is selected again, reverse the order
888-
which != LegacySortType.NO_SORTING -> ChangeCardOrder.DirectionChange
889-
else -> null
890-
} ?: return
891-
892-
Timber.i("updating order: %s", changeType)
893-
894-
when (changeType) {
895-
is ChangeCardOrder.OrderChange -> {
896-
sortTypeFlow.update { which }
897-
reverseDirectionFlow.update { ReverseDirection(orderAsc = false) }
898-
launchSearchForCards()
899-
}
900-
ChangeCardOrder.DirectionChange -> {
901-
reverseDirectionFlow.update { ReverseDirection(orderAsc = !orderAsc) }
902-
cards.reverse()
903-
viewModelScope.launch { flowOfSearchState.emit(SearchState.Completed) }
861+
flowOfReverseDirection.update {
862+
when (sortType) {
863+
is SortType.NoOrdering -> null
864+
is SortType.CollectionOrdering -> sortType.reverse
865+
}
904866
}
867+
868+
launchSearchForCards()
905869
}
906-
}
907870

908871
/**
909872
* Updates the backend with a new collection of columns
@@ -1116,9 +1079,6 @@ class CardBrowserViewModel(
11161079
return if (searchAdded) SaveSearchResult.SUCCESS else SaveSearchResult.ALREADY_EXISTS
11171080
}
11181081

1119-
/** Ignores any values before [initCompleted] is set */
1120-
private fun <T> Flow<T>.ignoreValuesFromViewModelLaunch(): Flow<T> = this.filter { initCompleted }
1121-
11221082
/**
11231083
* Sets the filter query (legacy): 'is:suspended'
11241084
*/
@@ -1316,7 +1276,7 @@ class CardBrowserViewModel(
13161276
) {
13171277
val searchString = withCol { searchRequestFlow.value.toSearchString().getOrThrow() }
13181278
flowOfSearchState.emit(SearchState.Searching)
1319-
val sortOrder = order.toSortOrder()
1279+
val sortOrder = SortType.buildSortOrder()
13201280
Timber.d("performing search: '%s'; order: %s", searchString, sortOrder)
13211281
val cards = com.ichi2.anki.searchForRows(searchString, sortOrder, cardsOrNotes)
13221282
Timber.d("Search returned %d card(s)", cards.size)
@@ -1498,14 +1458,6 @@ class CardBrowserViewModel(
14981458
val count: Int,
14991459
)
15001460

1501-
private sealed interface ChangeCardOrder {
1502-
data class OrderChange(
1503-
val sortType: LegacySortType,
1504-
) : ChangeCardOrder
1505-
1506-
data object DirectionChange : ChangeCardOrder
1507-
}
1508-
15091461
sealed class ChangeMultiSelectMode : Parcelable {
15101462
val resultedInMultiSelect: Boolean get() =
15111463
when (this) {

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

Lines changed: 0 additions & 38 deletions
This file was deleted.

AnkiDroid/src/main/java/com/ichi2/anki/dialogs/CardBrowserOrderDialog.kt

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)