From b72999a0cb2dd01c09cc3c31f862a7e2af93a39a Mon Sep 17 00:00:00 2001 From: Brayan Oliveira <69634269+brayandso@users.noreply.github.com> Date: Mon, 27 May 2024 21:09:00 -0300 Subject: [PATCH] feat(new reviewer): undo and redo --- .../ui/windows/reviewer/ReviewerFragment.kt | 17 ++++++ .../ui/windows/reviewer/ReviewerViewModel.kt | 55 ++++++++++++++++++- AnkiDroid/src/main/res/menu/reviewer2.xml | 12 ++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt index d8a6b5bcc9da..59e14f712991 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt @@ -34,6 +34,7 @@ import androidx.lifecycle.lifecycleScope import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.button.MaterialButton import com.ichi2.anki.AbstractFlashcardViewer.Companion.RESULT_NO_MORE_CARDS +import com.ichi2.anki.CollectionManager import com.ichi2.anki.Flag import com.ichi2.anki.NoteEditor import com.ichi2.anki.R @@ -120,6 +121,7 @@ class ReviewerFragment : R.id.action_edit -> launchEditNote() R.id.action_mark -> viewModel.toggleMark() R.id.action_open_deck_options -> launchDeckOptions() + R.id.action_redo -> viewModel.redo() R.id.action_suspend_card -> viewModel.suspendCard() R.id.action_suspend_note -> viewModel.suspendNote() R.id.action_flag_zero -> viewModel.setFlag(Flag.NONE) @@ -130,6 +132,7 @@ class ReviewerFragment : R.id.action_flag_five -> viewModel.setFlag(Flag.PINK) R.id.action_flag_six -> viewModel.setFlag(Flag.TURQUOISE) R.id.action_flag_seven -> viewModel.setFlag(Flag.PURPLE) + R.id.action_undo -> viewModel.undo() R.id.user_action_1 -> viewModel.userAction(1) R.id.user_action_2 -> viewModel.userAction(2) R.id.user_action_3 -> viewModel.userAction(3) @@ -230,6 +233,20 @@ class ReviewerFragment : .collectLatestIn(lifecycleScope) { flagCode -> menu.findItem(R.id.action_flag).setIcon(Flag.fromCode(flagCode).drawableRes) } + + val undoItem = menu.findItem(R.id.action_undo) + viewModel.undoLabelFlow.flowWithLifecycle(lifecycle) + .collectLatestIn(lifecycleScope) { label -> + undoItem.title = label ?: CollectionManager.TR.undoUndo() + undoItem.isEnabled = label != null + } + + val redoItem = menu.findItem(R.id.action_redo) + viewModel.redoLabelFlow.flowWithLifecycle(lifecycle) + .collectLatestIn(lifecycleScope) { label -> + redoItem.title = label ?: CollectionManager.TR.undoRedo() + redoItem.isEnabled = label != null + } } private val noteEditorLauncher = diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt index d00110247a4e..a1edf476cf11 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerViewModel.kt @@ -19,6 +19,7 @@ import androidx.activity.result.ActivityResult import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory +import anki.collection.OpChanges import anki.frontend.SetSchedulingStatesRequest import com.ichi2.anki.CollectionManager import com.ichi2.anki.CollectionManager.withCol @@ -40,9 +41,12 @@ import com.ichi2.anki.servicelayer.MARKED_TAG import com.ichi2.anki.servicelayer.NoteService import com.ichi2.anki.servicelayer.isBuryNoteAvailable import com.ichi2.anki.servicelayer.isSuspendNoteAvailable +import com.ichi2.libanki.ChangeManager import com.ichi2.libanki.hasTag import com.ichi2.libanki.note +import com.ichi2.libanki.redo import com.ichi2.libanki.sched.CurrentQueueState +import com.ichi2.libanki.undo import com.ichi2.libanki.undoableOp import com.ichi2.libanki.utils.TimeManager import kotlinx.coroutines.CompletableDeferred @@ -53,7 +57,8 @@ import kotlinx.coroutines.flow.MutableStateFlow class ReviewerViewModel(cardMediaPlayer: CardMediaPlayer) : CardViewerViewModel(cardMediaPlayer), - PostRequestHandler { + PostRequestHandler, + ChangeManager.Subscriber { private var queueState: Deferred = asyncIO { // this assumes that the Reviewer won't be launched if there isn't a queueState @@ -68,6 +73,8 @@ class ReviewerViewModel(cardMediaPlayer: CardMediaPlayer) : val actionFeedbackFlow = MutableSharedFlow() val canBuryNoteFlow = MutableStateFlow(true) val canSuspendNoteFlow = MutableStateFlow(true) + val undoLabelFlow = MutableStateFlow(null) + val redoLabelFlow = MutableStateFlow(null) private val server = AnkiServer(this).also { it.start() } private val stateMutationKey = TimeManager.time.intTimeMS().toString() @@ -88,6 +95,13 @@ class ReviewerViewModel(cardMediaPlayer: CardMediaPlayer) : */ private var statesMutated = true + init { + ChangeManager.subscribe(this) + launchCatchingIO { + updateUndoAndRedoLabels() + } + } + /* ********************************************************************************************* ************************ Public methods: meant to be used by the View ************************** ********************************************************************************************* */ @@ -230,6 +244,36 @@ class ReviewerViewModel(cardMediaPlayer: CardMediaPlayer) : } } + fun undo() { + launchCatchingIO { + val changes = undoableOp { + undo() + } + val message = if (changes.operation.isEmpty()) { + CollectionManager.TR.actionsNothingToUndo() + } else { + CollectionManager.TR.undoActionUndone(changes.operation) + } + actionFeedbackFlow.emit(message) + updateCurrentCard() + } + } + + fun redo() { + launchCatchingIO { + val changes = undoableOp { + redo() + } + val message = if (changes.operation.isEmpty()) { + CollectionManager.TR.actionsNothingToRedo() + } else { + CollectionManager.TR.undoRedoAction(changes.operation) + } + actionFeedbackFlow.emit(message) + updateCurrentCard() + } + } + fun userAction(@Reviewer.UserAction number: Int) { launchCatchingIO { eval.emit("javascript: ankidroid.userAction($number);") @@ -339,6 +383,15 @@ class ReviewerViewModel(cardMediaPlayer: CardMediaPlayer) : return text } + private suspend fun updateUndoAndRedoLabels() { + undoLabelFlow.emit(withCol { undoLabel() }) + redoLabelFlow.emit(withCol { redoLabel() }) + } + + override fun opExecuted(changes: OpChanges, handler: Any?) { + launchCatchingIO { updateUndoAndRedoLabels() } + } + companion object { fun factory(soundPlayer: CardMediaPlayer): ViewModelProvider.Factory { return viewModelFactory { diff --git a/AnkiDroid/src/main/res/menu/reviewer2.xml b/AnkiDroid/src/main/res/menu/reviewer2.xml index b4e74be38fb2..143b40cb0ac3 100644 --- a/AnkiDroid/src/main/res/menu/reviewer2.xml +++ b/AnkiDroid/src/main/res/menu/reviewer2.xml @@ -17,6 +17,18 @@ + +