Skip to content

Commit

Permalink
Merge 0178160 into 06a69c7
Browse files Browse the repository at this point in the history
  • Loading branch information
shobhitagarwal1612 authored Jan 2, 2025
2 parents 06a69c7 + 0178160 commit 39f4723
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ package com.google.android.ground.ui.datacollection.tasks.text

import android.view.LayoutInflater
import android.view.View
import com.google.android.ground.databinding.TextTaskFragBinding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.platform.ComposeView
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.tasks.AbstractTaskFragment
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint

/** Fragment allowing the user to answer questions to complete a task. */
Expand All @@ -30,10 +35,12 @@ class TextTaskFragment : AbstractTaskFragment<TextTaskViewModel>() {
override fun onCreateTaskView(inflater: LayoutInflater): TaskView =
TaskViewFactory.createWithHeader(inflater)

override fun onCreateTaskBody(inflater: LayoutInflater): View {
val taskBinding = TextTaskFragBinding.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) { newText -> viewModel.setValue(fromString(newText)) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.ui.datacollection.tasks.text

import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme
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,
valueChanged: (text: String) -> Unit = {},
) {
TextField(
value = value,
onValueChange = { valueChanged(it) },
modifier =
modifier
.wrapContentWidth(align = Alignment.Start)
.wrapContentHeight(align = Alignment.Top)
// TODO: Add horizontal padding as 16.dp when global padding is removed.
.padding(vertical = 8.dp)
.testTag(INPUT_TEXT_TEST_TAG),
textStyle = MaterialTheme.typography.bodyLarge,
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
)
}

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
fun TextTaskInputPreview() {
AppTheme { TextTaskInput(value = "Some value") }
}

@Preview
@Composable
@ExcludeFromJacocoGeneratedReport
fun TextTaskInputEmptyPreview() {
AppTheme { TextTaskInput(value = "") }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@
*/
package com.google.android.ground.ui.datacollection.tasks.text

import android.text.Editable
import android.text.TextWatcher
import androidx.lifecycle.LiveData
import androidx.lifecycle.asLiveData
import com.google.android.ground.model.submission.TaskData
import com.google.android.ground.model.submission.TextTaskData
import com.google.android.ground.model.submission.TextTaskData.Companion.fromString
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.filterIsInstance
Expand All @@ -31,20 +28,5 @@ class TextTaskViewModel @Inject constructor() : AbstractTaskViewModel() {

/** Transcoded text to be displayed for the current [TaskData]. */
val responseText: LiveData<String> =
taskTaskData.filterIsInstance<TextTaskData>().map { it.text }.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(fromString(s.toString()))
}

override fun afterTextChanged(s: Editable) {
// Do nothing.
}
}
taskTaskData.filterIsInstance<TextTaskData?>().map { it?.text ?: "" }.asLiveData()
}
41 changes: 0 additions & 41 deletions ground/src/main/res/layout/text_task_frag.xml

This file was deleted.

18 changes: 6 additions & 12 deletions ground/src/main/res/navigation/nav_graph.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,38 +143,32 @@
<fragment
android:id="@+id/drop_pin_task_fragment"
android:name="com.google.android.ground.ui.datacollection.tasks.point.DropPinTaskFragment"
android:label="@string/collect_data"
tools:layout="@layout/text_task_frag" />
android:label="@string/collect_data" />

<fragment
android:id="@+id/multiple_choice_task_fragment"
android:name="com.google.android.ground.ui.datacollection.tasks.multiplechoice.MultipleChoiceTaskFragment"
android:label="@string/collect_data"
tools:layout="@layout/text_task_frag" />
android:label="@string/collect_data" />

<fragment
android:id="@+id/number_task_fragment"
android:name="com.google.android.ground.ui.datacollection.tasks.number.NumberTaskFragment"
android:label="@string/collect_data"
tools:layout="@layout/text_task_frag" />
android:label="@string/collect_data" />

<fragment
android:id="@+id/photo_task_fragment"
android:name="com.google.android.ground.ui.datacollection.tasks.photo.PhotoTaskFragment"
android:label="@string/collect_data"
tools:layout="@layout/text_task_frag" />
android:label="@string/collect_data" />

<fragment
android:id="@+id/polygon_task_fragment"
android:name="com.google.android.ground.ui.datacollection.tasks.polygon.DrawAreaTaskFragment"
android:label="@string/collect_data"
tools:layout="@layout/text_task_frag" />
android:label="@string/collect_data" />

<fragment
android:id="@+id/question_task_fragment"
android:name="com.google.android.ground.ui.datacollection.tasks.text.TextTaskFragment"
android:label="@string/collect_data"
tools:layout="@layout/text_task_frag" />
android:label="@string/collect_data" />
</navigation>
<fragment
android:id="@+id/offline_areas_fragment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class DataCollectionFragmentTest : BaseHiltTest() {
)

runner()
.validateTextIsDisplayed(TASK_1_RESPONSE)
.assertInputTextDisplayed(TASK_1_RESPONSE)
.clickNextButton()
.assertOptionsDisplayed(TASK_2_OPTION_LABEL)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.assertTextContains
import androidx.compose.ui.test.hasAnySibling
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasTestTag
Expand All @@ -35,7 +36,6 @@ import androidx.compose.ui.test.performScrollToNode
import androidx.compose.ui.test.performTextClearance
import androidx.compose.ui.test.performTextInput
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
Expand All @@ -47,11 +47,11 @@ 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.text.INPUT_TEXT_TEST_TAG
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.allOf

/** Helper class for interacting with the data collection tasks and verifying the ui state. */
class TaskFragmentRunner(
Expand All @@ -73,8 +73,20 @@ class TaskFragmentRunner(
return this
}

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

internal fun inputText(text: String): TaskFragmentRunner {
onView(allOf(withId(R.id.user_response_text), isDisplayed())).perform(typeText(text))
getInputNode().assertIsDisplayed().performTextInput(text)
return this
}

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@

package com.google.android.ground.ui.datacollection.tasks.text

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.TextTaskData
import com.google.android.ground.model.task.Task
Expand Down Expand Up @@ -60,14 +53,31 @@ class TextTaskFragmentTest : BaseTaskFragmentTest<TextTaskFragment, TextTaskView
fun testResponse_defaultIsEmpty() = runWithTestDispatcher {
setupTaskFragment<TextTaskFragment>(job, task)

onView(withId(R.id.user_response_text))
.check(matches(withText("")))
.check(matches(isDisplayed()))
.check(matches(isEnabled()))
runner().assertInputTextDisplayed("").assertButtonIsDisabled("Next")

hasValue(null)
}

@Test
fun `inserted text is displayed`() = runWithTestDispatcher {
setupTaskFragment<TextTaskFragment>(job, task)

runner().inputText("some text").assertInputTextDisplayed("some text")

hasValue(TextTaskData("some text"))
}

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

runner()
.inputText("some text")
.clearInputText()
.assertInputTextDisplayed("")
.assertButtonIsDisabled("Next")

runner().assertButtonIsDisabled("Next")
hasValue(null)
}

@Test
Expand Down

0 comments on commit 39f4723

Please sign in to comment.