diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c324a1a7119..76102786288 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -213,6 +213,7 @@ + diff --git a/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt b/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt index 6641b39e76c..14645028aa5 100644 --- a/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt +++ b/app/src/main/java/org/oppia/android/app/activity/ActivityComponentImpl.kt @@ -64,6 +64,7 @@ import org.oppia.android.app.testing.DragDropTestActivity import org.oppia.android.app.testing.DrawableBindingAdaptersTestActivity import org.oppia.android.app.testing.ExplorationInjectionActivity import org.oppia.android.app.testing.ExplorationTestActivity +import org.oppia.android.app.testing.FractionInputInteractionViewTestActivity import org.oppia.android.app.testing.HomeFragmentTestActivity import org.oppia.android.app.testing.HomeTestActivity import org.oppia.android.app.testing.HtmlParserTestActivity @@ -139,6 +140,7 @@ interface ActivityComponentImpl : fun inject(faqSingleActivity: FAQSingleActivity) fun inject(forceNetworkTypeActivity: ForceNetworkTypeActivity) fun inject(forceNetworkTypeTestActivity: ForceNetworkTypeTestActivity) + fun inject(fractionInputInteractionViewTestActivity: FractionInputInteractionViewTestActivity) fun inject(helpActivity: HelpActivity) fun inject(homeActivity: HomeActivity) fun inject(homeFragmentTestActivity: HomeFragmentTestActivity) diff --git a/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt b/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt index 731d26e0590..81cd9e14035 100644 --- a/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt +++ b/app/src/main/java/org/oppia/android/app/parser/FractionParsingUiError.kt @@ -20,7 +20,10 @@ enum class FractionParsingUiError(@StringRes private var error: Int?) { DIVISION_BY_ZERO(error = R.string.fraction_error_divide_by_zero), /** Corresponds to [FractionParsingError.NUMBER_TOO_LONG]. */ - NUMBER_TOO_LONG(error = R.string.fraction_error_larger_than_seven_digits); + NUMBER_TOO_LONG(error = R.string.fraction_error_larger_than_seven_digits), + + /** Corresponds to [FractionParsingError.EMPTY_INPUT]. */ + EMPTY_INPUT(error = R.string.fraction_error_empty_input); /** * Returns the string corresponding to this error's string resources, or null if there is none. @@ -39,6 +42,7 @@ enum class FractionParsingUiError(@StringRes private var error: Int?) { FractionParsingError.INVALID_FORMAT -> INVALID_FORMAT FractionParsingError.DIVISION_BY_ZERO -> DIVISION_BY_ZERO FractionParsingError.NUMBER_TOO_LONG -> NUMBER_TOO_LONG + FractionParsingError.EMPTY_INPUT -> EMPTY_INPUT } } } diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt index 22d42a74744..193248effe7 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt @@ -43,12 +43,17 @@ class FractionInteractionViewModel private constructor( override fun onPropertyChanged(sender: Observable, propertyId: Int) { errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( pendingAnswerError, - answerText.isNotEmpty() + true // Allow submit on empty answer. ) } } errorMessage.addOnPropertyChangedCallback(callback) isAnswerAvailable.addOnPropertyChangedCallback(callback) + // Force-update the UI to reflect the state of the errorMessage and isAnswerAvailable property: + errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( + /* pendingAnswerError= */null, + /* inputAnswerAvailable= */true + ) } override fun getPendingAnswer(): UserAnswer = UserAnswer.newBuilder().apply { @@ -64,23 +69,25 @@ class FractionInteractionViewModel private constructor( /** It checks the pending error for the current fraction input, and correspondingly updates the error string based on the specified error category. */ override fun checkPendingAnswerError(category: AnswerErrorCategory): String? { - if (answerText.isNotEmpty()) { - when (category) { - AnswerErrorCategory.REAL_TIME -> { + when (category) { + AnswerErrorCategory.REAL_TIME -> { + if (answerText.isNotEmpty()) { pendingAnswerError = FractionParsingUiError.createFromParsingError( fractionParser.getRealTimeAnswerError(answerText.toString()) ).getErrorMessageFromStringRes(resourceHandler) + } else { + pendingAnswerError = null } - AnswerErrorCategory.SUBMIT_TIME -> { - pendingAnswerError = - FractionParsingUiError.createFromParsingError( - fractionParser.getSubmitTimeError(answerText.toString()) - ).getErrorMessageFromStringRes(resourceHandler) - } } - errorMessage.set(pendingAnswerError) + AnswerErrorCategory.SUBMIT_TIME -> { + pendingAnswerError = + FractionParsingUiError.createFromParsingError( + fractionParser.getSubmitTimeError(answerText.toString()) + ).getErrorMessageFromStringRes(resourceHandler) + } } + errorMessage.set(pendingAnswerError) return pendingAnswerError } diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt index 5d0822fae6c..132c988774b 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/MathExpressionInteractionsViewModel.kt @@ -73,6 +73,12 @@ class MathExpressionInteractionsViewModel private constructor( * bound to the corresponding edit text. */ var answerText: CharSequence = "" + // The value of ths field is set from the Binding and from the TextWatcher. Any + // programmatic modification needs to be done here, so that the Binding and the TextWatcher + // do not step on each other. + set(value) { + field = value.toString().trim() + } /** * Defines whether an answer is currently available to parse. This is expected to be directly @@ -166,7 +172,7 @@ class MathExpressionInteractionsViewModel private constructor( } override fun onTextChanged(answer: CharSequence, start: Int, before: Int, count: Int) { - answerText = answer.toString().trim() + answerText = answer val isAnswerTextAvailable = answerText.isNotEmpty() if (isAnswerTextAvailable != isAnswerAvailable.get()) { isAnswerAvailable.set(isAnswerTextAvailable) diff --git a/app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity.kt new file mode 100644 index 00000000000..bb812a550a9 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/FractionInputInteractionViewTestActivity.kt @@ -0,0 +1,112 @@ +package org.oppia.android.app.testing + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.databinding.DataBindingUtil +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponentImpl +import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity +import org.oppia.android.app.customview.interaction.FractionInputInteractionView +import org.oppia.android.app.model.InputInteractionViewTestActivityParams +import org.oppia.android.app.model.Interaction +import org.oppia.android.app.model.UserAnswer +import org.oppia.android.app.model.WrittenTranslationContext +import org.oppia.android.app.player.state.answerhandling.AnswerErrorCategory +import org.oppia.android.app.player.state.answerhandling.InteractionAnswerErrorOrAvailabilityCheckReceiver +import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver +import org.oppia.android.app.player.state.itemviewmodel.FractionInteractionViewModel +import org.oppia.android.app.player.state.itemviewmodel.StateItemViewModel +import org.oppia.android.app.player.state.itemviewmodel.StateItemViewModel.InteractionItemFactory +import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener +import org.oppia.android.databinding.ActivityFractionInputInteractionViewTestBinding +import org.oppia.android.util.extensions.getProtoExtra +import org.oppia.android.util.extensions.putProtoExtra +import javax.inject.Inject + +/** + * This is a dummy activity to test [FractionInputInteractionView]. + */ +class FractionInputInteractionViewTestActivity : + InjectableAutoLocalizedAppCompatActivity(), + StateKeyboardButtonListener, + InteractionAnswerErrorOrAvailabilityCheckReceiver, + InteractionAnswerReceiver { + private lateinit var binding: ActivityFractionInputInteractionViewTestBinding + + @Inject + lateinit var fractionInteractionViewModelFactory: FractionInteractionViewModel.FactoryImpl + + /** Gives access to the [FractionInteractionViewModel]. */ + val fractionInteractionViewModel by lazy { + fractionInteractionViewModelFactory.create() + } + + /** Gives access to the translation context. */ + lateinit var writtenTranslationContext: WrittenTranslationContext + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + (activityComponent as ActivityComponentImpl).inject(this) + binding = DataBindingUtil.setContentView( + this, R.layout.activity_fraction_input_interaction_view_test + ) + + val params = + intent.getProtoExtra( + TEST_ACTIVITY_PARAMS_ARGUMENT_KEY, + InputInteractionViewTestActivityParams.getDefaultInstance() + ) + writtenTranslationContext = params.writtenTranslationContext + binding.fractionInteractionViewModel = fractionInteractionViewModel + } + + /** Checks submit-time errors. */ + fun getPendingAnswerErrorOnSubmitClick(v: View) { + fractionInteractionViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME) + } + + override fun onPendingAnswerErrorOrAvailabilityCheck( + pendingAnswerError: String?, + inputAnswerAvailable: Boolean + ) { + } + + override fun onAnswerReadyForSubmission(answer: UserAnswer) { + } + + override fun onEditorAction(actionCode: Int) { + } + + private inline fun InteractionItemFactory.create( + interaction: Interaction = Interaction.getDefaultInstance() + ): T { + return create( + entityId = "fake_entity_id", + hasConversationView = false, + interaction = interaction, + interactionAnswerReceiver = this@FractionInputInteractionViewTestActivity, + answerErrorReceiver = this@FractionInputInteractionViewTestActivity, + hasPreviousButton = false, + isSplitView = false, + writtenTranslationContext, + timeToStartNoticeAnimationMs = null + ) as T + } + + companion object { + private const val TEST_ACTIVITY_PARAMS_ARGUMENT_KEY = + "FractionInputInteractionViewTestActivity.params" + + /** Creates an intent to open this activity. */ + fun createIntent( + context: Context, + extras: InputInteractionViewTestActivityParams + ): Intent { + return Intent(context, FractionInputInteractionViewTestActivity::class.java).also { + it.putProtoExtra(TEST_ACTIVITY_PARAMS_ARGUMENT_KEY, extras) + } + } + } +} diff --git a/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt index 7190382efa0..ac786bb146a 100644 --- a/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/InputInteractionViewTestActivity.kt @@ -8,7 +8,6 @@ import androidx.databinding.DataBindingUtil import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity -import org.oppia.android.app.customview.interaction.FractionInputInteractionView import org.oppia.android.app.customview.interaction.NumericInputInteractionView import org.oppia.android.app.customview.interaction.TextInputInteractionView import org.oppia.android.app.model.InputInteractionViewTestActivityParams @@ -24,7 +23,6 @@ import org.oppia.android.app.model.WrittenTranslationContext import org.oppia.android.app.player.state.answerhandling.AnswerErrorCategory import org.oppia.android.app.player.state.answerhandling.InteractionAnswerErrorOrAvailabilityCheckReceiver import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver -import org.oppia.android.app.player.state.itemviewmodel.FractionInteractionViewModel import org.oppia.android.app.player.state.itemviewmodel.MathExpressionInteractionsViewModel import org.oppia.android.app.player.state.itemviewmodel.NumericInputViewModel import org.oppia.android.app.player.state.itemviewmodel.RatioExpressionInputInteractionViewModel @@ -40,7 +38,7 @@ import org.oppia.android.app.player.state.itemviewmodel.MathExpressionInteractio /** * This is a dummy activity to test input interaction views. - * It contains [FractionInputInteractionView], [NumericInputInteractionView],and [TextInputInteractionView]. + * It contains [NumericInputInteractionView],and [TextInputInteractionView]. */ class InputInteractionViewTestActivity : InjectableAutoLocalizedAppCompatActivity(), @@ -55,9 +53,6 @@ class InputInteractionViewTestActivity : @Inject lateinit var textInputViewModelFactory: TextInputViewModel.FactoryImpl - @Inject - lateinit var fractionInteractionViewModelFactory: FractionInteractionViewModel.FactoryImpl - @Inject lateinit var ratioViewModelFactory: RatioExpressionInputInteractionViewModel.FactoryImpl @@ -68,10 +63,6 @@ class InputInteractionViewTestActivity : val textInputViewModel by lazy { textInputViewModelFactory.create() } - val fractionInteractionViewModel by lazy { - fractionInteractionViewModelFactory.create() - } - val ratioExpressionInputInteractionViewModel by lazy { ratioViewModelFactory.create( interaction = Interaction.newBuilder().putCustomizationArgs( @@ -127,13 +118,11 @@ class InputInteractionViewTestActivity : binding.numericInputViewModel = numericInputViewModel binding.textInputViewModel = textInputViewModel - binding.fractionInteractionViewModel = fractionInteractionViewModel binding.ratioInteractionInputViewModel = ratioExpressionInputInteractionViewModel binding.mathExpressionInteractionsViewModel = mathExpressionViewModel } fun getPendingAnswerErrorOnSubmitClick(v: View) { - fractionInteractionViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME) numericInputViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME) ratioExpressionInputInteractionViewModel .checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME) diff --git a/app/src/main/res/layout/activity_fraction_input_interaction_view_test.xml b/app/src/main/res/layout/activity_fraction_input_interaction_view_test.xml new file mode 100644 index 00000000000..ddd18d752d9 --- /dev/null +++ b/app/src/main/res/layout/activity_fraction_input_interaction_view_test.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + +