diff --git a/AnkiDroid/src/main/assets/scripts/ankidroid.js b/AnkiDroid/src/main/assets/scripts/ankidroid.js index 9f213874a724..84baa7e585aa 100644 --- a/AnkiDroid/src/main/assets/scripts/ankidroid.js +++ b/AnkiDroid/src/main/assets/scripts/ankidroid.js @@ -23,11 +23,12 @@ globalThis.ankidroid.showAllHints = function () { }; /** - * @param {InputEvent} event - the oninput event of the type answer + * @param {KeyboardEvent} event - the onkeydown event of the type answer */ -globalThis.ankidroid.onTypeAnswerInput = function (event) { - const encodedValue = encodeURIComponent(event.target.value); - window.location.href = `ankidroid://typeinput/${encodedValue}`; +globalThis.ankidroid.onTypeAnswerKeyDown = function (event) { + if (event.key === "Enter") { + window.location.href = `ankidroid://show-answer`; + } }; document.addEventListener("focusin", event => { 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 d6c68cec2989..97146928ebe3 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 @@ -48,7 +48,6 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding -import androidx.core.widget.addTextChangedListener import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.commit import androidx.fragment.app.viewModels @@ -208,7 +207,7 @@ class ReviewerFragment : } } - viewModel.statesMutationEval.collectIn(lifecycleScope) { eval -> + viewModel.statesMutationEvalFlow.collectIn(lifecycleScope) { eval -> webView.evaluateJavascript(eval) { viewModel.onStateMutationCallback() } @@ -288,7 +287,7 @@ class ReviewerFragment : val typeAnswerContainer = view.findViewById(R.id.type_answer_container) val typeAnswerEditText = view.findViewById(R.id.type_answer_edit_text).apply { - setOnEditorActionListener { editTextView, actionId, _ -> + setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_DONE) { viewModel.onShowAnswer() return@setOnEditorActionListener true @@ -303,11 +302,9 @@ class ReviewerFragment : insetsController.hide(WindowInsetsCompat.Type.ime()) } } - addTextChangedListener { editable -> - viewModel.typedAnswer = editable?.toString() ?: "" - } } + val isHtmlTypeAnswerEnabled = Prefs.isHtmlTypeAnswerEnabled lifecycleScope.launch { val autoFocusTypeAnswer = Prefs.autoFocusTypeAnswer lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -317,7 +314,7 @@ class ReviewerFragment : return@collect } - if (Prefs.isHtmlTypeAnswerEnabled) { + if (isHtmlTypeAnswerEnabled) { webView.requestFocus() webView.evaluateJavascript("document.getElementById('typeans').focus();", null) return@collect @@ -341,6 +338,22 @@ class ReviewerFragment : viewModel.onCardUpdatedFlow.flowWithLifecycle(lifecycle).collectIn(lifecycleScope) { typeAnswerEditText.text = null } + + viewModel.onTypedAnswerResultFlow + .flowWithLifecycle(lifecycle) + .collectIn(lifecycleScope) { request -> + if (isHtmlTypeAnswerEnabled) { + val script = """document.getElementById("typeans").value;""" + webView.evaluateJavascript(script) { callback -> + // the retuned string comes with surrounding `"`, so remove it once + val typedAnswer = callback.removeSurrounding("\"") + request.complete(typedAnswer) + } + } else { + val typedAnswer = typeAnswerEditText.text.toString() + request.complete(typedAnswer) + } + } } /** Chooses the input type based on whether the expected answer is a number or text */ @@ -719,7 +732,7 @@ class ReviewerFragment : when (url.host) { "focusin" -> webviewHasFocus = true "focusout" -> webviewHasFocus = false - "typeinput" -> url.path?.substring(1)?.let { viewModel.typedAnswer = it } + "show-answer" -> viewModel.onShowAnswer() } true } 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 cd722fc61234..f2515daa3c10 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 @@ -70,6 +70,7 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.withTimeoutOrNull import org.intellij.lang.annotations.Language import timber.log.Timber @@ -96,6 +97,7 @@ class ReviewerViewModel : val redoLabelFlow = MutableStateFlow(null) val countsFlow = MutableStateFlow(Counts() to Counts.Queue.NEW) val typeAnswerFlow = MutableStateFlow(null) + val onTypedAnswerResultFlow = MutableSharedFlow>() val onCardUpdatedFlow = MutableSharedFlow() val destinationFlow = MutableSharedFlow() val editNoteTagsFlow = MutableSharedFlow() @@ -106,11 +108,11 @@ class ReviewerViewModel : val whiteboardEnabledFlow = MutableStateFlow(false) val replayVoiceFlow = MutableSharedFlow() val timeBoxReachedFlow = MutableSharedFlow() + val statesMutationEvalFlow = MutableSharedFlow() override val server: AnkiServer = AnkiServer(this, StudyScreenRepository.getServerPort()).also { it.start() } private val stateMutationKey = TimeManager.time.intTimeMS().toString() - val statesMutationEval = MutableSharedFlow() - var typedAnswer = "" + private var typedAnswer = "" private val autoAdvance = AutoAdvance(this) private val isHtmlTypeAnswerEnabled = Prefs.isHtmlTypeAnswerEnabled @@ -185,6 +187,17 @@ class ReviewerViewModel : while (!statesMutated) { delay(50) } + + val typedAnswerResult = CompletableDeferred() + if (typeAnswerFlow.value != null) { + onTypedAnswerResultFlow.emit(typedAnswerResult) + } else { + typedAnswerResult.complete("") + } + typedAnswer = withTimeoutOrNull(1000L) { + typedAnswerResult.await() + } ?: "" + updateNextTimes() showAnswer() loadAndPlayMedia(CardSide.ANSWER) @@ -415,7 +428,7 @@ class ReviewerViewModel : return } statesMutated = false - statesMutationEval.emit( + statesMutationEvalFlow.emit( "anki.mutateNextCardStates('$stateMutationKey', async (states, customData, ctx) => { $js });", ) } @@ -560,7 +573,7 @@ class ReviewerViewModel : val repl = """
-
""".trimIndent()