Skip to content

Commit f80f982

Browse files
committed
Bring up numeric keyboard if answer is a number #1617
1 parent 04ec728 commit f80f982

File tree

4 files changed

+84
-1
lines changed

4 files changed

+84
-1
lines changed

AnkiDroid/src/androidTest/java/com/ichi2/anki/ReviewerFragmentTest.kt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package com.ichi2.anki
1515

16+
import android.text.InputType
1617
import androidx.core.content.edit
1718
import androidx.test.espresso.Espresso.onView
1819
import androidx.test.espresso.action.ViewActions.click
@@ -21,6 +22,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
2122
import androidx.test.espresso.matcher.ViewMatchers.withId
2223
import androidx.test.ext.junit.rules.ActivityScenarioRule
2324
import androidx.test.ext.junit.runners.AndroidJUnit4
25+
import com.google.android.material.textfield.TextInputEditText
2426
import com.ichi2.anki.libanki.Collection
2527
import com.ichi2.anki.preferences.sharedPrefs
2628
import com.ichi2.anki.tests.InstrumentedTest
@@ -40,6 +42,7 @@ import org.junit.Rule
4042
import org.junit.Test
4143
import org.junit.runner.RunWith
4244
import java.util.concurrent.TimeUnit
45+
import kotlin.time.Duration.Companion.seconds
4346

4447
@RunWith(AndroidJUnit4::class)
4548
class ReviewerFragmentTest : InstrumentedTest() {
@@ -118,6 +121,55 @@ class ReviewerFragmentTest : InstrumentedTest() {
118121
ensureAnswerButtonsAreDisplayed()
119122
}
120123

124+
@Test
125+
fun testSelectedKeyboardType() {
126+
setNewReviewer()
127+
closeGetStartedScreenIfExists()
128+
closeBackupCollectionDialogIfExists()
129+
130+
val inputTypeNumber =
131+
InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED
132+
val inputTypeText = InputType.TYPE_CLASS_TEXT
133+
134+
val testValues: List<Pair<String, Int>> =
135+
listOf(
136+
"123" to inputTypeNumber,
137+
"-123.45" to inputTypeNumber,
138+
"123.45" to inputTypeNumber,
139+
"123,45" to inputTypeNumber,
140+
"<b>123</b>" to inputTypeNumber,
141+
"AnkiDroid" to inputTypeText,
142+
"123abc" to inputTypeText,
143+
"" to inputTypeText,
144+
)
145+
146+
testValues.forEachIndexed { index, (typedAnswer, _) ->
147+
addTypedAnswerNote(answer = typedAnswer).firstCard(col).update {
148+
did = col.decks.id("Default$index")
149+
}
150+
}
151+
152+
// Check decks after adding all notes to ensure that the deck list is updated with the new cards
153+
testValues.forEachIndexed { index, (_, expectedInputType) ->
154+
// Ensures that we are in the deckpicker screen to make reviewDeckWithName work
155+
if (index > 0) onView(withId(R.id.back_button)).perform(click())
156+
checkInputType(expectedInputType, index)
157+
}
158+
}
159+
160+
fun checkInputType(
161+
expectedInputType: Int,
162+
index: Int,
163+
) {
164+
reviewDeckWithName("Default$index")
165+
ensureKeyboardIsDisplayed()
166+
onView(withId(R.id.type_answer_edit_text)).check { view, _ ->
167+
val editText = view as TextInputEditText
168+
val inputType = editText.inputType
169+
assertThat(inputType, equalTo(expectedInputType))
170+
}
171+
}
172+
121173
private fun clickShowAnswerAndAnswerGood() {
122174
clickShowAnswer()
123175
ensureAnswerButtonsAreDisplayed()
@@ -128,6 +180,14 @@ class ReviewerFragmentTest : InstrumentedTest() {
128180
onView(withId(R.id.show_answer)).perform(click())
129181
}
130182

183+
private fun ensureKeyboardIsDisplayed() {
184+
onView(withId(R.id.type_answer_edit_text)).checkWithTimeout(
185+
matches(isDisplayed()),
186+
100,
187+
30.seconds.inWholeMilliseconds,
188+
)
189+
}
190+
131191
private fun ensureAnswerButtonsAreDisplayed() {
132192
// We need to wait for the card to fully load to allow enough time for
133193
// the messages to be passed in and out of the WebView when evaluating

AnkiDroid/src/androidTest/java/com/ichi2/anki/tests/InstrumentedTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ abstract class InstrumentedTest {
190190
return n
191191
}
192192

193+
internal fun addTypedAnswerNote(
194+
front: String = "Front",
195+
answer: String = "Answer",
196+
): Note = addNoteUsingNoteTypeName("Basic (type in the answer)", front, answer)
197+
193198
@DuplicatedCode("This is copied from RobolectricTest. This will be refactored into a shared library later")
194199
fun addClozeNote(
195200
text: String,

AnkiDroid/src/main/java/com/ichi2/anki/ui/windows/reviewer/ReviewerFragment.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.content.Intent
2020
import android.hardware.SensorManager
2121
import android.net.Uri
2222
import android.os.Bundle
23+
import android.text.InputType
2324
import android.text.SpannableString
2425
import android.text.style.UnderlineSpan
2526
import android.view.KeyEvent
@@ -76,6 +77,7 @@ import com.ichi2.anki.preferences.reviewer.ReviewerMenuView
7677
import com.ichi2.anki.preferences.reviewer.ViewerAction
7778
import com.ichi2.anki.previewer.CardViewerActivity
7879
import com.ichi2.anki.previewer.CardViewerFragment
80+
import com.ichi2.anki.previewer.TypeAnswer
7981
import com.ichi2.anki.previewer.stdHtml
8082
import com.ichi2.anki.reviewer.BindingMap
8183
import com.ichi2.anki.reviewer.ReviewerBinding
@@ -98,12 +100,13 @@ import com.ichi2.anki.utils.isWindowCompact
98100
import com.ichi2.themes.Themes
99101
import com.ichi2.utils.dp
100102
import com.ichi2.utils.show
103+
import com.ichi2.utils.stripHtml
101104
import com.squareup.seismic.ShakeDetector
102105
import kotlinx.coroutines.Job
103106
import kotlinx.coroutines.delay
104107
import kotlinx.coroutines.launch
108+
import org.jetbrains.annotations.VisibleForTesting
105109
import timber.log.Timber
106-
import java.lang.IllegalArgumentException
107110
import kotlin.math.roundToInt
108111
import kotlin.reflect.jvm.jvmName
109112

@@ -326,6 +329,8 @@ class ReviewerFragment :
326329

327330
typeAnswerContainer.isVisible = true
328331
typeAnswerEditText.apply {
332+
inputType = chooseInputType(typeInAnswer)
333+
329334
if (imeHintLocales != typeInAnswer.imeHintLocales) {
330335
imeHintLocales = typeInAnswer.imeHintLocales
331336
context?.getSystemService<InputMethodManager>()?.restartInput(this)
@@ -342,6 +347,15 @@ class ReviewerFragment :
342347
}
343348
}
344349

350+
/** Chooses the input type based on whether the expected answer is a number or text */
351+
@VisibleForTesting
352+
fun chooseInputType(typeAnswer: TypeAnswer): Int =
353+
if (stripHtml(typeAnswer.expectedAnswer).matches(Regex("^-?\\d+([.,]\\d*)?$"))) {
354+
InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL or InputType.TYPE_NUMBER_FLAG_SIGNED
355+
} else {
356+
InputType.TYPE_CLASS_TEXT
357+
}
358+
345359
private fun resetZoom() {
346360
webView.settings.loadWithOverviewMode = false
347361
webView.settings.loadWithOverviewMode = true

AnkiDroid/src/main/java/com/ichi2/utils/HtmlUtils.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.ichi2.utils
1717

18+
import androidx.core.text.HtmlCompat
1819
import com.ichi2.anki.common.utils.htmlEncode
1920

2021
object HtmlUtils {
@@ -30,3 +31,6 @@ object HtmlUtils {
3031

3132
fun escape(html: String): String = html.htmlEncode()
3233
}
34+
35+
/** Removes HTML tags from a string */
36+
fun stripHtml(html: String): String = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()

0 commit comments

Comments
 (0)