Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:oppia/oppia-android into new-onb…
Browse files Browse the repository at this point in the history
…oarding-ui
  • Loading branch information
adhiamboperes committed Mar 19, 2024
2 parents deacb45 + 14a43d1 commit 692e62e
Show file tree
Hide file tree
Showing 73 changed files with 2,909 additions and 983 deletions.
2 changes: 1 addition & 1 deletion app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@
<activity android:name=".app.testing.ExplorationInjectionActivity" />
<activity android:name=".app.testing.ExplorationTestActivity" />
<activity android:name=".app.testing.FractionInputInteractionViewTestActivity" />
<activity android:name=".app.testing.TextInputInteractionViewTestActivity" />
<activity
android:name=".app.testing.TestFontScaleConfigurationUtilActivity"
android:theme="@style/OppiaThemeWithoutActionBar" />
Expand All @@ -224,6 +225,7 @@
<activity android:name=".app.testing.InputInteractionViewTestActivity" />
<activity android:name=".app.testing.RatioInputInteractionViewTestActivity" />
<activity android:name=".app.testing.ImageRegionSelectionTestActivity" />
<activity android:name=".app.testing.MathExpressionInteractionsViewTestActivity" />
<activity android:name=".app.testing.ImageViewBindingAdaptersTestActivity" />
<activity android:name=".app.testing.ListItemLeadingMarginSpanTestActivity" />
<activity android:name=".app.testing.MarginBindingAdaptersTestActivity" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -55,25 +70,34 @@ class DragAndDropSortInteractionViewModel private constructor(
subtitledHtml.contentId to translatedHtml
}

private val _choiceItems: MutableList<DragDropInteractionContentViewModel> =
private val _originalChoiceItems: MutableList<DragDropInteractionContentViewModel> =
computeChoiceItems(contentIdHtmlMap, choiceSubtitledHtmls, this, resourceHandler)

private val _choiceItems = _originalChoiceItems.toMutableList()
val choiceItems: List<DragDropInteractionContentViewModel> = _choiceItems

private var pendingAnswerError: String? = null
private val isAnswerAvailable = ObservableField(false)
var errorMessage = ObservableField<String>("")

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(
Expand All @@ -98,6 +122,7 @@ class DragAndDropSortInteractionViewModel private constructor(
if (allowMultipleItemsInSamePosition) {
(adapter as BindableAdapter<*>).setDataUnchecked(_choiceItems)
}
checkPendingAnswerError(AnswerErrorCategory.REAL_TIME)
}

fun onItemMoved(
Expand Down Expand Up @@ -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<StringList>): ListOfSetsOfHtmlStrings {
return ListOfSetsOfHtmlStrings.newBuilder()
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -420,6 +427,25 @@ class MathExpressionInteractionsViewModel private constructor(
allowedVariables: List<String>,
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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ 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
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
Expand All @@ -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<Boolean>(false)
val errorMessage = ObservableField<String>("")

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 {
Expand All @@ -55,6 +80,7 @@ class TextInputViewModel private constructor(
if (isAnswerTextAvailable != isAnswerAvailable.get()) {
isAnswerAvailable.set(isAnswerTextAvailable)
}
checkPendingAnswerError(AnswerErrorCategory.REAL_TIME)
}

override fun afterTextChanged(s: Editable) {
Expand Down Expand Up @@ -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
}
}
}
Loading

0 comments on commit 692e62e

Please sign in to comment.