Skip to content

Commit

Permalink
Fix part of #5070: Display empty answer message in drag & drop sort i…
Browse files Browse the repository at this point in the history
…nteraction (#5323)

<!-- READ ME FIRST: Please fill in the explanation section below and
check off every point from the Essential Checklist! -->
## Explanation
<!--
- Explain what your PR does. If this PR fixes an existing bug, please
include
- "Fixes #bugnum:" in the explanation so that GitHub can auto-close the
issue
  - when this PR is merged.
  -->

Fixes part of #5070

Enables the `submit_answer_button` when the pending answer is empty.
Instead of disabling the button, an error message, stating "_**Arrange
the boxes to continue.**_", is now displayed when the user attempts to
submit without arranging the boxes.


https://github.com/oppia/oppia-android/assets/84731134/c0083266-8641-414a-b14d-de662123d4a2

## Essential Checklist
<!-- Please tick the relevant boxes by putting an "x" in them. -->
- [x] The PR title and explanation each start with "Fix #bugnum: " (If
this PR fixes part of an issue, prefix the title with "Fix part of
#bugnum: ...".)
- [x] Any changes to
[scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets)
files have their rationale included in the PR explanation.
- [x] The PR follows the [style
guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide).
- [x] The PR does not contain any unnecessary code changes from Android
Studio
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)).
- [x] The PR is made from a branch that's **not** called "develop" and
is up-to-date with "develop".
- [x] The PR is **assigned** to the appropriate reviewers
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)).

## For UI-specific PRs only
<!-- Delete these section if this PR does not include UI-related
changes. -->
If your PR includes UI-related changes, then:
- Add screenshots for portrait/landscape for both a tablet & phone of
the before & after UI changes
- For the screenshots above, include both English and pseudo-localized
(RTL) screenshots (see [RTL
guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines))
- Add a video showing the full UX flow with a screen reader enabled (see
[accessibility
guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide))
- For PRs introducing new UI elements or color changes, both light and
dark mode screenshots must be included
- Add a screenshot demonstrating that you ran affected Espresso tests
locally & that they're passing

---------

Co-authored-by: Adhiambo Peres <[email protected]>
  • Loading branch information
theMr17 and adhiamboperes authored Mar 18, 2024
1 parent e2f94e4 commit 14a43d1
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 5 deletions.
2 changes: 1 addition & 1 deletion app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,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 @@ -320,7 +321,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
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
7 changes: 7 additions & 0 deletions app/src/main/res/layout/drag_drop_interaction_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,12 @@
app:draggableData="@{viewModel.choiceItems}"
app:onDragEnded="@{(adapter) -> viewModel.onDragEnded(adapter)}"
app:onItemDrag="@{(indexFrom, indexTo, adapter) -> viewModel.onItemDragged(indexFrom, indexTo, adapter)}" />

<TextView
android:id="@+id/drag_drop_interaction_error"
style="@style/InputInteractionErrorTextView"
android:text="@{viewModel.errorMessage}"
android:textColor="@color/component_color_shared_input_error_color"
android:visibility="@{viewModel.errorMessage.length()>0? View.VISIBLE : View.GONE}" />
</LinearLayout>
</layout>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@
<string name="return_to_lesson">Return to lesson</string>
<string name="explanation">Explanation:</string>
<string name="drag_and_drop_interaction_group_merge_hint">If two items are equal, merge them.</string>
<string name="drag_and_drop_interaction_empty_input">Arrange the boxes to continue.</string>
<string name="link_to_item_below">Link to item %s</string>
<string name="unlink_items">Unlink items at %s</string>
<string name="move_item_down_content_description">Move item down to %s</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,96 @@ class StateFragmentTest {
}
}

@Test
fun testStateFragment_loadDragDropExp_submitWithoutArranging_showsErrorMessage() {
setUpTestWithLanguageSwitchingFeatureOff()
launchForExploration(TEST_EXPLORATION_ID_4, shouldSavePartialProgress = false).use {
startPlayingExploration()
clickSubmitAnswerButton()
onView(withId(R.id.drag_drop_interaction_error))
.check(
matches(
withText(
R.string.drag_and_drop_interaction_empty_input
)
)
)
}
}

@Test
fun testStateFragment_loadDragDropExp_withGrouping_submitWithoutArranging_showsErrorMessage_dragItem_errorMessageIsReset() { // ktlint-disable max-line-length
setUpTestWithLanguageSwitchingFeatureOff()
launchForExploration(TEST_EXPLORATION_ID_4, shouldSavePartialProgress = false).use {
startPlayingExploration()

// Drag and drop interaction with grouping.
// Submit answer without any changes.
clickSubmitAnswerButton()
// Empty input error is displayed.
onView(withId(R.id.drag_drop_interaction_error))
.check(
matches(
isDisplayed()
)
)
// Submit button is disabled due to the error.
verifySubmitAnswerButtonIsDisabled()
// Drag and rearrange an item.
dragAndDropItem(fromPosition = 0, toPosition = 1)
// Empty input error is reset.
onView(withId(R.id.drag_drop_interaction_error))
.check(
matches(
not(isDisplayed())
)
)
// Submit button is enabled back.
verifySubmitAnswerButtonIsEnabled()
}
}

@Test
fun testStateFragment_loadDragDropExp_withoutGrouping_submitWithoutArranging_showsErrorMessage_dragItem_errorMessageIsReset() { // ktlint-disable max-line-length
setUpTestWithLanguageSwitchingFeatureOff()
launchForExploration(TEST_EXPLORATION_ID_2, shouldSavePartialProgress = false).use {
startPlayingExploration()
playThroughPrototypeState1()
playThroughPrototypeState2()
playThroughPrototypeState3()
playThroughPrototypeState4()
playThroughPrototypeState5()
playThroughPrototypeState6()
playThroughPrototypeState7()
playThroughPrototypeState8()

// Drag and drop interaction without grouping.
// Ninth state: Drag Drop Sort. Correct answer: Move 1st item to 4th position.
// Submit answer without any changes.
clickSubmitAnswerButton()
// Empty input error is displayed.
onView(withId(R.id.drag_drop_interaction_error))
.check(
matches(
isDisplayed()
)
)
// Submit button is disabled due to the error.
verifySubmitAnswerButtonIsDisabled()
// Drag and rearrange an item.
dragAndDropItem(fromPosition = 0, toPosition = 1)
// Empty input error is reset.
onView(withId(R.id.drag_drop_interaction_error))
.check(
matches(
not(isDisplayed())
)
)
// Submit button is enabled back.
verifySubmitAnswerButtonIsEnabled()
}
}

@Test
fun testStateFragment_loadDragDropExp_mergeFirstTwoItems_worksCorrectly() {
setUpTestWithLanguageSwitchingFeatureOff()
Expand Down

0 comments on commit 14a43d1

Please sign in to comment.