Update UI when viewing votes.

This commit is contained in:
Michelle Tang
2025-10-17 16:57:54 -04:00
committed by Cody Henthorne
parent e4abc6d256
commit 1544cb81d5
4 changed files with 63 additions and 18 deletions

View File

@@ -11,8 +11,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -26,6 +27,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
@@ -38,6 +40,7 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.setFragmentResult
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Dividers
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.horizontalGutters
@@ -85,7 +88,7 @@ class PollVotesFragment : ComposeDialogFragment() {
val state by viewModel.state.collectAsStateWithLifecycle()
Scaffolds.Settings(
title = stringResource(id = R.string.Poll__poll_results),
title = stringResource(if (state.poll?.hasEnded == true) R.string.Poll__poll_results else R.string.Poll__poll_details),
onNavigationClick = this::dismissAllowingStateLoss,
navigationIcon = ImageVector.vectorResource(id = R.drawable.symbol_x_24),
navigationContentDescription = stringResource(id = R.string.Material3SearchToolbar__close)
@@ -117,27 +120,34 @@ private fun PollResultsScreen(
LazyColumn(
modifier = modifier
.fillMaxWidth()
.horizontalGutters(24.dp)
) {
item {
Spacer(Modifier.size(16.dp))
Text(
text = stringResource(R.string.Poll__question),
style = MaterialTheme.typography.titleSmall
style = MaterialTheme.typography.titleSmall,
modifier = Modifier.horizontalGutters()
)
TextField(
value = state.poll!!.question,
onValueChange = {},
modifier = Modifier.padding(top = 12.dp, bottom = 24.dp).fillMaxWidth(),
modifier = Modifier.padding(top = 12.dp, bottom = 24.dp).horizontalGutters().fillMaxWidth(),
colors = TextFieldDefaults.colors(
disabledTextColor = MaterialTheme.colorScheme.onSurface,
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant,
disabledIndicatorColor = Color.Transparent
),
shape = RoundedCornerShape(8.dp),
enabled = false
)
}
items(state.pollOptions) { PollOptionSection(it) }
itemsIndexed(state.pollOptions) { index, option ->
PollOptionSection(option, state.poll!!.hasEnded)
if (index != state.pollOptions.lastIndex) {
Dividers.Default()
}
}
if (state.isAuthor && !state.poll!!.hasEnded) {
item {
@@ -146,9 +156,10 @@ private fun PollResultsScreen(
.fillMaxWidth()
.clickable(onClick = onEndPoll)
.padding(vertical = 16.dp)
.horizontalGutters()
) {
Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_trash_24),
imageVector = ImageVector.vectorResource(id = R.drawable.symbol_stop_24),
contentDescription = stringResource(R.string.Poll__end_poll),
tint = MaterialTheme.colorScheme.onSurface
)
@@ -161,22 +172,40 @@ private fun PollResultsScreen(
@Composable
private fun PollOptionSection(
option: PollOptionModel
option: PollOptionModel,
hasEnded: Boolean
) {
var expand by remember { mutableStateOf(false) }
val context = LocalContext.current
Row(
modifier = Modifier.padding(vertical = 12.dp)
modifier = Modifier.padding(vertical = 12.dp).horizontalGutters(),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = option.pollOption.text, modifier = Modifier.weight(1f), style = MaterialTheme.typography.titleSmall)
Text(text = pluralStringResource(R.plurals.Poll__num_votes, option.voters.size, option.voters.size), style = MaterialTheme.typography.bodyLarge)
if (option.hasMostVotes && hasEnded) {
Icon(
imageVector = ImageVector.vectorResource(R.drawable.symbol_favorite_fill_16),
contentDescription = stringResource(R.string.Poll__poll_winner),
modifier = Modifier.padding(2.dp)
)
}
if (option.voters.isNotEmpty()) {
Text(text = pluralStringResource(R.plurals.Poll__num_votes, option.voters.size, option.voters.size), style = MaterialTheme.typography.bodyLarge)
}
}
if (!expand && option.voters.size > MAX_INITIAL_VOTER_COUNT) {
if (option.voters.isEmpty()) {
Text(
text = stringResource(R.string.Poll__no_votes),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.horizontalGutters()
)
} else if (!expand && option.voters.size > MAX_INITIAL_VOTER_COUNT) {
option.voters.subList(0, MAX_INITIAL_VOTER_COUNT).forEach { recipient ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 12.dp)
modifier = Modifier.padding(vertical = 12.dp).horizontalGutters()
) {
AvatarImage(recipient = recipient, modifier = Modifier.padding(end = 16.dp).size(40.dp))
Text(text = if (recipient.isSelf) stringResource(id = R.string.Recipient_you) else recipient.getShortDisplayName(context))
@@ -185,7 +214,7 @@ private fun PollOptionSection(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 12.dp).clickable { expand = true }
modifier = Modifier.padding(vertical = 12.dp).horizontalGutters().clickable { expand = true }
) {
Image(
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
@@ -199,7 +228,7 @@ private fun PollOptionSection(
option.voters.forEach { recipient ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 12.dp)
modifier = Modifier.padding(vertical = 12.dp).horizontalGutters()
) {
AvatarImage(recipient = recipient, modifier = Modifier.padding(end = 16.dp).size(40.dp))
Text(text = if (recipient.isSelf) stringResource(id = R.string.Recipient_you) else recipient.getShortDisplayName(context))

View File

@@ -33,13 +33,15 @@ class PollVotesViewModel(pollId: Long) : ViewModel() {
private fun loadPollInfo(pollId: Long) {
viewModelScope.launch(SignalDispatchers.IO) {
val poll = SignalDatabase.polls.getPollFromId(pollId)!!
val mostVotes = poll.pollOptions.maxByOrNull { option -> option.voters.size }?.voters?.size
_state.update {
it.copy(
poll = poll,
pollOptions = poll.pollOptions.map { option ->
PollOptionModel(
pollOption = option,
voters = Recipient.resolvedList(option.voters.map { voter -> RecipientId.from(voter.id) })
voters = Recipient.resolvedList(option.voters.map { voter -> RecipientId.from(voter.id) }),
hasMostVotes = option.voters.size == mostVotes
)
},
isAuthor = poll.authorId == Recipient.self().id.toLong()
@@ -57,5 +59,6 @@ data class PollVotesState(
data class PollOptionModel(
val pollOption: PollOption,
val voters: List<Recipient> = emptyList()
val voters: List<Recipient> = emptyList(),
val hasMostVotes: Boolean
)

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="15dp"
android:viewportWidth="16"
android:viewportHeight="15">
<path
android:pathData="M8.951,1.348C8.652,0.427 7.348,0.427 7.049,1.348L5.887,4.925C5.842,5.062 5.714,5.155 5.57,5.155H1.809C0.841,5.155 0.438,6.395 1.222,6.964L4.264,9.174C4.381,9.259 4.43,9.41 4.385,9.547L3.223,13.124C2.924,14.045 3.978,14.811 4.762,14.242L7.804,12.031C7.921,11.946 8.079,11.946 8.196,12.031L11.238,14.242C12.022,14.811 13.076,14.045 12.777,13.124L11.615,9.547C11.57,9.41 11.619,9.259 11.736,9.174L14.778,6.964C15.562,6.395 15.159,5.155 14.191,5.155H10.43C10.286,5.155 10.158,5.062 10.113,4.925L8.951,1.348Z"
android:fillColor="#000000"/>
</vector>

View File

@@ -8809,8 +8809,10 @@
<string name="Poll__poll_voted">%1$s voted in the poll: \"%2$s\"</string>
<!-- Option in settings to configure notifications for polls -->
<string name="Poll__poll">Polls</string>
<!-- Title of screen that shows the poll results of a poll -->
<!-- Title of screen that shows the poll results of a poll after it has ended-->
<string name="Poll__poll_results">Poll results</string>
<!-- Title of screen that shows the poll details of a poll -->
<string name="Poll__poll_details">Poll details</string>
<!-- Header text displaying the question of the poll -->
<string name="Poll__question">Question</string>
<!-- Text displaying how many votes, %1$d, an option has gotten -->
@@ -8818,6 +8820,8 @@
<item quantity="one">%1$d vote</item>
<item quantity="other">%1$d votes</item>
</plurals>
<!-- Content description to describe the icon that is shown next to the winning option when the poll ends -->
<string name="Poll__poll_winner">Winner</string>
<!-- Text that when pressed will end the poll -->
<string name="Poll__end_poll">End poll</string>
<!-- Button that once pressed will show all of the voters for a poll -->