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 14 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,161 @@
/*
* 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

@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(MultipleChoiceTestTags.MULTIPLE_CHOICE_ITEM)) {
Row(modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
when (item.cardinality) {
MultipleChoice.Cardinality.SELECT_ONE -> {
RadioButton(
modifier = Modifier.testTag(MultipleChoiceTestTags.SELECT_MULTIPLE_RADIO),
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(MultipleChoiceTestTags.OTHER_INPUT_TEXT),
)
}
}

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 99 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#L99

Added line #L99 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 105 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#L102-L105

Added lines #L102 - L105 were not covered by tests
)
)
}

Check warning on line 108 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#L107-L108

Added lines #L107 - L108 were not covered by tests
}

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

Check warning on line 115 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#L115

Added line #L115 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 121 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-L121

Added lines #L118 - L121 were not covered by tests
)
)
}

Check warning on line 124 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#L123-L124

Added lines #L123 - L124 were not covered by tests
}

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

Check warning on line 131 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#L131

Added line #L131 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 139 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-L139

Added lines #L134 - L139 were not covered by tests
)
)
}

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#L141-L142

Added lines #L141 - L142 were not covered by tests
}

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

Check warning on line 149 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#L149

Added line #L149 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 157 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-L157

Added lines #L152 - L157 were not covered by tests
)
)
}

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#L159-L160

Added lines #L159 - L160 were not covered by tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,49 @@ 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

/**
* 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(MultipleChoiceTestTags.MULTIPLE_CHOICE_LIST)) {
items(items) { item ->
MultipleChoiceItemView(
item = item,
isLastIndex = items.indexOf(item) == items.lastIndex,
toggleItem = { viewModel.onItemToggled(it) },
otherValueChanged = { viewModel.onOtherTextChanged(it) },
)
}
}
}
}
}
Loading
Loading