Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Multiple Choice Task] Fix UI bugs in multiple choice selectors #2899

Merged
merged 17 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,4 @@ data class MultipleChoiceItem(
val isSelected: Boolean = false,
val isOtherOption: Boolean = false,
val otherText: String = "",
) {
fun isTheSameItem(otherItem: MultipleChoiceItem): Boolean = this.option.id == otherItem.option.id

fun areContentsTheSame(otherItem: MultipleChoiceItem): Boolean =
otherItem.isSelected == this.isSelected
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Copyright 2024 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.multiplechoice

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
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.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.android.ground.ExcludeFromJacocoGeneratedReport
import com.google.android.ground.R
import com.google.android.ground.model.task.MultipleChoice
import com.google.android.ground.model.task.Option
import com.google.android.ground.ui.theme.AppTheme

const val MULTIPLE_CHOICE_ITEM_TEST_TAG = "multiple choice item test tag"
const val OTHER_INPUT_TEXT_TEST_TAG = "other input test tag"
const val SELECT_MULTIPLE_RADIO_TEST_TAG = "select multiple radio test tag"

/**
* A composable function that displays a single item in a multiple-choice list.
*
* This composable provides a visually consistent and interactive way to present an option within a
* list of choices where the user can select one or more items. It typically includes a text label
* and a selectable indicator (e.g., a checkbox).
*/
@Composable
fun MultipleChoiceItemView(
item: MultipleChoiceItem,
modifier: Modifier = Modifier,
isLastIndex: Boolean = false,
toggleItem: (item: MultipleChoiceItem) -> Unit = {},
otherValueChanged: (text: String) -> Unit = {},
anandwana001 marked this conversation as resolved.
Show resolved Hide resolved
) {
Column(modifier = Modifier.testTag(MULTIPLE_CHOICE_ITEM_TEST_TAG)) {
Row(modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
when (item.cardinality) {
MultipleChoice.Cardinality.SELECT_ONE -> {
RadioButton(
modifier = Modifier.testTag(SELECT_MULTIPLE_RADIO_TEST_TAG),
selected = item.isSelected,
onClick = { toggleItem(item) },
anandwana001 marked this conversation as resolved.
Show resolved Hide resolved
)
}

MultipleChoice.Cardinality.SELECT_MULTIPLE -> {
Checkbox(checked = item.isSelected, onCheckedChange = { toggleItem(item) })
}
}

ClickableText(
text = item.toTextLabel(),
modifier = modifier,
style = MaterialTheme.typography.bodyLarge,
onClick = { toggleItem(item) },
)
}

if (item.isOtherOption) {
Row(modifier = modifier.padding(horizontal = 48.dp)) {
TextField(
value = item.otherText,
textStyle = MaterialTheme.typography.bodyLarge,
onValueChange = { otherValueChanged(it) },
modifier = Modifier.testTag(OTHER_INPUT_TEXT_TEST_TAG),
)
}
}

if (!isLastIndex) {
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
}
}
}

@Composable
private fun MultipleChoiceItem.toTextLabel() =
AnnotatedString(if (isOtherOption) stringResource(id = R.string.other) else option.label)

@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
@Composable
@ExcludeFromJacocoGeneratedReport
fun SelectOneListItemPreview() {
AppTheme {

Check warning on line 110 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L110

Added line #L110 was not covered by tests
MultipleChoiceItemView(
item =
MultipleChoiceItem(
Option(id = "id", code = "code", label = "Option 1"),
cardinality = MultipleChoice.Cardinality.SELECT_ONE,
isSelected = false,

Check warning on line 116 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L113-L116

Added lines #L113 - L116 were not covered by tests
)
)
}

Check warning on line 119 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L118-L119

Added lines #L118 - L119 were not covered by tests
}

@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
@Composable
@ExcludeFromJacocoGeneratedReport
fun SelectMultipleListItemPreview() {
AppTheme {

Check warning on line 126 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L126

Added line #L126 was not covered by tests
MultipleChoiceItemView(
item =
MultipleChoiceItem(
Option(id = "id", code = "code", label = "Option 2"),
cardinality = MultipleChoice.Cardinality.SELECT_MULTIPLE,
isSelected = false,

Check warning on line 132 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L129-L132

Added lines #L129 - L132 were not covered by tests
)
)
}

Check warning on line 135 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L134-L135

Added lines #L134 - L135 were not covered by tests
}

@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
@Composable
@ExcludeFromJacocoGeneratedReport
fun SelectOneOtherListItemPreview() {
AppTheme {

Check warning on line 142 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L142

Added line #L142 was not covered by tests
MultipleChoiceItemView(
item =
MultipleChoiceItem(
Option(id = "id", code = "code", label = "Option 3"),
cardinality = MultipleChoice.Cardinality.SELECT_ONE,
isSelected = true,
isOtherOption = true,
otherText = "Other text",

Check warning on line 150 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L145-L150

Added lines #L145 - L150 were not covered by tests
)
)
}

Check warning on line 153 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L152-L153

Added lines #L152 - L153 were not covered by tests
}

@Preview(backgroundColor = 0xFFFFFFFF, showBackground = true)
@Composable
@ExcludeFromJacocoGeneratedReport
fun SelectMultipleOtherListItemPreview() {
AppTheme {

Check warning on line 160 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L160

Added line #L160 was not covered by tests
MultipleChoiceItemView(
item =
MultipleChoiceItem(
Option(id = "id", code = "code", label = "Option 4"),
cardinality = MultipleChoice.Cardinality.SELECT_MULTIPLE,
isSelected = true,
isOtherOption = true,
otherText = "Other text",

Check warning on line 168 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L163-L168

Added lines #L163 - L168 were not covered by tests
)
)
}

Check warning on line 171 in ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt

View check run for this annotation

Codecov / codecov/patch

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/multiplechoice/MultipleChoiceItemView.kt#L170-L171

Added lines #L170 - L171 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,51 @@ package com.google.android.ground.ui.datacollection.tasks.multiplechoice

import android.view.LayoutInflater
import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.google.android.ground.databinding.MultipleChoiceTaskFragBinding
import com.google.android.ground.model.task.MultipleChoice
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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.lifecycle.asLiveData
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
import kotlinx.coroutines.launch

const val MULTIPLE_CHOICE_LIST_TEST_TAG = "multiple choice items test tag"

/**
* Fragment allowing the user to answer single selection multiple choice questions to complete a
* task.
*/
@AndroidEntryPoint
class MultipleChoiceTaskFragment : AbstractTaskFragment<MultipleChoiceTaskViewModel>() {
private lateinit var binding: MultipleChoiceTaskFragBinding
private lateinit var multipleChoiceAdapter:
ListAdapter<MultipleChoiceItem, RecyclerView.ViewHolder>

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

override fun onCreateTaskBody(inflater: LayoutInflater): View {
binding = MultipleChoiceTaskFragBinding.inflate(inflater)
setupMultipleChoice(binding.selectOptionList)
return binding.root
}
override fun onCreateTaskBody(inflater: LayoutInflater): View =
ComposeView(requireContext()).apply { setContent { AppTheme { ShowMultipleChoiceItems() } } }

// TODO: Test comment for adding links to repo.
private fun setupMultipleChoice(recyclerView: RecyclerView) {
val multipleChoice = checkNotNull(getTask().multipleChoice)
val canSelectMultiple = multipleChoice.cardinality == MultipleChoice.Cardinality.SELECT_MULTIPLE
multipleChoiceAdapter = MultipleChoiceAdapter(viewModel, canSelectMultiple)
recyclerView.apply {
adapter = multipleChoiceAdapter
itemAnimator = null
setHasFixedSize(true)
}
lifecycleScope.launch {
viewModel.itemsFlow.collect { items -> multipleChoiceAdapter.submitList(items) }
@Composable
private fun ShowMultipleChoiceItems() {
val list by viewModel.itemsFlow.asLiveData().observeAsState()
list?.let { items ->
LazyColumn(Modifier.fillMaxSize().testTag(MULTIPLE_CHOICE_LIST_TEST_TAG)) {
items(items) { item ->
MultipleChoiceItemView(
item = item,
isLastIndex = items.indexOf(item) == items.lastIndex,
toggleItem = { viewModel.onItemToggled(it) },
otherValueChanged = { viewModel.onOtherTextChanged(it) },
)
}
}
}
}
}
Loading
Loading