Skip to content

Commit

Permalink
[Data Collection] Update "Next" to "Done" when on last task (#2023)
Browse files Browse the repository at this point in the history
  • Loading branch information
shobhitagarwal1612 authored Oct 30, 2023
1 parent 712bf49 commit c849c5a
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class DataCollectionFragment : Hilt_DataCollectionFragment(), BackPressListener
false
} else {
// Otherwise, select the previous step.
viewModel.setCurrentPosition(viewModel.currentPosition.value!! - 1)
viewModel.updateCurrentPosition(viewModel.getVisibleTaskPosition() - 1)
true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,9 @@ internal constructor(

private val responses: MutableMap<Task, TaskData?> = LinkedHashMap()

private val currentPositionKey = "currentPosition"
// Tracks the user's current position in the list of tasks for the current Job
// Tracks the task's current position in the list of tasks for the current job
var currentPosition: @Hot(replays = true) MutableLiveData<Int> =
savedStateHandle.getLiveData(currentPositionKey, 0)
savedStateHandle.getLiveData(TASK_POSITION_KEY, 0)

var currentTaskData: TaskData? = null

Expand Down Expand Up @@ -162,7 +161,7 @@ internal constructor(
* Validates the user's input and displays an error if the user input was invalid. Progresses to
* the next Data Collection screen if the user input was valid.
*/
fun onNextClicked() {
fun onNextClicked(position: Int) {
val currentTask = currentTaskViewModel ?: return

val validationError = currentTask.validate()
Expand All @@ -171,16 +170,10 @@ internal constructor(
return
}

val currentTaskPosition = currentPosition.value!!
val finalTaskPosition = tasks.size - 1

assert(finalTaskPosition >= 0)
assert(currentTaskPosition in 0..finalTaskPosition)

responses[currentTask.task] = currentTaskData

if (currentTaskPosition != finalTaskPosition) {
setCurrentPosition(currentPosition.value!! + 1)
if (!isLastPosition(position)) {
updateCurrentPosition(position + 1)
} else {
val taskDataDeltas =
responses.map { (task, taskData) -> TaskDataDelta(task.id, task.type, taskData) }
Expand All @@ -202,14 +195,30 @@ internal constructor(
}
}

fun setCurrentPosition(position: Int) {
savedStateHandle[currentPositionKey] = position
/** Returns the position of the task fragment visible to the user. */
fun getVisibleTaskPosition() = currentPosition.value!!

/** Displays the task at the given position to the user. */
fun updateCurrentPosition(position: Int) {
savedStateHandle[TASK_POSITION_KEY] = position
}

/** Returns true if the given task position is last. */
fun isLastPosition(taskPosition: Int): Boolean {
val finalTaskPosition = tasks.size - 1

assert(finalTaskPosition >= 0)
assert(taskPosition in 0..finalTaskPosition)

return taskPosition == finalTaskPosition
}

private fun createSuggestLoiTask(taskType: Task.Type): Task =
Task(id = "-1", index = -1, taskType, resources.getString(R.string.new_site), isRequired = true)

companion object {
private const val TASK_POSITION_KEY = "currentPosition"

fun getViewModelClass(taskType: Task.Type): Class<out AbstractTaskViewModel> =
when (taskType) {
Task.Type.TEXT -> TextTaskViewModel::class.java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enum class ButtonAction(
) {

// All tasks
DONE(Type.TEXT, Theme.DARK_GREEN, textId = R.string.done),
NEXT(Type.TEXT, Theme.DARK_GREEN, textId = R.string.next),
SKIP(Type.TEXT, Theme.TRANSPARENT, textId = R.string.skip),
UNDO(Type.ICON, Theme.LIGHT_GREEN, drawableId = R.drawable.ic_undo_black),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ abstract class AbstractTaskFragment<T : AbstractTaskViewModel> : AbstractFragmen

protected fun addNextButton() =
addButton(ButtonAction.NEXT)
.setOnClickListener { dataCollectionViewModel.onNextClicked() }
.setOnClickListener { moveToNext() }
.setOnTaskUpdated { button, taskData -> button.enableIfTrue(taskData.isNotNullOrEmpty()) }
.disable()

Expand All @@ -135,7 +135,11 @@ abstract class AbstractTaskFragment<T : AbstractTaskViewModel> : AbstractFragmen

private fun onSkip() {
check(viewModel.hasNoData()) { "User should not be able to skip a task with data." }
dataCollectionViewModel.onNextClicked()
moveToNext()
}

fun moveToNext() {
dataCollectionViewModel.onNextClicked(position)
}

fun addUndoButton() =
Expand All @@ -144,7 +148,8 @@ abstract class AbstractTaskFragment<T : AbstractTaskViewModel> : AbstractFragmen
.setOnTaskUpdated { button, taskData -> button.showIfTrue(taskData.isNotNullOrEmpty()) }
.hide()

protected fun addButton(action: ButtonAction): TaskButton {
protected fun addButton(buttonAction: ButtonAction): TaskButton {
val action = if (buttonAction.shouldReplaceWithDoneButton()) ButtonAction.DONE else buttonAction
check(!buttons.contains(action)) { "Button $action already bound" }
val button =
TaskButtonFactory.createAndAttachButton(
Expand All @@ -157,6 +162,10 @@ abstract class AbstractTaskFragment<T : AbstractTaskViewModel> : AbstractFragmen
return button
}

/** Returns true if the given [ButtonAction] should be replace with "Done" button. */
private fun ButtonAction.shouldReplaceWithDoneButton() =
this == ButtonAction.NEXT && dataCollectionViewModel.isLastPosition(position)

@TestOnly fun getButtons() = buttons

@TestOnly fun getButtonsIndex() = buttonsIndex
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class CaptureLocationTaskFragment :
.setOnClickListener { viewModel.updateResponse() }
.setOnTaskUpdated { button, taskData -> button.showIfTrue(taskData.isNullOrEmpty()) }
addButton(ButtonAction.NEXT)
.setOnClickListener { dataCollectionViewModel.onNextClicked() }
.setOnClickListener { moveToNext() }
.setOnTaskUpdated { button, taskData -> button.showIfTrue(taskData.isNotNullOrEmpty()) }
.hide()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class DropAPinTaskFragment : Hilt_DropAPinTaskFragment<DropAPinTaskViewModel>()
.setOnClickListener { viewModel.dropPin() }
.setOnTaskUpdated { button, taskData -> button.showIfTrue(taskData.isNullOrEmpty()) }
addButton(ButtonAction.NEXT)
.setOnClickListener { dataCollectionViewModel.onNextClicked() }
.setOnClickListener { moveToNext() }
.setOnTaskUpdated { button, taskData -> button.showIfTrue(taskData.isNotNullOrEmpty()) }
.hide()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ class DataCollectionFragmentTest : BaseHiltTest() {
onView(withText(TASK_1_NAME)).check(matches(not(isDisplayed())))
onView(withText(TASK_2_NAME)).check(matches(isDisplayed()))

// Click "next" on final task
// Click "done" on final task
onView(allOf(withId(R.id.user_response_text), isDisplayed())).perform(typeText(task2Response))
onView(allOf(withText("Next"), isDisplayed())).perform(click())
onView(allOf(withText("Done"), isDisplayed())).perform(click())
advanceUntilIdle()

verify(submissionRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import org.robolectric.shadows.ShadowAlertDialog

Expand Down Expand Up @@ -146,6 +148,14 @@ class MultipleChoiceTaskFragmentTest :
assertFragmentHasButtons(ButtonAction.SKIP, ButtonAction.NEXT)
}

@Test
fun testActionButtons_whenLastTask() {
whenever(dataCollectionViewModel.isLastPosition(any())).thenReturn(true)
setupTaskFragment<MultipleChoiceTaskFragment>(job, task)

hasButtons(ButtonAction.SKIP, ButtonAction.DONE)
}

@Test
fun testActionButtons_whenTaskIsOptional() {
setupTaskFragment<MultipleChoiceTaskFragment>(job, task.copy(isRequired = false))
Expand Down

0 comments on commit c849c5a

Please sign in to comment.