Skip to content

Commit

Permalink
[Compose] Migrate number task to compose UI (#2977)
Browse files Browse the repository at this point in the history
* Move TextTaskInput to shared components

* Reuse TextTaskInput in NumberTaskFragment

* Update unit tests

* Remove unused code
  • Loading branch information
shobhitagarwal1612 authored Jan 3, 2025
1 parent e411546 commit acb6193
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ package com.google.android.ground.ui.common

import android.content.res.ColorStateList
import android.net.Uri
import android.text.TextWatcher
import android.view.View
import android.widget.ImageView
import androidx.core.content.ContextCompat
import androidx.core.widget.ImageViewCompat
import androidx.databinding.BindingAdapter
import com.google.android.gms.common.SignInButton
import com.google.android.ground.R
import com.google.android.material.textfield.TextInputEditText
import com.squareup.picasso.Picasso
import timber.log.Timber

Expand All @@ -41,12 +39,6 @@ object BindingAdapters {
button.setOnClickListener(onClickCallback)
}

@JvmStatic
@BindingAdapter("textChangedListener")
fun bindTextWatcher(editText: TextInputEditText, textWatcher: TextWatcher) {
editText.addTextChangedListener(textWatcher)
}

@JvmStatic
@BindingAdapter("imageUrl")
fun bindUri(view: ImageView?, url: String?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.ui.datacollection.tasks.text
package com.google.android.ground.ui.datacollection.components

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
Expand All @@ -24,19 +24,17 @@ import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.android.ground.ExcludeFromJacocoGeneratedReport
import com.google.android.ground.ui.theme.AppTheme

const val INPUT_TEXT_TEST_TAG: String = "text task input test tag"

@Composable
fun TextTaskInput(
value: String,
modifier: Modifier = Modifier,
keyboardType: KeyboardType = KeyboardType.Text,
valueChanged: (text: String) -> Unit = {},
) {
TextField(
Expand All @@ -48,11 +46,10 @@ fun TextTaskInput(
.wrapContentHeight(align = Alignment.Top)
// TODO: Add horizontal padding as 16.dp when global padding is removed.
// Issue URL: https://github.com/google/ground-android/issues/2976
.padding(vertical = 8.dp)
.testTag(INPUT_TEXT_TEST_TAG),
.padding(vertical = 8.dp),
textStyle = MaterialTheme.typography.bodyLarge,
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
keyboardOptions = KeyboardOptions(keyboardType = keyboardType),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,42 @@ package com.google.android.ground.ui.datacollection.tasks.number

import android.view.LayoutInflater
import android.view.View
import com.google.android.ground.databinding.NumberTaskFragBinding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.input.KeyboardType
import com.google.android.ground.model.submission.NumberTaskData.Companion.fromNumber
import com.google.android.ground.ui.datacollection.components.TaskView
import com.google.android.ground.ui.datacollection.components.TaskViewFactory
import com.google.android.ground.ui.datacollection.components.TextTaskInput
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskFragment
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint

const val INPUT_NUMBER_TEST_TAG: String = "number task input test tag"

/** Fragment allowing the user to answer questions to complete a task. */
@AndroidEntryPoint
class NumberTaskFragment : AbstractTaskFragment<NumberTaskViewModel>() {

override fun onCreateTaskView(inflater: LayoutInflater): TaskView =
TaskViewFactory.createWithHeader(layoutInflater)

override fun onCreateTaskBody(inflater: LayoutInflater): View {
val taskBinding = NumberTaskFragBinding.inflate(inflater)
taskBinding.viewModel = viewModel
taskBinding.lifecycleOwner = this
return taskBinding.root
override fun onCreateTaskBody(inflater: LayoutInflater): View =
ComposeView(requireContext()).apply { setContent { AppTheme { ShowTextInputField() } } }

@Composable
private fun ShowTextInputField() {
val userResponse by viewModel.responseText.observeAsState("")
TextTaskInput(
userResponse,
keyboardType = KeyboardType.Decimal,
modifier = Modifier.testTag(INPUT_NUMBER_TEST_TAG),
) { newText ->
viewModel.setValue(fromNumber(newText))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@
*/
package com.google.android.ground.ui.datacollection.tasks.number

import android.text.Editable
import android.text.TextWatcher
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import com.google.android.ground.model.submission.NumberTaskData
import com.google.android.ground.model.submission.NumberTaskData.Companion.fromNumber
import com.google.android.ground.model.submission.TaskData
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskViewModel
import javax.inject.Inject
Expand All @@ -30,21 +27,6 @@ import kotlinx.coroutines.flow.map
class NumberTaskViewModel @Inject constructor() : AbstractTaskViewModel() {

/** Transcoded text to be displayed for the current [TaskData]. */
val numberResponseText: LiveData<String> =
taskTaskData.filterIsInstance<NumberTaskData>().map { it.number }.asLiveData()

val textWatcher =
object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// Do nothing.
}

override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
setValue(fromNumber(s.toString()))
}

override fun afterTextChanged(s: Editable) {
// Do nothing.
}
}
val responseText: LiveData<String> =
taskTaskData.filterIsInstance<NumberTaskData?>().map { it?.number ?: "" }.asLiveData()
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.testTag
import com.google.android.ground.model.submission.TextTaskData.Companion.fromString
import com.google.android.ground.ui.datacollection.components.TaskView
import com.google.android.ground.ui.datacollection.components.TaskViewFactory
import com.google.android.ground.ui.datacollection.components.TextTaskInput
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskFragment
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint

const val INPUT_TEXT_TEST_TAG: String = "text task input test tag"

/** Fragment allowing the user to answer questions to complete a task. */
@AndroidEntryPoint
class TextTaskFragment : AbstractTaskFragment<TextTaskViewModel>() {
Expand All @@ -41,6 +46,8 @@ class TextTaskFragment : AbstractTaskFragment<TextTaskViewModel>() {
@Composable
private fun ShowTextInputField() {
val userResponse by viewModel.responseText.observeAsState("")
TextTaskInput(userResponse) { newText -> viewModel.setValue(fromString(newText)) }
TextTaskInput(userResponse, modifier = Modifier.testTag(INPUT_TEXT_TEST_TAG)) { newText ->
viewModel.setValue(fromString(newText))
}
}
}
45 changes: 0 additions & 45 deletions ground/src/main/res/layout/number_task_frag.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.google.android.ground.ui.datacollection

import android.text.InputType
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotDisplayed
Expand All @@ -39,14 +38,13 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withInputType
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.google.android.ground.BaseHiltTest
import com.google.android.ground.CustomViewActions.forceTypeText
import com.google.android.ground.R
import com.google.android.ground.ui.datacollection.tasks.multiplechoice.MULTIPLE_CHOICE_LIST_TEST_TAG
import com.google.android.ground.ui.datacollection.tasks.multiplechoice.OTHER_INPUT_TEXT_TEST_TAG
import com.google.android.ground.ui.datacollection.tasks.multiplechoice.SELECT_MULTIPLE_RADIO_TEST_TAG
import com.google.android.ground.ui.datacollection.tasks.number.INPUT_NUMBER_TEST_TAG
import com.google.android.ground.ui.datacollection.tasks.text.INPUT_TEXT_TEST_TAG
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand All @@ -73,28 +71,38 @@ class TaskFragmentRunner(
return this
}

private fun getInputNode() = baseHiltTest.composeTestRule.onNodeWithTag(INPUT_TEXT_TEST_TAG)
private fun getTextInputNode() = baseHiltTest.composeTestRule.onNodeWithTag(INPUT_TEXT_TEST_TAG)

internal fun inputText(text: String): TaskFragmentRunner {
getInputNode().assertIsDisplayed().performTextInput(text)
getTextInputNode().assertIsDisplayed().performTextInput(text)
return this
}

internal fun clearInputText(): TaskFragmentRunner {
getInputNode().performTextClearance()
getTextInputNode().performTextClearance()
return this
}

internal fun assertInputTextDisplayed(text: String): TaskFragmentRunner {
getInputNode().assertIsDisplayed().assertTextContains(text)
getTextInputNode().assertIsDisplayed().assertTextContains(text)
return this
}

private fun getNumberInputNode() =
baseHiltTest.composeTestRule.onNodeWithTag(INPUT_NUMBER_TEST_TAG)

internal fun inputNumber(number: Double): TaskFragmentRunner {
val decimalNumberFlag = InputType.TYPE_CLASS_NUMBER + InputType.TYPE_NUMBER_FLAG_DECIMAL
onView(withId(R.id.user_response_text))
.check(matches(withInputType(decimalNumberFlag)))
.perform(forceTypeText(number.toString()))
getNumberInputNode().performTextInput(number.toString())
return this
}

internal fun clearInputNumber(): TaskFragmentRunner {
getNumberInputNode().performTextClearance()
return this
}

internal fun assertInputNumberDisplayed(text: String): TaskFragmentRunner {
getNumberInputNode().assertIsDisplayed().assertTextContains(text)
return this
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@
*/
package com.google.android.ground.ui.datacollection.tasks.number

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import com.google.android.ground.R
import com.google.android.ground.model.job.Job
import com.google.android.ground.model.submission.NumberTaskData
import com.google.android.ground.model.task.Task
Expand Down Expand Up @@ -65,12 +58,7 @@ class NumberTaskFragmentTest : BaseTaskFragmentTest<NumberTaskFragment, NumberTa
fun testResponse_defaultIsEmpty() = runWithTestDispatcher {
setupTaskFragment<NumberTaskFragment>(job, task)

onView(withId(R.id.user_response_text))
.check(matches(withText("")))
.check(matches(isDisplayed()))
.check(matches(isEnabled()))

runner().assertButtonIsDisabled("Next")
runner().assertInputNumberDisplayed("").assertButtonIsDisabled("Next")

hasValue(null)
}
Expand All @@ -79,11 +67,24 @@ class NumberTaskFragmentTest : BaseTaskFragmentTest<NumberTaskFragment, NumberTa
fun testResponse_onUserInput_nextButtonIsEnabled() = runWithTestDispatcher {
setupTaskFragment<NumberTaskFragment>(job, task)

runner().inputNumber(123.1).assertButtonIsEnabled("Next")
runner().inputNumber(123.1).assertInputNumberDisplayed("123.1").assertButtonIsEnabled("Next")

hasValue(NumberTaskData("123.1"))
}

@Test
fun `deleting number resets the displayed text and next button`() = runWithTestDispatcher {
setupTaskFragment<NumberTaskFragment>(job, task)

runner()
.inputNumber(129.2)
.clearInputNumber()
.assertInputNumberDisplayed("")
.assertButtonIsDisabled("Next")

hasValue(null)
}

@Test
fun testActionButtons() {
setupTaskFragment<NumberTaskFragment>(job, task)
Expand Down

0 comments on commit acb6193

Please sign in to comment.