First Commit

This commit is contained in:
2025-12-18 16:28:50 +07:00
commit 8c3e4f491f
9974 changed files with 396488 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-compose-library")
}
android {
namespace = "io.element.android.features.poll.api"
}
dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
implementation(projects.libraries.matrix.api)
}
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.actions
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline
interface EndPollAction {
suspend fun execute(timeline: Timeline, pollStartId: EventId): Result<Unit>
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.actions
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline
interface SendPollResponseAction {
suspend fun execute(
timeline: Timeline,
pollStartId: EventId,
answerId: String
): Result<Unit>
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.create
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.matrix.api.timeline.Timeline
interface CreatePollEntryPoint : FeatureEntryPoint {
data class Params(
val timelineMode: Timeline.Mode,
val mode: CreatePollMode,
)
fun createNode(
parentNode: Node,
buildContext: BuildContext,
params: Params,
): Node
}
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.create
import io.element.android.libraries.matrix.api.core.EventId
sealed interface CreatePollMode {
data object NewPoll : CreatePollMode
data class EditPoll(val eventId: EventId) : CreatePollMode
}
@@ -0,0 +1,13 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.history
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
interface PollHistoryEntryPoint : SimpleFeatureEntryPoint
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.pollcontent
import io.element.android.libraries.matrix.api.poll.PollAnswer
/**
* UI model for a [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 showVotes whether the votes for this answer should be displayed.
* @property votesCount the number of votes for this answer.
* @property percentage the percentage of votes for this answer.
*/
data class PollAnswerItem(
val answer: PollAnswer,
val isSelected: Boolean,
val isEnabled: Boolean,
val isWinner: Boolean,
val showVotes: Boolean,
val votesCount: Int,
val percentage: Float,
)
@@ -0,0 +1,204 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.pollcontent
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
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.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.poll.api.R
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
import io.element.android.libraries.designsystem.toEnabledColor
import io.element.android.libraries.designsystem.utils.CommonDrawables
import io.element.android.libraries.ui.strings.CommonPlurals
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
internal fun PollAnswerView(
answerItem: PollAnswerItem,
modifier: Modifier = Modifier,
) {
val nbVotesText = pluralStringResource(
id = CommonPlurals.common_poll_votes_count,
count = answerItem.votesCount,
answerItem.votesCount,
)
val a11yText = buildString {
val sentenceDelimiter = stringResource(CommonStrings.common_sentence_delimiter)
append(answerItem.answer.text.removeSuffix("."))
if (answerItem.showVotes) {
append(sentenceDelimiter)
append(nbVotesText)
if (answerItem.votesCount != 0) {
append(sentenceDelimiter)
(answerItem.percentage * 100).toInt().let { percent ->
append(pluralStringResource(R.plurals.a11y_polls_percent_of_total, percent, percent))
}
}
if (answerItem.isWinner) {
append(sentenceDelimiter)
append(stringResource(R.string.a11y_polls_winning_answer))
}
}
}
Row(
modifier = modifier
.fillMaxWidth()
.clearAndSetSemantics {
contentDescription = a11yText
},
) {
Icon(
imageVector = if (answerItem.isSelected) {
CompoundIcons.CheckCircleSolid()
} else {
CompoundIcons.Circle()
},
contentDescription = null,
modifier = Modifier
.padding(0.5.dp)
.size(22.dp),
tint = if (answerItem.isEnabled) {
if (answerItem.isSelected) {
ElementTheme.colors.iconPrimary
} else {
ElementTheme.colors.iconSecondary
}
} else {
ElementTheme.colors.iconDisabled
},
)
Spacer(modifier = Modifier.width(12.dp))
Column {
Row {
Text(
modifier = Modifier.weight(1f),
text = answerItem.answer.text,
style = if (answerItem.isWinner) ElementTheme.typography.fontBodyLgMedium else ElementTheme.typography.fontBodyLgRegular,
)
if (answerItem.showVotes) {
Row(
modifier = Modifier.align(Alignment.Bottom),
verticalAlignment = Alignment.CenterVertically,
) {
if (answerItem.isWinner) {
Icon(
resourceId = CommonDrawables.ic_winner,
contentDescription = null,
tint = ElementTheme.colors.iconAccentTertiary,
)
Spacer(modifier = Modifier.width(2.dp))
Text(
text = nbVotesText,
style = ElementTheme.typography.fontBodySmMedium,
color = ElementTheme.colors.textPrimary,
)
} else {
Text(
text = nbVotesText,
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
)
}
}
}
}
Spacer(modifier = Modifier.height(10.dp))
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
color = if (answerItem.isWinner) ElementTheme.colors.textSuccessPrimary else answerItem.isEnabled.toEnabledColor(),
progress = {
when {
answerItem.showVotes -> answerItem.percentage
answerItem.isSelected -> 1f
else -> 0f
}
},
trackColor = ElementTheme.colors.progressIndicatorTrackColor,
strokeCap = StrokeCap.Round,
)
}
}
}
@PreviewsDayNight
@Composable
internal fun PollAnswerViewDisclosedNotSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(showVotes = true, isSelected = false),
)
}
@PreviewsDayNight
@Composable
internal fun PollAnswerViewDisclosedSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(showVotes = true, isSelected = true),
)
}
@PreviewsDayNight
@Composable
internal fun PollAnswerViewUndisclosedNotSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(showVotes = false, isSelected = false),
)
}
@PreviewsDayNight
@Composable
internal fun PollAnswerViewUndisclosedSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(showVotes = false, isSelected = true),
)
}
@PreviewsDayNight
@Composable
internal fun PollAnswerViewEndedWinnerNotSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(showVotes = true, isSelected = false, isEnabled = false, isWinner = true),
)
}
@PreviewsDayNight
@Composable
internal fun PollAnswerViewEndedWinnerSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(showVotes = true, isSelected = true, isEnabled = false, isWinner = true),
)
}
@PreviewsDayNight
@Composable
internal fun PollAnswerViewEndedSelectedPreview() = ElementPreview {
PollAnswerView(
answerItem = aPollAnswerItem(showVotes = true, isSelected = true, isEnabled = false, isWinner = false),
)
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.pollcontent
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.poll.PollKind
import kotlinx.collections.immutable.ImmutableList
/**
* UI model for a PollContent.
* @property eventId the event id of the poll.
* @property question the poll question.
* @property answerItems the list of answers.
* @property pollKind the kind of poll.
* @property isPollEditable whether the poll is editable.
* @property isPollEnded whether the poll is ended.
* @property isMine whether the poll has been created by me.
*/
data class PollContentState(
val eventId: EventId?,
val question: String,
val answerItems: ImmutableList<PollAnswerItem>,
val pollKind: PollKind,
val isPollEditable: Boolean,
val isPollEnded: Boolean,
val isMine: Boolean,
)
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.pollcontent
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
interface PollContentStateFactory {
suspend fun create(eventTimelineItem: EventTimelineItem, content: PollContent): PollContentState {
return create(
eventId = eventTimelineItem.eventId,
isEditable = eventTimelineItem.isEditable,
isOwn = eventTimelineItem.isOwn,
content = content,
)
}
suspend fun create(eventId: EventId?, isEditable: Boolean, isOwn: Boolean, content: PollContent): PollContentState
}
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.pollcontent
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.poll.PollAnswer
import io.element.android.libraries.matrix.api.poll.PollKind
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
fun aPollQuestion() = "What type of food should we have at the party?"
fun aPollAnswerItemList(
hasVotes: Boolean = true,
isEnded: Boolean = false,
showVotes: Boolean = true,
) = persistentListOf(
aPollAnswerItem(
answer = PollAnswer("option_1", "Italian \uD83C\uDDEE\uD83C\uDDF9"),
showVotes = showVotes,
isEnabled = !isEnded,
isWinner = isEnded,
votesCount = if (hasVotes) 5 else 0,
percentage = if (hasVotes) 0.5f else 0f
),
aPollAnswerItem(
answer = PollAnswer("option_2", "Chinese \uD83C\uDDE8\uD83C\uDDF3"),
showVotes = showVotes,
isEnabled = !isEnded,
isWinner = false,
votesCount = 0,
percentage = 0f
),
aPollAnswerItem(
answer = PollAnswer("option_3", "Brazilian \uD83C\uDDE7\uD83C\uDDF7"),
showVotes = showVotes,
isEnabled = !isEnded,
isWinner = false,
isSelected = true,
votesCount = if (hasVotes) 1 else 0,
percentage = if (hasVotes) 0.1f else 0f
),
aPollAnswerItem(
showVotes = showVotes,
isEnabled = !isEnded,
votesCount = if (hasVotes) 4 else 0,
percentage = if (hasVotes) 0.4f else 0f,
),
)
fun aPollAnswerItem(
answer: PollAnswer = PollAnswer(
"option_4",
"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,
showVotes: Boolean = true,
votesCount: Int = 4,
percentage: Float = 0.4f,
) = PollAnswerItem(
answer = answer,
isSelected = isSelected,
isEnabled = isEnabled,
isWinner = isWinner,
showVotes = showVotes,
votesCount = votesCount,
percentage = percentage
)
fun aPollContentState(
eventId: EventId? = null,
isMine: Boolean = false,
isEnded: Boolean = false,
showVotes: Boolean = true,
isPollEditable: Boolean = true,
hasVotes: Boolean = true,
question: String = aPollQuestion(),
pollKind: PollKind = PollKind.Disclosed,
answerItems: ImmutableList<PollAnswerItem> = aPollAnswerItemList(
isEnded = isEnded,
showVotes = showVotes,
hasVotes = hasVotes
),
) = PollContentState(
eventId = eventId,
question = question,
answerItems = answerItems,
pollKind = pollKind,
isPollEditable = isMine && !isEnded && isPollEditable,
isPollEnded = isEnded,
isMine = isMine,
)
@@ -0,0 +1,302 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.pollcontent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.poll.PollAnswer
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
@Composable
fun PollContentView(
state: PollContentState,
onSelectAnswer: (pollStartId: EventId, answerId: String) -> Unit,
onEditPoll: (pollStartId: EventId) -> Unit,
onEndPoll: (pollStartId: EventId) -> Unit,
modifier: Modifier = Modifier,
) {
PollContentView(
eventId = state.eventId,
question = state.question,
answerItems = state.answerItems,
pollKind = state.pollKind,
isPollEditable = state.isPollEditable,
isPollEnded = state.isPollEnded,
isMine = state.isMine,
onEditPoll = onEditPoll,
onSelectAnswer = onSelectAnswer,
onEndPoll = onEndPoll,
modifier = modifier,
)
}
@Composable
fun PollContentView(
eventId: EventId?,
question: String,
answerItems: ImmutableList<PollAnswerItem>,
pollKind: PollKind,
isPollEditable: Boolean,
isPollEnded: Boolean,
isMine: Boolean,
onSelectAnswer: (pollStartId: EventId, answerId: String) -> Unit,
onEditPoll: (pollStartId: EventId) -> Unit,
onEndPoll: (pollStartId: EventId) -> Unit,
modifier: Modifier = Modifier,
) {
val votesCount = remember(answerItems) { answerItems.sumOf { it.votesCount } }
fun onSelectAnswer(pollAnswer: PollAnswer) {
eventId?.let { onSelectAnswer(it, pollAnswer.id) }
}
fun onEditPoll() {
eventId?.let { onEditPoll(it) }
}
fun onEndPoll() {
eventId?.let { onEndPoll(it) }
}
var showConfirmation: Boolean by remember { mutableStateOf(false) }
if (showConfirmation) {
ConfirmationDialog(
content = stringResource(id = CommonStrings.common_poll_end_confirmation),
onSubmitClick = {
onEndPoll()
showConfirmation = false
},
onDismiss = { showConfirmation = false },
)
}
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
PollTitleView(title = question, isPollEnded = isPollEnded)
PollAnswers(answerItems = answerItems, onSelectAnswer = ::onSelectAnswer)
if (isPollEnded || pollKind == PollKind.Disclosed) {
DisclosedPollBottomNotice(votesCount = votesCount)
} else {
UndisclosedPollBottomNotice()
}
if (isMine) {
CreatorView(
isPollEnded = isPollEnded,
isPollEditable = isPollEditable,
onEditPoll = ::onEditPoll,
onEndPoll = { showConfirmation = true },
modifier = Modifier.fillMaxWidth(),
)
}
}
}
@Composable
private fun PollAnswers(
answerItems: ImmutableList<PollAnswerItem>,
onSelectAnswer: (PollAnswer) -> Unit,
) {
Column(
modifier = Modifier.selectableGroup(),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
answerItems.forEach {
PollAnswerView(
answerItem = it,
modifier = Modifier
.selectable(
selected = it.isSelected,
enabled = it.isEnabled,
onClick = { onSelectAnswer(it.answer) },
role = Role.RadioButton,
),
)
}
}
}
@Composable
private fun ColumnScope.DisclosedPollBottomNotice(
votesCount: Int,
) {
Text(
modifier = Modifier.align(Alignment.End),
style = ElementTheme.typography.fontBodyXsRegular,
color = ElementTheme.colors.textSecondary,
text = stringResource(CommonStrings.common_poll_total_votes, votesCount),
)
}
@Composable
private 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),
)
}
@Composable
private fun CreatorView(
isPollEnded: Boolean,
isPollEditable: Boolean,
onEditPoll: () -> Unit,
onEndPoll: () -> Unit,
modifier: Modifier = Modifier
) {
when {
isPollEditable ->
Button(
text = stringResource(id = CommonStrings.action_edit_poll),
onClick = onEditPoll,
modifier = modifier,
)
!isPollEnded ->
Button(
text = stringResource(id = CommonStrings.action_end_poll),
onClick = onEndPoll,
modifier = modifier,
)
}
}
@PreviewsDayNight
@Composable
internal fun PollContentViewUndisclosedPreview() = ElementPreview {
PollContentView(
eventId = EventId("\$anEventId"),
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(showVotes = false),
pollKind = PollKind.Undisclosed,
isPollEnded = false,
isPollEditable = false,
isMine = false,
onSelectAnswer = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
}
@PreviewsDayNight
@Composable
internal fun PollContentViewDisclosedPreview() = ElementPreview {
PollContentView(
eventId = EventId("\$anEventId"),
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(),
pollKind = PollKind.Disclosed,
isPollEnded = false,
isPollEditable = false,
isMine = false,
onSelectAnswer = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
}
@PreviewsDayNight
@Composable
internal fun PollContentViewEndedPreview() = ElementPreview {
PollContentView(
eventId = EventId("\$anEventId"),
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(isEnded = true),
pollKind = PollKind.Disclosed,
isPollEnded = true,
isPollEditable = false,
isMine = false,
onSelectAnswer = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
}
@PreviewsDayNight
@Composable
internal fun PollContentViewCreatorEditablePreview() = ElementPreview {
PollContentView(
eventId = EventId("\$anEventId"),
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(hasVotes = false, isEnded = false),
pollKind = PollKind.Disclosed,
isPollEnded = false,
isPollEditable = true,
isMine = true,
onSelectAnswer = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
}
@PreviewsDayNight
@Composable
internal fun PollContentViewCreatorPreview() = ElementPreview {
PollContentView(
eventId = EventId("\$anEventId"),
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(isEnded = false),
pollKind = PollKind.Disclosed,
isPollEnded = false,
isPollEditable = false,
isMine = true,
onSelectAnswer = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
}
@PreviewsDayNight
@Composable
internal fun PollContentViewCreatorEndedPreview() = ElementPreview {
PollContentView(
eventId = EventId("\$anEventId"),
question = "What type of food should we have at the party?",
answerItems = aPollAnswerItemList(isEnded = true),
pollKind = PollKind.Disclosed,
isPollEnded = true,
isPollEditable = false,
isMine = true,
onSelectAnswer = { _, _ -> },
onEditPoll = {},
onEndPoll = {},
)
}
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.poll.api.pollcontent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun PollTitleView(
title: String,
isPollEnded: Boolean,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
if (isPollEnded) {
Icon(
imageVector = CompoundIcons.PollsEnd(),
contentDescription = stringResource(id = CommonStrings.a11y_poll_end),
modifier = Modifier.size(22.dp)
)
} else {
Icon(
imageVector = CompoundIcons.Polls(),
contentDescription = stringResource(id = CommonStrings.a11y_poll),
modifier = Modifier.size(22.dp)
)
}
Text(
text = title,
style = ElementTheme.typography.fontBodyLgMedium
)
}
}
@PreviewsDayNight
@Composable
internal fun PollTitleViewPreview() = ElementPreview {
PollTitleView(
title = "What is your favorite color?",
isPollEnded = false
)
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d procento z celkového počtu hlasů"</item>
<item quantity="few">"%1$d procenta z celkového počtu hlasů"</item>
<item quantity="other">"%1$d procent z celkového počtu hlasů"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Odstraní předchozí výběr"</string>
<string name="a11y_polls_winning_answer">"Toto je vítězná odpověď"</string>
</resources>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="zero">"%1$d y cant o\'r holl bleidleisiau"</item>
<item quantity="one">"%1$d y cant o\'r holl bleidleisiau"</item>
<item quantity="two">"%1$d y cant o\'r holl bleidleisiau"</item>
<item quantity="few">"%1$d y cant o\'r holl bleidleisiau"</item>
<item quantity="many">"%1$d y cant o\'r holl bleidleisiau"</item>
<item quantity="other">"%1$d y cant o\'r holl bleidleisiau"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Bydd yn dileu\'r dewis blaenorol"</string>
<string name="a11y_polls_winning_answer">"Dyma\'r ateb buddugol"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d% af de samlede stemmer"</item>
<item quantity="other">"%1$d procent af det samlede antal stemmer"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Fjerner tidligere valg"</string>
<string name="a11y_polls_winning_answer">"Dette er det vindende svar"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d Prozent aller Stimmen"</item>
<item quantity="other">"%1$d Prozent aller Stimmen"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Entfernt die vorherige Auswahl"</string>
<string name="a11y_polls_winning_answer">"Das ist die meistgewählte Antwort"</string>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d τοις εκατό των συνολικών ψήφων"</item>
<item quantity="other">"%1$d τοις εκατό του συνόλου των ψήφων"</item>
</plurals>
<string name="a11y_polls_winning_answer">"Αυτή είναι η νικητήρια απάντηση"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d protsent kõikidest antud häältest"</item>
<item quantity="other">"%1$d protsenti kõikidest antud häältest"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"See kustutab eelmise valiku"</string>
<string name="a11y_polls_winning_answer">"See vastus võitis"</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_polls_will_remove_selection">"Aurreko hautaketa kenduko du"</string>
<string name="a11y_polls_winning_answer">"Erantzun hau gailendu da"</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_polls_will_remove_selection">"گزینش پیشین را برخواهد داشت"</string>
<string name="a11y_polls_winning_answer">"این پاسخ برنده است"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d prosentti kaikista äänistä"</item>
<item quantity="other">"%1$d prosenttia kaikista äänistä"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Poistaa edellisen valinnan"</string>
<string name="a11y_polls_winning_answer">"Tämä on voittava vastaus"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d pour cent du total des votes"</item>
<item quantity="other">"%1$d pour cent du total des votes"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Supprimera la sélection précédente"</string>
<string name="a11y_polls_winning_answer">"Cest la réponse gagnante"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"az összes szavazat %1$d százaléka"</item>
<item quantity="other">"az összes szavazat %1$d százaléka"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Eltávolítja a korábbi kijelölést"</string>
<string name="a11y_polls_winning_answer">"Ez a győztes válasz"</string>
</resources>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="other">"%1$d persen dari total suara"</item>
</plurals>
<string name="a11y_polls_winning_answer">"Ini adalah jawaban yang menang"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d percento dei voti totali"</item>
<item quantity="other">"%1$d percento dei voti totali"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Rimuoverà la selezione precedente"</string>
<string name="a11y_polls_winning_answer">"Questa è la risposta vincente"</string>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="other">"%1$d 총 투표율"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"이전 선택 항목을 제거합니다"</string>
<string name="a11y_polls_winning_answer">"이것이 승리의 답입니다"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d prosent av totalt antall stemmer"</item>
<item quantity="other">"%1$d prosent av totalt antall stemmer"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Vil fjerne forrige valg"</string>
<string name="a11y_polls_winning_answer">"Dette er vinnersvaret"</string>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_polls_will_remove_selection">"Verwijdert de vorige selectie"</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d procent wszystkich głosów"</item>
<item quantity="few">"%1$d procenty wszystkich głosów"</item>
<item quantity="many">"%1$d procent wszystkich głosów"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Spowoduje to usunięcie poprzedniego zaznaczenia"</string>
<string name="a11y_polls_winning_answer">"Zwycięska odpowiedź"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d por cento de todos os votos"</item>
<item quantity="other">"%1$d por cento de todos os votos"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Removerá a seleção anterior"</string>
<string name="a11y_polls_winning_answer">"Esta é a resposta vencedora"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d porcento de todos os votos"</item>
<item quantity="other">"%1$d porcento de todos os votos"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Irá remover seleção anterior"</string>
<string name="a11y_polls_winning_answer">"Esta é a reposta vencedora"</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d la suta din totalul voturilor"</item>
<item quantity="few">"%1$d la suta din totalul voturilor"</item>
<item quantity="other">"%1$d la suta din totalul voturilor"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Va șterge selecția anterioară"</string>
<string name="a11y_polls_winning_answer">"Acesta este votul câștigător"</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d процент от общего числа голосов"</item>
<item quantity="few">"%1$d процента от общего числа голосов"</item>
<item quantity="many">"%1$d процентов от общего числа голосов"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Удалить предыдущий ответ"</string>
<string name="a11y_polls_winning_answer">"Это лучший ответ"</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d percento z celkového počtu hlasov"</item>
<item quantity="few">"%1$d percentá z celkového počtu hlasov"</item>
<item quantity="other">"%1$d percent z celkového počtu hlasov"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Odstráni predchádzajúci výber"</string>
<string name="a11y_polls_winning_answer">"Toto je víťazná odpoveď"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d procent av totala röster"</item>
<item quantity="other">"%1$d procent av totala röster"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Kommer att ta bort föregående val"</string>
<string name="a11y_polls_winning_answer">"Detta är det vinnande svaret"</string>
</resources>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d відсоток від усіх голосів"</item>
<item quantity="few">"%1$d відсотки від усіх голосів"</item>
<item quantity="many">"%1$d відсотків від усіх голосів"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Попередній вибір буде прибрано"</string>
<string name="a11y_polls_winning_answer">"Ця відповідь перемогла"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"Jami ovozlarning %1$d foizi"</item>
<item quantity="other">"Jami ovozlarning %1$d foizi"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Oldingi tanlov olib tashlanadi"</string>
<string name="a11y_polls_winning_answer">"Bu g\'alaba qozongan javob"</string>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="other">"總票數的百分之 %1$d"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"將會移除先前的選擇"</string>
<string name="a11y_polls_winning_answer">"這是得票數最高的選項"</string>
</resources>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="other">"%1$d 总投票百分比"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"将移除之前的选择"</string>
<string name="a11y_polls_winning_answer">"这是获胜的答案"</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="a11y_polls_percent_of_total">
<item quantity="one">"%1$d percent of total votes"</item>
<item quantity="other">"%1$d percents of total votes"</item>
</plurals>
<string name="a11y_polls_will_remove_selection">"Will remove previous selection"</string>
<string name="a11y_polls_winning_answer">"This is the winning answer"</string>
</resources>