diff --git a/app/BUILD.bazel b/app/BUILD.bazel index 834ddb79025..2e155aeb696 100644 --- a/app/BUILD.bazel +++ b/app/BUILD.bazel @@ -222,6 +222,7 @@ VIEW_MODELS_WITH_RESOURCE_IMPORTS = [ "src/main/java/org/oppia/android/app/parser/StringToNumberParser.kt", "src/main/java/org/oppia/android/app/parser/StringToRatioParser.kt", "src/main/java/org/oppia/android/app/player/audio/AudioViewModel.kt", + "src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragDropInteractionContentViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/FractionInteractionViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/ImageRegionSelectionInteractionViewModel.kt", @@ -321,7 +322,6 @@ VIEW_MODELS = [ "src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContentViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueInteractionViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/ContinueNavigationButtonViewModel.kt", - "src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/FeedbackViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/NextButtonViewModel.kt", "src/main/java/org/oppia/android/app/player/state/itemviewmodel/NumericInputViewModel.kt", diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 24f808fc31f..1dee1b80db5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -215,6 +215,7 @@ + @@ -224,6 +225,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 67fd30fdf06..39ae00f3628 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 @@ -76,6 +76,7 @@ import org.oppia.android.app.testing.ImageViewBindingAdaptersTestActivity import org.oppia.android.app.testing.InputInteractionViewTestActivity import org.oppia.android.app.testing.ListItemLeadingMarginSpanTestActivity import org.oppia.android.app.testing.MarginBindingAdaptersTestActivity +import org.oppia.android.app.testing.MathExpressionInteractionsViewTestActivity import org.oppia.android.app.testing.NavigationDrawerTestActivity import org.oppia.android.app.testing.PoliciesFragmentTestActivity import org.oppia.android.app.testing.ProfileChooserFragmentTestActivity @@ -86,6 +87,7 @@ import org.oppia.android.app.testing.SpotlightFragmentTestActivity import org.oppia.android.app.testing.StateAssemblerMarginBindingAdaptersTestActivity import org.oppia.android.app.testing.StateAssemblerPaddingBindingAdaptersTestActivity import org.oppia.android.app.testing.TestFontScaleConfigurationUtilActivity +import org.oppia.android.app.testing.TextInputInteractionViewTestActivity import org.oppia.android.app.testing.TextViewBindingAdaptersTestActivity import org.oppia.android.app.testing.TopicRevisionTestActivity import org.oppia.android.app.testing.TopicTestActivity @@ -154,6 +156,8 @@ interface ActivityComponentImpl : fun inject(imageRegionSelectionTestActivity: ImageRegionSelectionTestActivity) fun inject(imageViewBindingAdaptersTestActivity: ImageViewBindingAdaptersTestActivity) fun inject(inputInteractionViewTestActivity: InputInteractionViewTestActivity) + fun inject(textInputInteractionViewTestActivity: TextInputInteractionViewTestActivity) + fun inject(mathExpressionInteractionsViewTestActivity: MathExpressionInteractionsViewTestActivity) fun inject(ratioInputInteractionViewTestActivity: RatioInputInteractionViewTestActivity) fun inject(licenseListActivity: LicenseListActivity) fun inject(licenseTextViewerActivity: LicenseTextViewerActivity) diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt index 6cd22508141..d6f88ea9cec 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/DragAndDropSortInteractionViewModel.kt @@ -1,8 +1,10 @@ package org.oppia.android.app.player.state.itemviewmodel +import androidx.annotation.StringRes import androidx.databinding.Observable import androidx.databinding.ObservableField import androidx.recyclerview.widget.RecyclerView +import org.oppia.android.R import org.oppia.android.app.model.Interaction import org.oppia.android.app.model.InteractionObject import org.oppia.android.app.model.ListOfSetsOfHtmlStrings @@ -13,6 +15,7 @@ import org.oppia.android.app.model.SubtitledHtml import org.oppia.android.app.model.TranslatableHtmlContentId 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.InteractionAnswerHandler import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver @@ -23,6 +26,18 @@ import org.oppia.android.app.translation.AppLanguageResourceHandler import org.oppia.android.domain.translation.TranslationController import javax.inject.Inject +/** Represents the type of errors that can be thrown by drag and drop sort interaction. */ +enum class DragAndDropSortInteractionError(@StringRes private var error: Int?) { + VALID(error = null), + EMPTY_INPUT(error = R.string.drag_and_drop_interaction_empty_input); + + /** + * Returns the string corresponding to this error's string resources, or null if there is none. + */ + fun getErrorMessageFromStringRes(resourceHandler: AppLanguageResourceHandler): String? = + error?.let(resourceHandler::getStringInLocale) +} + /** [StateItemViewModel] for drag drop & sort choice list. */ class DragAndDropSortInteractionViewModel private constructor( val entityId: String, @@ -55,25 +70,34 @@ class DragAndDropSortInteractionViewModel private constructor( subtitledHtml.contentId to translatedHtml } - private val _choiceItems: MutableList = + private val _originalChoiceItems: MutableList = computeChoiceItems(contentIdHtmlMap, choiceSubtitledHtmls, this, resourceHandler) + private val _choiceItems = _originalChoiceItems.toMutableList() val choiceItems: List = _choiceItems + private var pendingAnswerError: String? = null private val isAnswerAvailable = ObservableField(false) + var errorMessage = ObservableField("") init { val callback: Observable.OnPropertyChangedCallback = object : Observable.OnPropertyChangedCallback() { override fun onPropertyChanged(sender: Observable, propertyId: Int) { interactionAnswerErrorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( - pendingAnswerError = null, - inputAnswerAvailable = true + pendingAnswerError, + inputAnswerAvailable = true // Allow submission without arranging or merging items. ) } } isAnswerAvailable.addOnPropertyChangedCallback(callback) - isAnswerAvailable.set(true) // For drag drop submit button will be enabled by default. + errorMessage.addOnPropertyChangedCallback(callback) + + // Initializing with default values so that submit button is enabled by default. + interactionAnswerErrorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( + pendingAnswerError = null, + inputAnswerAvailable = true + ) } override fun onItemDragged( @@ -98,6 +122,7 @@ class DragAndDropSortInteractionViewModel private constructor( if (allowMultipleItemsInSamePosition) { (adapter as BindableAdapter<*>).setDataUnchecked(_choiceItems) } + checkPendingAnswerError(AnswerErrorCategory.REAL_TIME) } fun onItemMoved( @@ -129,6 +154,20 @@ class DragAndDropSortInteractionViewModel private constructor( this@DragAndDropSortInteractionViewModel.writtenTranslationContext }.build() + /** + * It checks the pending error for the current drag and drop sort interaction, and correspondingly + * updates the error string based on the specified error category. + */ + override fun checkPendingAnswerError(category: AnswerErrorCategory): String? { + pendingAnswerError = when (category) { + AnswerErrorCategory.REAL_TIME -> null + AnswerErrorCategory.SUBMIT_TIME -> + getSubmitTimeError().getErrorMessageFromStringRes(resourceHandler) + } + errorMessage.set(pendingAnswerError) + return pendingAnswerError + } + /** Returns an HTML list containing all of the HTML string elements as items in the list. */ private fun convertItemsToAnswer(htmlItems: List): ListOfSetsOfHtmlStrings { return ListOfSetsOfHtmlStrings.newBuilder() @@ -190,6 +229,13 @@ class DragAndDropSortInteractionViewModel private constructor( (adapter as BindableAdapter<*>).setDataUnchecked(_choiceItems) } + private fun getSubmitTimeError(): DragAndDropSortInteractionError { + return if (_originalChoiceItems == _choiceItems) + DragAndDropSortInteractionError.EMPTY_INPUT + else + DragAndDropSortInteractionError.VALID + } + /** Implementation of [StateItemViewModel.InteractionItemFactory] for this view model. */ class FactoryImpl @Inject constructor( private val resourceHandler: AppLanguageResourceHandler, 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 132c988774b..06b6bb3a47c 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 @@ -105,12 +105,18 @@ class MathExpressionInteractionsViewModel private constructor( override fun onPropertyChanged(sender: Observable, propertyId: Int) { errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( pendingAnswerError, - answerText.isNotEmpty() + inputAnswerAvailable = true // Allow blank answer submission. ) } } errorMessage.addOnPropertyChangedCallback(callback) isAnswerAvailable.addOnPropertyChangedCallback(callback) + + // Initializing with default values so that submit button is enabled by default. + errorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( + pendingAnswerError = null, + inputAnswerAvailable = true + ) } override fun getPendingAnswer(): UserAnswer = UserAnswer.newBuilder().apply { @@ -147,18 +153,16 @@ class MathExpressionInteractionsViewModel private constructor( }.build() override fun checkPendingAnswerError(category: AnswerErrorCategory): String? { - if (answerText.isNotEmpty()) { - pendingAnswerError = when (category) { - // There's no support for real-time errors. - AnswerErrorCategory.REAL_TIME -> null - AnswerErrorCategory.SUBMIT_TIME -> { - interactionType.computeSubmitTimeError( - answerText.toString(), allowedVariables, resourceHandler - ) - } + pendingAnswerError = when (category) { + // There's no support for real-time errors. + AnswerErrorCategory.REAL_TIME -> null + AnswerErrorCategory.SUBMIT_TIME -> { + interactionType.computeSubmitTimeError( + answerText.toString(), allowedVariables, resourceHandler + ) } - errorMessage.set(pendingAnswerError) } + errorMessage.set(pendingAnswerError) return pendingAnswerError } @@ -290,7 +294,10 @@ class MathExpressionInteractionsViewModel private constructor( } private companion object { - private enum class InteractionType( + /** + * Enum class representing different types of interactions in a mathematical expression input field. + */ + enum class InteractionType( val viewType: ViewType, @StringRes val defaultHintTextStringId: Int, val hasPlaceholder: Boolean, @@ -420,6 +427,25 @@ class MathExpressionInteractionsViewModel private constructor( allowedVariables: List, appLanguageResourceHandler: AppLanguageResourceHandler ): String? { + if (answerText.isBlank()) { + return when (this) { + NUMERIC_EXPRESSION -> { + appLanguageResourceHandler.getStringInLocale( + R.string.numeric_expression_error_empty_input + ) + } + ALGEBRAIC_EXPRESSION -> { + appLanguageResourceHandler.getStringInLocale( + R.string.algebraic_expression_error_empty_input + ) + } + MATH_EQUATION -> { + appLanguageResourceHandler.getStringInLocale( + R.string.math_equation_error_empty_input + ) + } + } + } return when (val parseResult = parseAnswer(answerText, allowedVariables)) { is MathParsingResult.Failure -> when (val error = parseResult.error) { is DisabledVariablesInUseError -> { diff --git a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt index b3bdbba2ac0..7685e3673b9 100644 --- a/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/itemviewmodel/TextInputViewModel.kt @@ -2,6 +2,7 @@ package org.oppia.android.app.player.state.itemviewmodel import android.text.Editable import android.text.TextWatcher +import androidx.annotation.StringRes import androidx.databinding.Observable import androidx.databinding.ObservableField import org.oppia.android.R @@ -9,6 +10,7 @@ import org.oppia.android.app.model.Interaction import org.oppia.android.app.model.InteractionObject 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.InteractionAnswerHandler import org.oppia.android.app.player.state.answerhandling.InteractionAnswerReceiver @@ -28,20 +30,43 @@ class TextInputViewModel private constructor( ) : StateItemViewModel(ViewType.TEXT_INPUT_INTERACTION), InteractionAnswerHandler { var answerText: CharSequence = "" val hintText: CharSequence = deriveHintText(interaction) + private var pendingAnswerError: String? = null var isAnswerAvailable = ObservableField(false) + val errorMessage = ObservableField("") init { val callback: Observable.OnPropertyChangedCallback = object : Observable.OnPropertyChangedCallback() { override fun onPropertyChanged(sender: Observable, propertyId: Int) { interactionAnswerErrorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( - /* pendingAnswerError= */ null, - answerText.isNotEmpty() + pendingAnswerError = pendingAnswerError, + inputAnswerAvailable = true // Allow submit on empty answer. ) } } isAnswerAvailable.addOnPropertyChangedCallback(callback) + errorMessage.addOnPropertyChangedCallback(callback) + + // Initializing with default values so that submit button is enabled by default. + interactionAnswerErrorOrAvailabilityCheckReceiver.onPendingAnswerErrorOrAvailabilityCheck( + pendingAnswerError = null, + inputAnswerAvailable = true + ) + } + + override fun checkPendingAnswerError(category: AnswerErrorCategory): String? { + return when (category) { + AnswerErrorCategory.REAL_TIME -> null + AnswerErrorCategory.SUBMIT_TIME -> { + TextParsingUiError.createForText( + answerText.toString() + ).createForText(resourceHandler) + } + }.also { + pendingAnswerError = it + errorMessage.set(it) + } } fun getAnswerTextWatcher(): TextWatcher { @@ -55,6 +80,7 @@ class TextInputViewModel private constructor( if (isAnswerTextAvailable != isAnswerAvailable.get()) { isAnswerAvailable.set(isAnswerTextAvailable) } + checkPendingAnswerError(AnswerErrorCategory.REAL_TIME) } override fun afterTextChanged(s: Editable) { @@ -121,4 +147,22 @@ class TextInputViewModel private constructor( ) } } + + private enum class TextParsingUiError(@StringRes private var error: Int?) { + /** Corresponds to non empty input. */ + VALID(error = null), + + /** Corresponds to empty input. */ + EMPTY_INPUT(error = R.string.text_error_empty_input); + + /** Returns the string corresponding to this error's string resources, or null if there is none. */ + fun createForText(resourceHandler: AppLanguageResourceHandler): String? = + error?.let(resourceHandler::getStringInLocale) + + companion object { + /** Returns the [TextParsingUiError] corresponding to the input. */ + fun createForText(text: String): TextParsingUiError = + if (text.isEmpty()) EMPTY_INPUT else VALID + } + } } 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 f033c023d61..20706aa12a3 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 @@ -9,34 +9,25 @@ 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.NumericInputInteractionView -import org.oppia.android.app.customview.interaction.TextInputInteractionView import org.oppia.android.app.model.InputInteractionViewTestActivityParams -import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathInteractionType.ALGEBRAIC_EXPRESSION -import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathInteractionType.MATH_EQUATION -import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathInteractionType.MATH_INTERACTION_TYPE_UNSPECIFIED -import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathInteractionType.NUMERIC_EXPRESSION -import org.oppia.android.app.model.InputInteractionViewTestActivityParams.MathInteractionType.UNRECOGNIZED 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.MathExpressionInteractionsViewModel import org.oppia.android.app.player.state.itemviewmodel.NumericInputViewModel 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.itemviewmodel.TextInputViewModel import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener import org.oppia.android.databinding.ActivityInputInteractionViewTestBinding import org.oppia.android.util.extensions.getProtoExtra import org.oppia.android.util.extensions.putProtoExtra import javax.inject.Inject -import org.oppia.android.app.player.state.itemviewmodel.MathExpressionInteractionsViewModel.FactoryImpl.FactoryFactoryImpl as MathExpViewModelFactoryFactoryImpl /** * This is a dummy activity to test input interaction views. - * It contains [NumericInputInteractionView],and [TextInputInteractionView]. + * It contains [NumericInputInteractionView] */ class InputInteractionViewTestActivity : InjectableAutoLocalizedAppCompatActivity(), @@ -48,17 +39,8 @@ class InputInteractionViewTestActivity : @Inject lateinit var numericInputViewModelFactory: NumericInputViewModel.FactoryImpl - @Inject - lateinit var textInputViewModelFactory: TextInputViewModel.FactoryImpl - - @Inject - lateinit var mathExpViewModelFactoryFactory: MathExpViewModelFactoryFactoryImpl - val numericInputViewModel by lazy { numericInputViewModelFactory.create() } - val textInputViewModel by lazy { textInputViewModelFactory.create() } - - lateinit var mathExpressionViewModel: MathExpressionInteractionsViewModel lateinit var writtenTranslationContext: WrittenTranslationContext override fun onCreate(savedInstanceState: Bundle?) { @@ -74,37 +56,8 @@ class InputInteractionViewTestActivity : InputInteractionViewTestActivityParams.getDefaultInstance() ) writtenTranslationContext = params.writtenTranslationContext - when (params.mathInteractionType) { - NUMERIC_EXPRESSION -> { - mathExpressionViewModel = - mathExpViewModelFactoryFactory - .createFactoryForNumericExpression() - .create(interaction = params.interaction) - } - ALGEBRAIC_EXPRESSION -> { - mathExpressionViewModel = - mathExpViewModelFactoryFactory - .createFactoryForAlgebraicExpression() - .create(interaction = params.interaction) - } - MATH_EQUATION -> { - mathExpressionViewModel = - mathExpViewModelFactoryFactory - .createFactoryForMathEquation() - .create(interaction = params.interaction) - } - MATH_INTERACTION_TYPE_UNSPECIFIED, UNRECOGNIZED, null -> { - // Default to numeric expression arbitrarily (since something needs to be defined). - mathExpressionViewModel = - mathExpViewModelFactoryFactory - .createFactoryForNumericExpression() - .create(interaction = params.interaction) - } - } binding.numericInputViewModel = numericInputViewModel - binding.textInputViewModel = textInputViewModel - binding.mathExpressionInteractionsViewModel = mathExpressionViewModel } fun getPendingAnswerErrorOnSubmitClick(v: View) { diff --git a/app/src/main/java/org/oppia/android/app/testing/MathExpressionInteractionsViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/MathExpressionInteractionsViewTestActivity.kt new file mode 100644 index 00000000000..55219840ea9 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/MathExpressionInteractionsViewTestActivity.kt @@ -0,0 +1,147 @@ +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.model.Interaction +import org.oppia.android.app.model.MathExpressionInteractionsViewTestActivityParams +import org.oppia.android.app.model.MathExpressionInteractionsViewTestActivityParams.MathInteractionType +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.MathExpressionInteractionsViewModel +import org.oppia.android.app.player.state.itemviewmodel.StateItemViewModel +import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener +import org.oppia.android.databinding.ActivityMathExpressionInteractionViewTestBinding +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 input interaction views. + * It contains [MathExpressionInteractionsView]. + */ +class MathExpressionInteractionsViewTestActivity : + InjectableAutoLocalizedAppCompatActivity(), + StateKeyboardButtonListener, + InteractionAnswerErrorOrAvailabilityCheckReceiver, + InteractionAnswerReceiver { + + private lateinit var binding: + ActivityMathExpressionInteractionViewTestBinding + + /** + * Injects the [MathExpressionInteractionsViewModel.FactoryImpl] for creating + * [MathExpressionInteractionsViewModel] instances. + */ + @Inject + lateinit var mathExpViewModelFactoryFactory: + MathExpressionInteractionsViewModel.FactoryImpl.FactoryFactoryImpl + + /** The [MathExpressionInteractionsViewModel] instance. */ + lateinit var mathExpressionViewModel: MathExpressionInteractionsViewModel + + /** 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_math_expression_interaction_view_test + ) + val params = + intent.getProtoExtra( + TEST_ACTIVITY_PARAMS_ARGUMENT_KEY, + MathExpressionInteractionsViewTestActivityParams.getDefaultInstance() + ) + writtenTranslationContext = params.writtenTranslationContext + when (params.mathInteractionType) { + MathInteractionType.NUMERIC_EXPRESSION -> { + mathExpressionViewModel = + mathExpViewModelFactoryFactory + .createFactoryForNumericExpression() + .create(interaction = params.interaction) + } + MathInteractionType.ALGEBRAIC_EXPRESSION -> { + mathExpressionViewModel = + mathExpViewModelFactoryFactory + .createFactoryForAlgebraicExpression() + .create(interaction = params.interaction) + } + MathInteractionType.MATH_EQUATION -> { + mathExpressionViewModel = + mathExpViewModelFactoryFactory + .createFactoryForMathEquation() + .create(interaction = params.interaction) + } + MathInteractionType.MATH_INTERACTION_TYPE_UNSPECIFIED, + MathInteractionType.UNRECOGNIZED, null -> { + // Default to numeric expression arbitrarily (since something needs to be defined). + mathExpressionViewModel = + mathExpViewModelFactoryFactory + .createFactoryForNumericExpression() + .create(interaction = params.interaction) + } + } + + binding.mathExpressionInteractionsViewModel = mathExpressionViewModel + } + + /** Checks submit-time errors. */ + fun getPendingAnswerErrorOnSubmitClick(v: View) { + mathExpressionViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME) + } + + override fun onPendingAnswerErrorOrAvailabilityCheck( + pendingAnswerError: String?, + inputAnswerAvailable: Boolean + ) { + binding.submitButton.isEnabled = pendingAnswerError == null + } + + override fun onAnswerReadyForSubmission(answer: UserAnswer) { + } + + override fun onEditorAction(actionCode: Int) { + } + + private inline fun StateItemViewModel + .InteractionItemFactory.create( + interaction: Interaction = Interaction.getDefaultInstance() + ): T { + return create( + entityId = "fake_entity_id", + hasConversationView = false, + interaction = interaction, + interactionAnswerReceiver = this@MathExpressionInteractionsViewTestActivity, + answerErrorReceiver = this@MathExpressionInteractionsViewTestActivity, + hasPreviousButton = false, + isSplitView = false, + writtenTranslationContext, + timeToStartNoticeAnimationMs = null + ) as T + } + + companion object { + private const val TEST_ACTIVITY_PARAMS_ARGUMENT_KEY = + "MathExpressionInteractionsViewTestActivity.params" + + /** Function to create intent for MathExpressionInteractionsViewTestActivity. */ + fun createIntent( + context: Context, + extras: MathExpressionInteractionsViewTestActivityParams + ): Intent { + return Intent(context, MathExpressionInteractionsViewTestActivity::class.java).also { + it.putProtoExtra(TEST_ACTIVITY_PARAMS_ARGUMENT_KEY, extras) + } + } + } +} diff --git a/app/src/main/java/org/oppia/android/app/testing/TextInputInteractionViewTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/TextInputInteractionViewTestActivity.kt new file mode 100644 index 00000000000..5308bac3f8e --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/testing/TextInputInteractionViewTestActivity.kt @@ -0,0 +1,89 @@ +package org.oppia.android.app.testing + +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.TextInputInteractionView +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.StateItemViewModel +import org.oppia.android.app.player.state.itemviewmodel.TextInputViewModel +import org.oppia.android.app.player.state.listener.StateKeyboardButtonListener +import org.oppia.android.databinding.ActivityTextInputInteractionViewTestBinding +import javax.inject.Inject + +/** + * This is a dummy activity to test input interaction views. + * It contains [TextInputInteractionView] + */ +class TextInputInteractionViewTestActivity : + InjectableAutoLocalizedAppCompatActivity(), + StateKeyboardButtonListener, + InteractionAnswerErrorOrAvailabilityCheckReceiver, + InteractionAnswerReceiver { + + private lateinit var binding: ActivityTextInputInteractionViewTestBinding + + @Inject + lateinit var textinputViewModelFactory: TextInputViewModel.FactoryImpl + + /** Gives access to the [TextInputViewModel]. */ + val textInputViewModel by lazy { + textinputViewModelFactory.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_text_input_interaction_view_test + ) + + writtenTranslationContext = WrittenTranslationContext.getDefaultInstance() + binding.textInputViewModel = textInputViewModel + } + + /** Checks submit-time errors. */ + fun getPendingAnswerErrorOnSubmitClick(v: View) { + textInputViewModel.checkPendingAnswerError(AnswerErrorCategory.SUBMIT_TIME) + } + + override fun onPendingAnswerErrorOrAvailabilityCheck( + pendingAnswerError: String?, + inputAnswerAvailable: Boolean + ) { + } + + override fun onAnswerReadyForSubmission(answer: UserAnswer) { + } + + override fun onEditorAction(actionCode: Int) { + } + + private inline fun + StateItemViewModel.InteractionItemFactory.create( + interaction: Interaction = Interaction.getDefaultInstance() + ): T { + return create( + entityId = "fake_entity_id", + hasConversationView = false, + interaction = interaction, + interactionAnswerReceiver = this@TextInputInteractionViewTestActivity, + answerErrorReceiver = this@TextInputInteractionViewTestActivity, + hasPreviousButton = false, + isSplitView = false, + writtenTranslationContext, + timeToStartNoticeAnimationMs = null + ) as T + } +} diff --git a/app/src/main/res/layout/activity_input_interaction_view_test.xml b/app/src/main/res/layout/activity_input_interaction_view_test.xml index 780294fa6b4..c60eb2fec5e 100644 --- a/app/src/main/res/layout/activity_input_interaction_view_test.xml +++ b/app/src/main/res/layout/activity_input_interaction_view_test.xml @@ -11,18 +11,12 @@ name="numericInputViewModel" type="org.oppia.android.app.player.state.itemviewmodel.NumericInputViewModel" /> - - - + - - - -