Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.ichi2.anki.ui.windows.reviewer

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import anki.collection.OpChanges
import anki.frontend.SetSchedulingStatesRequest
import anki.scheduler.CardAnswer.Rating
Expand Down Expand Up @@ -60,7 +61,10 @@ import com.ichi2.anki.servicelayer.isSuspendNoteAvailable
import com.ichi2.anki.settings.Prefs
import com.ichi2.anki.tryRedo
import com.ichi2.anki.tryUndo
import com.ichi2.anki.ui.windows.reviewer.autoadvance.AnswerAction
import com.ichi2.anki.ui.windows.reviewer.autoadvance.AutoAdvance
import com.ichi2.anki.ui.windows.reviewer.autoadvance.AutoAdvanceAction
import com.ichi2.anki.ui.windows.reviewer.autoadvance.QuestionAction
import com.ichi2.anki.utils.CollectionPreferences
import com.ichi2.anki.utils.Destination
import com.ichi2.anki.utils.ext.answerCard
Expand All @@ -83,7 +87,8 @@ class ReviewerViewModel(
savedStateHandle: SavedStateHandle,
) : CardViewerViewModel(savedStateHandle),
ChangeManager.Subscriber,
BindingProcessor<ReviewerBinding, ViewerAction> {
BindingProcessor<ReviewerBinding, ViewerAction>,
AutoAdvance.ActionListener {
private var queueState: Deferred<CurrentQueueState?> =
asyncIO {
withCol { sched.currentQueueState() }
Expand Down Expand Up @@ -123,7 +128,7 @@ class ReviewerViewModel(
private val stateMutationJs: Deferred<String> = asyncIO { withCol { cardStateCustomizer } }
private var typedAnswer = ""

private val autoAdvance = AutoAdvance(this)
private val autoAdvance = AutoAdvance(viewModelScope, this, currentCard)
private val isHtmlTypeAnswerEnabled = Prefs.isHtmlTypeAnswerEnabled
val answerTimer = AnswerTimer()

Expand Down Expand Up @@ -714,6 +719,18 @@ class ReviewerViewModel(
return true
}

override suspend fun onAutoAdvanceAction(action: AutoAdvanceAction) {
when (action) {
QuestionAction.SHOW_ANSWER -> onShowAnswer()
QuestionAction.SHOW_REMINDER -> actionFeedbackFlow.emit(TR.studyingQuestionTimeElapsed())
AnswerAction.BURY_CARD -> buryCard()
AnswerAction.ANSWER_AGAIN -> answerCard(Rating.AGAIN)
AnswerAction.ANSWER_GOOD -> answerCard(Rating.GOOD)
AnswerAction.ANSWER_HARD -> answerCard(Rating.HARD)
AnswerAction.SHOW_REMINDER -> actionFeedbackFlow.emit(TR.studyingAnswerTimeElapsed())
}
}

// Based in https://github.com/ankitects/anki/blob/1f95d030bbc7ebcc004ffe1e2be2a320c9fe1e94/qt/aqt/reviewer.py#L201
// and https://github.com/ankitects/anki/blob/1f95d030bbc7ebcc004ffe1e2be2a320c9fe1e94/qt/aqt/reviewer.py#L219
override fun opExecuted(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Brayan Oliveira <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.anki.ui.windows.reviewer.autoadvance

import com.ichi2.anki.libanki.DeckConfig
import com.ichi2.anki.libanki.DeckConfig.Companion.ANSWER_ACTION

enum class AnswerAction(
val code: Int,
) : AutoAdvanceAction {
BURY_CARD(0),
ANSWER_AGAIN(1),
ANSWER_GOOD(2),
ANSWER_HARD(3),
SHOW_REMINDER(4),
;

companion object {
fun from(code: Int): AnswerAction = AnswerAction.entries.firstOrNull { it.code == code } ?: BURY_CARD

val DeckConfig.answerAction: AnswerAction
get() = AnswerAction.from(jsonObject.optInt(ANSWER_ACTION))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@
*/
package com.ichi2.anki.ui.windows.reviewer.autoadvance

import anki.scheduler.CardAnswer.Rating
import com.ichi2.anki.CollectionManager.TR
import com.ichi2.anki.asyncIO
import com.ichi2.anki.launchCatchingIO
import com.ichi2.anki.libanki.Card
import com.ichi2.anki.reviewer.AutomaticAnswerAction
import com.ichi2.anki.ui.windows.reviewer.ReviewerViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
* Implementation of the `Auto Advance` deck options
Expand All @@ -36,8 +34,18 @@ import kotlinx.coroutines.delay
* @see AutoAdvanceSettings
*/
class AutoAdvance(
val viewModel: ReviewerViewModel,
private val scope: CoroutineScope,
private val listener: ActionListener,
initialCard: Deferred<Card>,
) {
/**
* Listens to the `Auto Advance` actions set in Deck options,
* which can be either a [QuestionAction] or a [AnswerAction].
*/
fun interface ActionListener {
suspend fun onAutoAdvanceAction(action: AutoAdvanceAction)
}

var isEnabled = false
set(value) {
field = value
Expand All @@ -49,9 +57,8 @@ class AutoAdvance(
private var answerActionJob: Job? = null

private var settings =
viewModel.asyncIO {
val card = viewModel.currentCard.await()
AutoAdvanceSettings.createInstance(card.currentDeckId())
scope.asyncIO {
AutoAdvanceSettings.createInstance(initialCard.await().currentDeckId())
}

private suspend fun durationToShowQuestionFor() = settings.await().durationToShowQuestionFor
Expand All @@ -72,7 +79,7 @@ class AutoAdvance(
fun onCardChange(card: Card) {
cancelQuestionAndAnswerActionJobs()
settings =
viewModel.asyncIO {
scope.asyncIO {
AutoAdvanceSettings.createInstance(card.currentDeckId())
}
}
Expand All @@ -82,12 +89,9 @@ class AutoAdvance(
if (!durationToShowQuestionFor().isPositive() || !isEnabled) return

questionActionJob =
viewModel.launchCatchingIO {
scope.launch {
delay(durationToShowQuestionFor())
when (questionAction()) {
QuestionAction.SHOW_ANSWER -> viewModel.onShowAnswer()
QuestionAction.SHOW_REMINDER -> showReminder(TR.studyingQuestionTimeElapsed())
}
listener.onAutoAdvanceAction(questionAction())
}
}

Expand All @@ -96,21 +100,9 @@ class AutoAdvance(
if (!durationToShowAnswerFor().isPositive() || !isEnabled) return

answerActionJob =
viewModel.launchCatchingIO {
scope.launch {
delay(durationToShowAnswerFor())
when (answerAction()) {
AutomaticAnswerAction.BURY_CARD -> viewModel.buryCard()
AutomaticAnswerAction.ANSWER_AGAIN -> viewModel.answerCard(Rating.AGAIN)
AutomaticAnswerAction.ANSWER_HARD -> viewModel.answerCard(Rating.HARD)
AutomaticAnswerAction.ANSWER_GOOD -> viewModel.answerCard(Rating.GOOD)
AutomaticAnswerAction.SHOW_REMINDER -> showReminder(TR.studyingAnswerTimeElapsed())
}
listener.onAutoAdvanceAction(answerAction())
}
}

private fun showReminder(message: String) {
viewModel.launchCatchingIO {
viewModel.actionFeedbackFlow.emit(message)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 Brayan Oliveira <[email protected]>
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.ichi2.anki.ui.windows.reviewer.autoadvance

/**
* Common interface for all actions triggered by Auto Advance.
* Implemented by [QuestionAction] and [AnswerAction].
*/
sealed interface AutoAdvanceAction
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@ package com.ichi2.anki.ui.windows.reviewer.autoadvance

import com.ichi2.anki.CollectionManager.withCol
import com.ichi2.anki.libanki.DeckId
import com.ichi2.anki.reviewer.AutomaticAnswerAction
import com.ichi2.anki.reviewer.AutomaticAnswerAction.Companion.answerAction
import com.ichi2.anki.ui.windows.reviewer.autoadvance.AnswerAction.Companion.answerAction
import com.ichi2.anki.ui.windows.reviewer.autoadvance.QuestionAction.Companion.questionAction
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.toDuration

data class AutoAdvanceSettings(
val questionAction: QuestionAction,
val answerAction: AutomaticAnswerAction,
val answerAction: AnswerAction,
val durationToShowQuestionFor: Duration,
val durationToShowAnswerFor: Duration,
val waitForAudio: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.ichi2.anki.libanki.DeckConfig.Companion.QUESTION_ACTION

enum class QuestionAction(
val code: Int,
) {
) : AutoAdvanceAction {
SHOW_ANSWER(0),
SHOW_REMINDER(1),
;
Expand Down
Loading