Browse Source

Render ended poll with winning answers

pull/1113/head
Florian Renaud 1 year ago
parent
commit
bb2f5a1330
  1. 5
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt
  2. 19
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt
  3. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt
  4. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt
  5. 4
      features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt
  6. 78
      features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt
  7. 14
      features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt
  8. 31
      features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt

5
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemPollView.kt

@ -21,7 +21,7 @@ import androidx.compose.ui.Modifier @@ -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( @@ -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,
)

19
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt

@ -38,19 +38,23 @@ class TimelineItemContentPollFactory @Inject constructor( @@ -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( @@ -61,6 +65,7 @@ class TimelineItemContentPollFactory @Inject constructor(
answerItems = answerItems,
votes = content.votes,
pollKind = content.kind,
isEnded = isEndedPoll,
)
}
}

1
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt

@ -25,6 +25,7 @@ data class TimelineItemPollContent( @@ -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"
}

1
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt

@ -33,6 +33,7 @@ fun aTimelineItemPollContent(): 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(),
)
}

4
features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerItem.kt

@ -23,6 +23,8 @@ import io.element.android.libraries.matrix.api.poll.PollAnswer @@ -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 @@ -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,

78
features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt

@ -34,13 +34,14 @@ import androidx.compose.ui.Modifier @@ -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( @@ -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( @@ -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( @@ -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( @@ -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 PollAnswerDisclosedNotSelectedPreview() = ElementThemedPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = false),
onClick = { },
)
}
@Preview
@Composable
internal fun PollAnswerDisclosedSelectedPreview() = ElementThemedPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true),
onClick = { }
)
}
@Preview
@Composable
internal fun PollAnswerViewNoResultsPreview() = ElementPreview {
internal fun PollAnswerUndisclosedNotSelectedPreview() = ElementThemedPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isSelected = true),
answerItem = aPollAnswerItem(isDisclosed = false, isSelected = false),
onClick = { },
)
}
@DayNightPreviews
@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 PollAnswerViewWithResultPreview() = ElementPreview {
internal fun PollAnswerEndedSelectedPreview() = ElementThemedPreview {
PollAnswerView(
answerItem = aPollAnswerItem(isDisclosed = false),
answerItem = aPollAnswerItem(isDisclosed = true, isSelected = true, isEnabled = false, isWinner = false),
onClick = { }
)
}

14
features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerViewProvider.kt

@ -19,27 +19,33 @@ package io.element.android.features.poll.api @@ -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( @@ -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

31
features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt → features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollContentView.kt

@ -42,10 +42,11 @@ import io.element.android.libraries.ui.strings.CommonStrings @@ -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( @@ -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() { @@ -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 = { },
)
}
Loading…
Cancel
Save