Skip to content

Commit e4f60ca

Browse files
david-allisonmikehardy
authored andcommitted
refactor(image-occlusion): move deck name to ViewModel
I want to make the deck selection conditional on 'ADD' mode
1 parent 14741bd commit e4f60ca

File tree

2 files changed

+54
-24
lines changed

2 files changed

+54
-24
lines changed

AnkiDroid/src/main/java/com/ichi2/anki/pages/ImageOcclusion.kt

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,25 @@ import android.widget.TextView
2525
import androidx.activity.addCallback
2626
import androidx.core.os.bundleOf
2727
import androidx.fragment.app.viewModels
28+
import androidx.lifecycle.Lifecycle
2829
import androidx.lifecycle.lifecycleScope
30+
import androidx.lifecycle.repeatOnLifecycle
2931
import com.google.android.material.appbar.MaterialToolbar
30-
import com.ichi2.anki.CollectionManager.withCol
3132
import com.ichi2.anki.R
3233
import com.ichi2.anki.SingleFragmentActivity
3334
import com.ichi2.anki.common.annotations.NeedsTest
35+
import com.ichi2.anki.common.utils.android.isRobolectric
3436
import com.ichi2.anki.dialogs.DeckSelectionDialog
3537
import com.ichi2.anki.dialogs.DiscardChangesDialog
36-
import com.ichi2.anki.launchCatchingTask
3738
import com.ichi2.anki.model.SelectableDeck
3839
import com.ichi2.anki.pages.viewmodel.ImageOcclusionArgs
3940
import com.ichi2.anki.pages.viewmodel.ImageOcclusionViewModel
4041
import com.ichi2.anki.pages.viewmodel.ImageOcclusionViewModel.Companion.IO_ARGS_KEY
41-
import com.ichi2.anki.requireAnkiActivity
42-
import com.ichi2.anki.selectedDeckIfNotFiltered
4342
import com.ichi2.anki.startDeckSelection
43+
import com.ichi2.utils.HandlerUtils
44+
import kotlinx.coroutines.flow.Flow
4445
import kotlinx.coroutines.launch
46+
import kotlinx.coroutines.runBlocking
4547
import timber.log.Timber
4648

4749
/**
@@ -80,11 +82,6 @@ class ImageOcclusion :
8082
deckNameView = view.findViewById(R.id.deck_name)
8183
deckNameView.setOnClickListener { startDeckSelection(all = false, filtered = false, skipEmptyDefault = false) }
8284

83-
requireAnkiActivity().launchCatchingTask {
84-
val selectedDeck = withCol { selectedDeckIfNotFiltered() }
85-
deckNameView.text = selectedDeck.name
86-
}
87-
8885
@NeedsTest("#17393 verify that the added image occlusion cards are put in the correct deck")
8986
view.findViewById<MaterialToolbar>(R.id.toolbar).setOnMenuItemClickListener {
9087
if (it.itemId == R.id.action_save) {
@@ -93,6 +90,8 @@ class ImageOcclusion :
9390
}
9491
return@setOnMenuItemClickListener true
9592
}
93+
94+
setupFlows()
9695
}
9796

9897
override fun onCreateWebViewClient(savedInstanceState: Bundle?): PageWebViewClient =
@@ -113,13 +112,7 @@ class ImageOcclusion :
113112
override fun onDeckSelected(deck: SelectableDeck?) {
114113
if (deck == null) return
115114
require(deck is SelectableDeck.Deck)
116-
deckNameView.text = deck.name
117-
val deckDidChange = viewModel.handleDeckSelection(deck.deckId)
118-
if (deckDidChange) {
119-
viewLifecycleOwner.lifecycleScope.launch {
120-
withCol { decks.select(viewModel.selectedDeckId) }
121-
}
122-
}
115+
viewModel.handleDeckSelection(deck.deckId)
123116
}
124117

125118
// HACK: detect a successful save; #19443 will provide a better method
@@ -134,6 +127,29 @@ class ImageOcclusion :
134127
}
135128
}
136129

130+
private fun setupFlows() {
131+
fun onDeckNameChanged(name: String) {
132+
deckNameView.text = name
133+
}
134+
135+
viewModel.deckNameFlow.launchCollectionInLifecycleScope(::onDeckNameChanged)
136+
}
137+
138+
// TODO: Move this to an extension method once we have context parameters
139+
private fun <T> Flow<T>.launchCollectionInLifecycleScope(block: suspend (T) -> Unit) {
140+
lifecycleScope.launch {
141+
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
142+
this@launchCollectionInLifecycleScope.collect {
143+
if (isRobolectric) {
144+
HandlerUtils.postOnNewHandler { runBlocking { block(it) } }
145+
} else {
146+
block(it)
147+
}
148+
}
149+
}
150+
}
151+
}
152+
137153
companion object {
138154
/**
139155
* @param args arguments for either adding or editing a note

AnkiDroid/src/main/java/com/ichi2/anki/pages/viewmodel/ImageOcclusionViewModel.kt

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ import android.os.Parcelable
2121
import androidx.lifecycle.SavedStateHandle
2222
import androidx.lifecycle.ViewModel
2323
import androidx.lifecycle.viewModelScope
24-
import com.ichi2.anki.CollectionManager
24+
import com.ichi2.anki.CollectionManager.withCol
2525
import com.ichi2.anki.libanki.DeckId
2626
import com.ichi2.anki.libanki.NoteId
2727
import com.ichi2.anki.libanki.NoteTypeId
2828
import com.ichi2.anki.pages.ImageOcclusion
29+
import kotlinx.coroutines.flow.MutableStateFlow
30+
import kotlinx.coroutines.flow.launchIn
31+
import kotlinx.coroutines.flow.map
32+
import kotlinx.coroutines.flow.onEach
2933
import kotlinx.coroutines.launch
3034
import kotlinx.parcelize.Parcelize
3135
import org.json.JSONObject
@@ -94,31 +98,41 @@ class ImageOcclusionViewModel(
9498
val args: ImageOcclusionArgs =
9599
checkNotNull(savedStateHandle[IO_ARGS_KEY]) { "$IO_ARGS_KEY required" }
96100

97-
var selectedDeckId: DeckId = args.originalDeckId
101+
var selectedDeckIdFlow = MutableStateFlow(args.originalDeckId)
102+
103+
val deckNameFlow =
104+
selectedDeckIdFlow
105+
.map { did -> withCol { decks.name(did) } }
106+
107+
init {
108+
// if we are in 'add' mode, thr current deck is used to add the note.
109+
// This is reverted in 'onSaveOperationCompleted'
110+
selectedDeckIdFlow
111+
.onEach { withCol { decks.select(it) } }
112+
.launchIn(viewModelScope)
113+
}
98114

99115
/**
100116
* Handles the selection of a new deck.
101117
*
102118
* @param deckId The [DeckId] object representing the selected deck. Can be null if no deck is selected.
103119
*/
104-
fun handleDeckSelection(deckId: DeckId): Boolean {
105-
if (deckId == selectedDeckId) return false
106-
selectedDeckId = deckId
107-
return true
120+
fun handleDeckSelection(deckId: DeckId) {
121+
selectedDeckIdFlow.value = deckId
108122
}
109123

110124
/**
111125
* Executed when the 'save' operation is completed, before the UI receives the response
112126
*/
113127
fun onSaveOperationCompleted() {
114128
Timber.i("save operation completed")
115-
if (args.originalDeckId == selectedDeckId) return
129+
if (args.originalDeckId == selectedDeckIdFlow.value) return
116130
// reset to the previous deck that the backend "saw" as selected, this
117131
// avoids other screens unexpectedly having their working decks modified(
118132
// most important being the Reviewer where the user would find itself
119133
// studying another deck after editing a note with changing the deck)
120134
viewModelScope.launch {
121-
CollectionManager.withCol { backend.setCurrentDeck(args.originalDeckId) }
135+
withCol { backend.setCurrentDeck(args.originalDeckId) }
122136
}
123137
}
124138

0 commit comments

Comments
 (0)