Render ended poll with winning answers
This commit is contained in:
@@ -21,7 +21,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider
|
||||
import io.element.android.features.poll.api.ActivePollContentView
|
||||
import io.element.android.features.poll.api.PollContentView
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.matrix.api.poll.PollAnswer
|
||||
@@ -33,10 +33,11 @@ fun TimelineItemPollView(
|
||||
onAnswerSelected: (PollAnswer) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ActivePollContentView(
|
||||
PollContentView(
|
||||
question = content.question,
|
||||
answerItems = content.answerItems.toImmutableList(),
|
||||
pollKind = content.pollKind,
|
||||
isPollEnded = content.isEnded,
|
||||
onAnswerSelected = onAnswerSelected,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
@@ -38,19 +38,23 @@ class TimelineItemContentPollFactory @Inject constructor(
|
||||
// Todo Move this computation to the matrix rust sdk
|
||||
val pollVotesCount = content.votes.flatMap { it.value }.size
|
||||
val userVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys
|
||||
val isEndedPoll = content.endTime != null
|
||||
val winnerIds = content.answers.map { it.id }
|
||||
.groupBy { content.votes[it]?.size ?: 0 } // Group by votes count
|
||||
.maxBy { it.key } // Keep max voted answers
|
||||
.takeIf { it.key > 0 } // Ignore if no option has been voted
|
||||
?.value.orEmpty()
|
||||
val answerItems = content.answers.map { answer ->
|
||||
val votesCount = content.votes[answer.id]?.size ?: 0
|
||||
val isSelected = answer.id in userVotes
|
||||
val percentage = when {
|
||||
pollVotesCount == 0 -> 0f
|
||||
content.kind.isDisclosed -> votesCount.toFloat() / pollVotesCount.toFloat()
|
||||
isSelected -> 1f
|
||||
else -> 0f
|
||||
}
|
||||
val isWinner = answer.id in winnerIds
|
||||
val percentage = if (pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f
|
||||
PollAnswerItem(
|
||||
answer = answer,
|
||||
isSelected = isSelected,
|
||||
isDisclosed = content.kind.isDisclosed,
|
||||
isEnabled = isEndedPoll,
|
||||
isWinner = isWinner,
|
||||
isDisclosed = content.kind.isDisclosed || isEndedPoll,
|
||||
votesCount = votesCount,
|
||||
percentage = percentage,
|
||||
)
|
||||
@@ -61,6 +65,7 @@ class TimelineItemContentPollFactory @Inject constructor(
|
||||
answerItems = answerItems,
|
||||
votes = content.votes,
|
||||
pollKind = content.kind,
|
||||
isEnded = isEndedPoll,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ data class TimelineItemPollContent(
|
||||
val answerItems: List<PollAnswerItem>,
|
||||
val votes: Map<String, List<UserId>>,
|
||||
val pollKind: PollKind,
|
||||
val isEnded: Boolean,
|
||||
) : TimelineItemEventContent {
|
||||
override val type: String = "TimelineItemPollContent"
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ fun aTimelineItemPollContent(): TimelineItemPollContent {
|
||||
pollKind = PollKind.Disclosed,
|
||||
question = "What type of food should we have at the party?",
|
||||
answerItems = aPollAnswerItemList(),
|
||||
isEnded = false,
|
||||
votes = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import io.element.android.libraries.matrix.api.poll.PollAnswer
|
||||
*
|
||||
* @property answer the poll answer.
|
||||
* @property isSelected whether the user has selected this answer.
|
||||
* @property isEnabled whether the answer can be voted.
|
||||
* @property isWinner whether this is the winner answer in the poll.
|
||||
* @property isDisclosed whether the votes for this answer should be disclosed.
|
||||
* @property votesCount the number of votes for this answer.
|
||||
* @property percentage the percentage of votes for this answer.
|
||||
@@ -30,6 +32,8 @@ import io.element.android.libraries.matrix.api.poll.PollAnswer
|
||||
data class PollAnswerItem(
|
||||
val answer: PollAnswer,
|
||||
val isSelected: Boolean,
|
||||
val isEnabled: Boolean,
|
||||
val isWinner: Boolean,
|
||||
val isDisclosed: Boolean,
|
||||
val votesCount: Int,
|
||||
val percentage: Float,
|
||||
|
||||
@@ -34,13 +34,14 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconToggleButton
|
||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.toEnabledColor
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonPlurals
|
||||
|
||||
@@ -55,6 +56,7 @@ fun PollAnswerView(
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = answerItem.isSelected,
|
||||
enabled = answerItem.isEnabled,
|
||||
onClick = onClick,
|
||||
role = Role.RadioButton,
|
||||
)
|
||||
@@ -62,6 +64,7 @@ fun PollAnswerView(
|
||||
IconToggleButton(
|
||||
modifier = Modifier.size(22.dp),
|
||||
checked = answerItem.isSelected,
|
||||
enabled = answerItem.isEnabled,
|
||||
colors = IconButtonDefaults.iconToggleButtonColors(
|
||||
contentColor = ElementTheme.colors.iconSecondary,
|
||||
checkedContentColor = ElementTheme.colors.iconPrimary,
|
||||
@@ -83,7 +86,8 @@ fun PollAnswerView(
|
||||
Row {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = answerItem.answer.text
|
||||
text = answerItem.answer.text,
|
||||
style = if (answerItem.isWinner) ElementTheme.typography.fontBodyLgMedium else ElementTheme.typography.fontBodyLgRegular,
|
||||
)
|
||||
if (answerItem.isDisclosed) {
|
||||
Text(
|
||||
@@ -93,35 +97,85 @@ fun PollAnswerView(
|
||||
count = answerItem.votesCount,
|
||||
answerItem.votesCount
|
||||
),
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
style = if (answerItem.isWinner) ElementTheme.typography.fontBodySmMedium else ElementTheme.typography.fontBodySmRegular,
|
||||
color = if (answerItem.isWinner) ElementTheme.colors.textPrimary else ElementTheme.colors.textSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
progress = answerItem.percentage,
|
||||
color = if (answerItem.isWinner) ElementTheme.colors.textSuccessPrimary else answerItem.isEnabled.toEnabledColor(),
|
||||
progress = when {
|
||||
answerItem.isDisclosed -> answerItem.percentage
|
||||
answerItem.isSelected -> 1f
|
||||
else -> 0f
|
||||
},
|
||||
strokeCap = StrokeCap.Round,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PollAnswerViewNoResultsPreview() = ElementPreview {
|
||||
internal fun PollAnswerDisclosedNotSelectedPreview() = ElementThemedPreview {
|
||||
PollAnswerView(
|
||||
answerItem = aPollAnswerItem(isSelected = true),
|
||||
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false),
|
||||
onClick = { },
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PollAnswerViewWithResultPreview() = ElementPreview {
|
||||
internal fun PollAnswerDisclosedSelectedPreview() = ElementThemedPreview {
|
||||
PollAnswerView(
|
||||
answerItem = aPollAnswerItem(isDisclosed = false),
|
||||
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true),
|
||||
onClick = { }
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PollAnswerUndisclosedNotSelectedPreview() = ElementThemedPreview {
|
||||
PollAnswerView(
|
||||
answerItem = aPollAnswerItem(isDisclosed = false, isSelected = false),
|
||||
onClick = { },
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PollAnswerUndisclosedSelectedPreview() = ElementThemedPreview {
|
||||
PollAnswerView(
|
||||
answerItem = aPollAnswerItem(isDisclosed = false, isSelected = true),
|
||||
onClick = { }
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PollAnswerEndedWinnerNotSelectedPreview() = ElementThemedPreview {
|
||||
PollAnswerView(
|
||||
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false, isEnabled = false, isWinner = true),
|
||||
onClick = { }
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PollAnswerEndedWinnerSelectedPreview() = ElementThemedPreview {
|
||||
PollAnswerView(
|
||||
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = true),
|
||||
onClick = { }
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun PollAnswerEndedSelectedPreview() = ElementThemedPreview {
|
||||
PollAnswerView(
|
||||
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = false),
|
||||
onClick = { }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,27 +19,33 @@ package io.element.android.features.poll.api
|
||||
import io.element.android.libraries.matrix.api.poll.PollAnswer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
fun aPollAnswerItemList(isDisclosed: Boolean = true) = persistentListOf(
|
||||
fun aPollAnswerItemList(isEnded: Boolean = false, isDisclosed: Boolean = true) = persistentListOf(
|
||||
aPollAnswerItem(
|
||||
answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"),
|
||||
isDisclosed = isDisclosed,
|
||||
isEnabled = !isEnded,
|
||||
isWinner = isEnded,
|
||||
votesCount = 5,
|
||||
percentage = 0.5f
|
||||
),
|
||||
aPollAnswerItem(
|
||||
answer = PollAnswer("option_2", "Chinese \uD83C\uDDE8\uD83C\uDDF3"),
|
||||
isDisclosed = isDisclosed,
|
||||
isEnabled = !isEnded,
|
||||
isWinner = false,
|
||||
votesCount = 0,
|
||||
percentage = 0f
|
||||
),
|
||||
aPollAnswerItem(
|
||||
answer = PollAnswer("option_3", "Brazilian \uD83C\uDDE7\uD83C\uDDF7"),
|
||||
isDisclosed = isDisclosed,
|
||||
isEnabled = !isEnded,
|
||||
isWinner = false,
|
||||
isSelected = true,
|
||||
votesCount = 1,
|
||||
percentage = 0.1f
|
||||
),
|
||||
aPollAnswerItem(isDisclosed = isDisclosed),
|
||||
aPollAnswerItem(isDisclosed = isDisclosed, isEnabled = !isEnded),
|
||||
)
|
||||
|
||||
fun aPollAnswerItem(
|
||||
@@ -48,12 +54,16 @@ fun aPollAnswerItem(
|
||||
"French \uD83C\uDDEB\uD83C\uDDF7 But make it a very very very long option then this should just keep expanding"
|
||||
),
|
||||
isSelected: Boolean = false,
|
||||
isEnabled: Boolean = true,
|
||||
isWinner: Boolean = false,
|
||||
isDisclosed: Boolean = true,
|
||||
votesCount: Int = 4,
|
||||
percentage: Float = 0.4f,
|
||||
) = PollAnswerItem(
|
||||
answer = answer,
|
||||
isSelected = isSelected,
|
||||
isEnabled = isEnabled,
|
||||
isWinner = isWinner,
|
||||
isDisclosed = isDisclosed,
|
||||
votesCount = votesCount,
|
||||
percentage = percentage
|
||||
|
||||
@@ -42,10 +42,11 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun ActivePollContentView(
|
||||
fun PollContentView(
|
||||
question: String,
|
||||
answerItems: ImmutableList<PollAnswerItem>,
|
||||
pollKind: PollKind,
|
||||
isPollEnded: Boolean,
|
||||
onAnswerSelected: (PollAnswer) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -59,9 +60,9 @@ fun ActivePollContentView(
|
||||
|
||||
PollAnswers(answerItems = answerItems, onAnswerSelected = onAnswerSelected)
|
||||
|
||||
when (pollKind) {
|
||||
PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems)
|
||||
PollKind.Undisclosed -> UndisclosedPollBottomNotice()
|
||||
when {
|
||||
isPollEnded || pollKind == PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems)
|
||||
pollKind == PollKind.Undisclosed -> UndisclosedPollBottomNotice()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,22 +127,36 @@ fun ColumnScope.UndisclosedPollBottomNotice() {
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun ActivePollContentNoResultsPreview() = ElementPreview {
|
||||
ActivePollContentView(
|
||||
internal fun PollContentNoResultsPreview() = ElementPreview {
|
||||
PollContentView(
|
||||
question = "What type of food should we have at the party?",
|
||||
answerItems = aPollAnswerItemList(isDisclosed = false),
|
||||
pollKind = PollKind.Undisclosed,
|
||||
isPollEnded = false,
|
||||
onAnswerSelected = { },
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun ActivePollContentWithResultsPreview() = ElementPreview {
|
||||
ActivePollContentView(
|
||||
internal fun PollContentWithResultsPreview() = ElementPreview {
|
||||
PollContentView(
|
||||
question = "What type of food should we have at the party?",
|
||||
answerItems = aPollAnswerItemList(),
|
||||
pollKind = PollKind.Disclosed,
|
||||
isPollEnded = false,
|
||||
onAnswerSelected = { },
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun PollContentEndedPreview() = ElementPreview {
|
||||
PollContentView(
|
||||
question = "What type of food should we have at the party?",
|
||||
answerItems = aPollAnswerItemList(isEnded = true),
|
||||
pollKind = PollKind.Disclosed,
|
||||
isPollEnded = false,
|
||||
onAnswerSelected = { },
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user