diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt index 7c61466337..b7a39974d4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactory.kt @@ -23,7 +23,7 @@ import io.element.android.features.poll.api.PollAnswerItem import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.poll.PollKind +import io.element.android.libraries.matrix.api.poll.isDisclosed import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import javax.inject.Inject @@ -36,16 +36,15 @@ class TimelineItemContentPollFactory @Inject constructor( if (!featureFlagService.isFeatureEnabled(FeatureFlags.Polls)) return TimelineItemUnknownContent // Todo Move this computation to the matrix rust sdk - val showResults = content.kind == PollKind.Disclosed && matrixClient.sessionId in content.votes.flatMap { it.value } val pollVotesCount = content.votes.flatMap { it.value }.size val userVotes = content.votes.filter { matrixClient.sessionId in it.value }.keys val answerItems = content.answers.map { answer -> val votesCount = content.votes[answer.id]?.size ?: 0 - val progress = if (pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f + val progress = if (content.kind.isDisclosed && pollVotesCount > 0) votesCount.toFloat() / pollVotesCount.toFloat() else 0f PollAnswerItem( answer = answer, isSelected = answer.id in userVotes, - isDisclosed = showResults, + isDisclosed = content.kind.isDisclosed, votesCount = votesCount, progress = progress, ) @@ -56,7 +55,6 @@ class TimelineItemContentPollFactory @Inject constructor( answerItems = answerItems, votes = content.votes, pollKind = content.kind, - isDisclosed = showResults ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt index b8a2fa8bca..fcbe81b8da 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContent.kt @@ -25,7 +25,6 @@ data class TimelineItemPollContent( val answerItems: List, val votes: Map>, val pollKind: PollKind, - val isDisclosed: Boolean, ) : TimelineItemEventContent { override val type: String = "TimelineItemPollContent" } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt index 665d507ead..01253f6e77 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemPollContentProvider.kt @@ -24,14 +24,13 @@ open class TimelineItemPollContentProvider : PreviewParameterProvider get() = sequenceOf( aTimelineItemPollContent(), - aTimelineItemPollContent().copy(isDisclosed = true), + aTimelineItemPollContent().copy(pollKind = PollKind.Undisclosed), ) } fun aTimelineItemPollContent(): TimelineItemPollContent { return TimelineItemPollContent( pollKind = PollKind.Disclosed, - isDisclosed = false, question = "What type of food should we have at the party?", answerItems = aPollAnswerItemList(), votes = emptyMap(), diff --git a/features/poll/api/build.gradle.kts b/features/poll/api/build.gradle.kts index be198ba740..6d94fa1b2f 100644 --- a/features/poll/api/build.gradle.kts +++ b/features/poll/api/build.gradle.kts @@ -27,8 +27,6 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) - implementation(libs.androidx.constraintlayout) - implementation(libs.androidx.constraintlayout.compose) implementation(projects.libraries.matrix.api) ksp(libs.showkase.processor) diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt index 587c3306b1..2f534957cd 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/ActivePollContentView.kt @@ -18,12 +18,14 @@ package io.element.android.features.poll.api import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.BarChart +import androidx.compose.material.icons.outlined.Poll import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -47,54 +49,80 @@ fun ActivePollContentView( onAnswerSelected: (PollAnswer) -> Unit, modifier: Modifier = Modifier, ) { - val showResults = answerItems.any { it.isSelected } Column( modifier = modifier .selectableGroup() .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - ) { - Icon(imageVector = Icons.Default.BarChart, contentDescription = null) - Text( - text = question, - style = ElementTheme.typography.fontBodyLgMedium - ) - } + PollTitle(title = question) - answerItems.forEach { answerItem -> - PollAnswerView( - answerItem = answerItem, - onClick = { onAnswerSelected(answerItem.answer) } - ) - } + PollAnswers(answerItems = answerItems, onAnswerSelected = onAnswerSelected) - val votesCount = answerItems.sumOf { it.votesCount } - when { - pollKind == PollKind.Undisclosed -> { - Text( - modifier = Modifier - .align(Alignment.Start) - .padding(start = 32.dp), - style = ElementTheme.typography.fontBodyXsRegular, - color = ElementTheme.colors.textSecondary, - text = stringResource(CommonStrings.common_poll_undisclosed_text), - ) - } - showResults -> { - Text( - modifier = Modifier.align(Alignment.End), - style = ElementTheme.typography.fontBodyXsRegular, - color = ElementTheme.colors.textSecondary, - text = stringResource(CommonStrings.common_poll_total_votes, votesCount), - ) - } + when (pollKind) { + PollKind.Disclosed -> DisclosedPollBottomNotice(answerItems) + PollKind.Undisclosed -> UndisclosedPollBottomNotice() } } } +@Composable +internal fun PollTitle( + title: String, +) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Icon( + modifier = Modifier.size(22.dp), + imageVector = Icons.Outlined.Poll, + contentDescription = null + ) + Text( + text = title, + style = ElementTheme.typography.fontBodyLgMedium + ) + } +} + +@Composable +internal fun PollAnswers( + answerItems: ImmutableList, + onAnswerSelected: (PollAnswer) -> Unit, +) { + answerItems.forEach { answerItem -> + PollAnswerView( + answerItem = answerItem, + onClick = { onAnswerSelected(answerItem.answer) } + ) + } +} + +@Composable +internal fun ColumnScope.DisclosedPollBottomNotice( + answerItems: ImmutableList, +) { + val votesCount = answerItems.sumOf { it.votesCount } + Text( + modifier = Modifier.align(Alignment.End), + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + text = stringResource(CommonStrings.common_poll_total_votes, votesCount), + ) +} + +@Composable +fun ColumnScope.UndisclosedPollBottomNotice() { + Text( + modifier = Modifier + .align(Alignment.Start) + .padding(start = 34.dp), + style = ElementTheme.typography.fontBodyXsRegular, + color = ElementTheme.colors.textSecondary, + text = stringResource(CommonStrings.common_poll_undisclosed_text), + ) +} + @DayNightPreviews @Composable internal fun ActivePollContentNoResultsPreview() = ElementPreview { diff --git a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt index 26fa6fbb71..d6f4c815f9 100644 --- a/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt +++ b/features/poll/api/src/main/kotlin/io/element/android/features/poll/api/PollAnswerView.kt @@ -16,18 +16,21 @@ package io.element.android.features.poll.api +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.selection.selectable import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment 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.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import androidx.constraintlayout.compose.Visibility import io.element.android.libraries.designsystem.preview.DayNightPreviews import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator @@ -36,16 +39,14 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.ui.strings.CommonPlurals -@Suppress("DestructuringDeclarationWithTooManyEntries") // This is necessary to declare the constraints ids @Composable fun PollAnswerView( answerItem: PollAnswerItem, onClick: () -> Unit, modifier: Modifier = Modifier, ) { - ConstraintLayout( + Row( modifier - .wrapContentHeight() .fillMaxWidth() .selectable( selected = answerItem.isSelected, @@ -53,56 +54,38 @@ fun PollAnswerView( role = Role.RadioButton, ) ) { - val (radioButton, answerText, votesText, progressBar) = createRefs() RadioButton( - modifier = Modifier.constrainAs(radioButton) { - top.linkTo(answerText.top) - bottom.linkTo(answerText.bottom) - start.linkTo(parent.start) - end.linkTo(answerText.start) - }, + modifier = Modifier.size(22.dp), selected = answerItem.isSelected, onClick = null // null recommended for accessibility with screenreaders ) - Text( - modifier = Modifier.constrainAs(answerText) { - width = Dimension.fillToConstraints - top.linkTo(parent.top) - start.linkTo(radioButton.end, margin = 8.dp) - end.linkTo(votesText.start) - bottom.linkTo(progressBar.top) - }, - text = answerItem.answer.text, - ) - Text( - modifier = Modifier.constrainAs(votesText) { - start.linkTo(answerText.end) - end.linkTo(parent.end) - bottom.linkTo(answerText.bottom) - visibility = if (answerItem.isDisclosed) Visibility.Visible else Visibility.Gone - }, - text = pluralStringResource( - id = CommonPlurals.common_poll_votes_count, - count = answerItem.votesCount, - answerItem.votesCount - ), - style = ElementTheme.typography.fontBodySmRegular, - color = ElementTheme.colors.textSecondary, - ) - LinearProgressIndicator( - progress = answerItem.progress, - modifier = Modifier - .constrainAs(progressBar) { - start.linkTo(answerText.start) - end.linkTo(votesText.end) - top.linkTo(answerText.bottom, margin = 10.dp) - bottom.linkTo(parent.bottom) - width = Dimension.fillToConstraints - visibility = if (answerItem.isDisclosed) Visibility.Visible else Visibility.Gone - - }, - strokeCap = StrokeCap.Round, - ) + Spacer(modifier = Modifier.width(12.dp)) + Column { + Row { + Text( + modifier = Modifier.weight(1f), + text = answerItem.answer.text + ) + if (answerItem.isDisclosed) { + Text( + modifier = Modifier.align(Alignment.Bottom), + text = pluralStringResource( + id = CommonPlurals.common_poll_votes_count, + count = answerItem.votesCount, + answerItem.votesCount + ), + style = ElementTheme.typography.fontBodySmRegular, + color = ElementTheme.colors.textSecondary, + ) + } + } + Spacer(modifier = Modifier.height(10.dp)) + LinearProgressIndicator( + modifier = Modifier.fillMaxWidth(), + progress = answerItem.progress, + strokeCap = StrokeCap.Round, + ) + } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt index 85bb7c0256..b78f00bc86 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/poll/PollKind.kt @@ -20,5 +20,8 @@ enum class PollKind { Disclosed, /** Results should be only revealed when the poll is ended. */ - Undisclosed + Undisclosed, } + +val PollKind.isDisclosed: Boolean + get() = this == PollKind.Disclosed