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 3e4ac7941059..d2bca39e09f5 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 @@ -23,6 +23,8 @@ import android.text.style.UnderlineSpan import android.view.Menu import android.view.MenuItem import android.view.View +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager import android.webkit.WebView import android.widget.FrameLayout import androidx.activity.result.contract.ActivityResultContracts @@ -30,6 +32,7 @@ import androidx.annotation.StringRes import androidx.appcompat.view.menu.SubMenuBuilder import androidx.appcompat.widget.ActionMenuView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.getSystemService import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat @@ -40,6 +43,8 @@ import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.button.MaterialButton +import com.google.android.material.card.MaterialCardView +import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textview.MaterialTextView import com.ichi2.anki.AbstractFlashcardViewer.Companion.RESULT_NO_MORE_CARDS import com.ichi2.anki.CollectionManager @@ -92,6 +97,7 @@ import com.ichi2.anki.utils.ext.collectLatestIn import com.ichi2.anki.utils.ext.menu import com.ichi2.anki.utils.ext.removeSubMenu import com.ichi2.anki.utils.ext.sharedPrefs +import com.ichi2.anki.utils.ext.window import com.ichi2.libanki.sched.Counts import kotlinx.coroutines.launch @@ -107,7 +113,13 @@ class ReviewerFragment : get() = requireView().findViewById(R.id.webview) override val baseSnackbarBuilder: SnackbarBuilder = { - anchorView = this@ReviewerFragment.view?.findViewById(R.id.buttons_area) + val typeAnswerContainer = this@ReviewerFragment.view?.findViewById(R.id.type_answer_container) + anchorView = + if (typeAnswerContainer?.isVisible == true) { + typeAnswerContainer + } else { + this@ReviewerFragment.view?.findViewById(R.id.buttons_area) + } } override fun onStop() { @@ -236,10 +248,48 @@ class ReviewerFragment : easyButton.setAnswerButtonNextTime(R.string.ease_button_easy, times?.easy) } + // TODO keep text after configuration changes + val typeAnswerContainer = view.findViewById(R.id.type_answer_container) + val typeAnswerEditText = view.findViewById(R.id.type_answer_edit_text) + typeAnswerEditText.setOnEditorActionListener { editTextView, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE) { + viewModel.onShowAnswer(editTextView.text.toString()) + return@setOnEditorActionListener true + } + false + } + typeAnswerEditText.setOnFocusChangeListener { editTextView, hasFocus -> + val insetsController = WindowInsetsControllerCompat(window, editTextView) + if (hasFocus) { + insetsController.show(WindowInsetsCompat.Type.ime()) + } else { + insetsController.hide(WindowInsetsCompat.Type.ime()) + } + } + val autoFocusTypeAnswer = sharedPrefs().getBoolean(getString(R.string.type_in_answer_focus_key), true) + viewModel.typeInAnswerFlow.collectIn(lifecycleScope) { typeInAnswer -> + typeAnswerEditText.text = null + if (typeInAnswer == null) { + typeAnswerContainer.isVisible = false + return@collectIn + } + typeAnswerContainer.isVisible = true + typeAnswerEditText.apply { + if (imeHintLocales != typeInAnswer.imeHintLocales) { + imeHintLocales = typeInAnswer.imeHintLocales + context?.getSystemService()?.restartInput(this) + } + if (autoFocusTypeAnswer) { + requestFocus() + } + } + } + val showAnswerButton = view.findViewById(R.id.show_answer).apply { setOnClickListener { - viewModel.onShowAnswer() + val typedAnswer = typeAnswerEditText?.text?.toString() + viewModel.onShowAnswer(typedAnswer = typedAnswer) } } val answerButtonsLayout = view.findViewById(R.id.answer_buttons) 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 d7e8b129989f..4773afb73dce 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 @@ -37,6 +37,7 @@ import com.ichi2.anki.pages.CardInfoDestination import com.ichi2.anki.pages.DeckOptionsDestination import com.ichi2.anki.preferences.getShowIntervalOnButtons import com.ichi2.anki.previewer.CardViewerViewModel +import com.ichi2.anki.previewer.TypeInAnswer import com.ichi2.anki.reviewer.CardSide import com.ichi2.anki.servicelayer.MARKED_TAG import com.ichi2.anki.servicelayer.NoteService @@ -78,6 +79,7 @@ class ReviewerViewModel( val undoLabelFlow = MutableStateFlow(null) val redoLabelFlow = MutableStateFlow(null) val countsFlow = MutableStateFlow(Counts() to Counts.Queue.NEW) + val typeInAnswerFlow = MutableStateFlow(null) override val server = AnkiServer(this).also { it.start() } private val stateMutationKey = TimeManager.time.intTimeMS().toString() @@ -141,13 +143,13 @@ class ReviewerViewModel( } } - fun onShowAnswer() { + fun onShowAnswer(typedAnswer: String? = null) { launchCatchingIO { while (!statesMutated) { delay(50) } updateNextTimes() - showAnswer() + showAnswer(typedAnswer) loadAndPlaySounds(CardSide.ANSWER) if (!autoAdvance.shouldWaitForAudio()) { autoAdvance.onShowAnswer() @@ -417,11 +419,19 @@ class ReviewerViewModel( countsFlow.emit(state.counts to state.countsIndex) } - // TODO override suspend fun typeAnsFilter( text: String, typedAnswer: String?, - ): String = text + ): String { + val typeInAnswer = TypeInAnswer.getInstance(currentCard.await(), text) + return if (showingAnswer.value) { + typeInAnswerFlow.emit(null) + typeInAnswer?.answerFilter(typedAnswer ?: "") ?: text + } else { + typeInAnswerFlow.emit(typeInAnswer) + TypeInAnswer.removeTypeAnswerTags(text) + } + } private suspend fun updateUndoAndRedoLabels() { undoLabelFlow.emit(withCol { undoLabel() }) diff --git a/AnkiDroid/src/main/res/layout/reviewer2.xml b/AnkiDroid/src/main/res/layout/reviewer2.xml index 6a231cb24a9d..a492f77cccd0 100644 --- a/AnkiDroid/src/main/res/layout/reviewer2.xml +++ b/AnkiDroid/src/main/res/layout/reviewer2.xml @@ -74,7 +74,7 @@ android:layout_marginHorizontal="8dp" android:layout_marginBottom="4dp" app:layout_constraintTop_toBottomOf="@id/appbar" - app:layout_constraintBottom_toTopOf="@id/buttons_area" + app:layout_constraintBottom_toTopOf="@id/type_answer_container" style="@style/CardView.ViewerStyle"> + + + + + + + + + + +