Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,66 +16,47 @@

package io.getstream.chat.android.compose.ui.components.messages

import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.selection.toggleable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.getstream.chat.android.compose.R
import io.getstream.chat.android.compose.ui.components.avatar.AvatarSize
import io.getstream.chat.android.compose.ui.components.avatar.UserAvatarStack
import io.getstream.chat.android.compose.ui.components.button.StreamButtonSize
import io.getstream.chat.android.compose.ui.components.button.StreamButtonStyle
import io.getstream.chat.android.compose.ui.components.button.StreamButtonStyleDefaults
import io.getstream.chat.android.compose.ui.components.button.StreamTextButton
import io.getstream.chat.android.compose.ui.components.common.RadioCheck
import io.getstream.chat.android.compose.ui.components.composer.InputField
import io.getstream.chat.android.compose.ui.components.poll.AddAnswerDialog
import io.getstream.chat.android.compose.ui.components.poll.PollOptionVotingRow
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.ui.theme.MessageBubbleParams
import io.getstream.chat.android.compose.ui.theme.MessageFailedIconParams
import io.getstream.chat.android.compose.ui.theme.MessageStyling
import io.getstream.chat.android.compose.ui.theme.MessageStyling.PollStyle
import io.getstream.chat.android.compose.ui.theme.StreamTokens
import io.getstream.chat.android.compose.ui.util.applyIf
import io.getstream.chat.android.compose.ui.util.isErrorOrFailed
import io.getstream.chat.android.compose.ui.util.passiveRipple
import io.getstream.chat.android.compose.util.extensions.toSet
Expand All @@ -85,7 +66,6 @@ import io.getstream.chat.android.models.Option
import io.getstream.chat.android.models.Poll
import io.getstream.chat.android.models.User
import io.getstream.chat.android.models.Vote
import io.getstream.chat.android.models.VotingVisibility
import io.getstream.chat.android.previewdata.PreviewMessageData
import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState
import io.getstream.chat.android.ui.common.state.messages.poll.PollSelectionType
Expand Down Expand Up @@ -418,7 +398,7 @@ private fun NewOptionDialog(
)
}

@Suppress("LongParameterList", "LongMethod")
@Suppress("LongParameterList")
@Composable
private fun PollOptionItem(
modifier: Modifier = Modifier,
Expand All @@ -433,98 +413,19 @@ private fun PollOptionItem(
onCastVote: () -> Unit,
onRemoveVote: () -> Unit,
) {
val typography = ChatTheme.typography
val toggleRole = if (poll.maxVotesAllowed == 1) Role.RadioButton else Role.Checkbox
val onToggle: (Boolean) -> Unit = { enabled ->
val canVote = poll.maxVotesAllowed?.let { checkedCount < it } ?: true
if (enabled && canVote && !checked) {
onCastVote.invoke()
} else if (!enabled) {
onRemoveVote.invoke()
}
}

Row(
modifier = modifier
.fillMaxWidth()
.applyIf(!poll.closed) {
toggleable(
value = checked,
role = toggleRole,
onValueChange = onToggle,
)
}
.semantics(mergeDescendants = true) {},
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingSm),
verticalAlignment = Alignment.CenterVertically,
) {
if (!poll.closed) {
RadioCheck(
modifier = Modifier.semantics { hideFromAccessibility() },
checked = checked,
onCheckedChange = onToggle,
borderColor = style.outlineColor,
)
}

Column(verticalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs)) {
Row(Modifier.heightIn(min = AvatarSize.ExtraSmall)) {
Text(
modifier = Modifier.weight(1f),
text = option.text,
style = typography.captionDefault,
color = style.textColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)

if (users.isNotEmpty() && poll.votingVisibility != VotingVisibility.ANONYMOUS) {
UserAvatarStack(
overlap = StreamTokens.spacingXs,
users = users.take(MaxStackedAvatars),
avatarSize = AvatarSize.ExtraSmall,
modifier = Modifier.padding(start = StreamTokens.spacingXs, end = StreamTokens.spacing2xs),
)
}

val voteCountDescription = pluralStringResource(
R.plurals.stream_compose_poll_vote_counts,
voteCount,
voteCount,
)
Text(
modifier = Modifier
.align(Alignment.CenterVertically)
.semantics { contentDescription = voteCountDescription },
text = voteCount.toString(),
style = typography.metadataDefault,
color = style.textColor,
)
}

val progress by animateFloatAsState(
targetValue = if (voteCount == 0 || totalVoteCount == 0) {
0f
} else {
voteCount / totalVoteCount.toFloat()
},
)

LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.height(8.dp)
.clip(RoundedCornerShape(4.dp))
.clearAndSetSemantics {},
progress = { progress },
color = style.progressColor,
trackColor = style.trackColor,
gapSize = 0.dp,
strokeCap = StrokeCap.Square,
drawStopIndicator = { /* Don't draw the stop indicator */ },
)
}
}
PollOptionVotingRow(
modifier = modifier,
poll = poll,
option = option,
voteCount = voteCount,
totalVoteCount = totalVoteCount,
users = users,
checkedCount = checkedCount,
checked = checked,
style = style,
onCastVote = onCastVote,
onRemoveVote = onRemoveVote,
)
}

@Composable
Expand Down Expand Up @@ -568,8 +469,6 @@ private fun EndPollConfirmationDialog(
)
}

private const val MaxStackedAvatars = 3

@Composable
private fun PollOptionButton(
text: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
Expand Down Expand Up @@ -206,40 +207,45 @@ internal fun PollAnswersItem(
contentPadding = PaddingValues(StreamTokens.spacingMd),
verticalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
) {
Text(
text = answer.text,
color = colors.textPrimary,
style = typography.bodyDefault,
)

Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
Column(
modifier = Modifier.semantics(mergeDescendants = true) {},
verticalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
) {
val user = answer.user?.takeIf { showAvatar }
if (user != null) {
ChatTheme.componentFactory.UserAvatar(
params = UserAvatarParams(
modifier = Modifier.size(AvatarSize.ExtraSmall),
user = user,
showIndicator = false,
showBorder = false,
),
)
Text(
text = answer.text,
color = colors.textPrimary,
style = typography.bodyDefault,
)

Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
) {
val user = answer.user?.takeIf { showAvatar }
if (user != null) {
ChatTheme.componentFactory.UserAvatar(
params = UserAvatarParams(
modifier = Modifier.size(AvatarSize.ExtraSmall),
user = user,
showIndicator = false,
showBorder = false,
),
)

Text(
modifier = Modifier.weight(1f),
text = user.name,
color = colors.chatTextUsername,
style = typography.captionDefault,
)
}

Text(
modifier = Modifier.weight(1f),
text = user.name,
color = colors.chatTextUsername,
text = ChatTheme.dateFormatter.formatDate(answer.createdAt),
color = colors.textTertiary,
style = typography.captionDefault,
)
}

Text(
text = ChatTheme.dateFormatter.formatDate(answer.createdAt),
color = colors.textTertiary,
style = typography.captionDefault,
)
}

if (showUpdateButton) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import io.getstream.chat.android.compose.R
Expand All @@ -40,6 +44,9 @@ public fun PollDialogHeader(
onBackPressed: () -> Unit,
trailingContent: @Composable () -> Unit = {},
) {
val view = LocalView.current
LaunchedEffect(title) { view.announceForAccessibility(title) }

Row(
modifier = Modifier
.fillMaxWidth()
Expand All @@ -52,7 +59,9 @@ public fun PollDialogHeader(
)

Text(
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.semantics { heading() },
text = title,
style = ChatTheme.typography.headingMedium,
maxLines = 1,
Expand Down
Loading
Loading