|
16 | 16 |
|
17 | 17 | package io.getstream.chat.android.compose.ui.components.messages |
18 | 18 |
|
19 | | -import androidx.compose.animation.core.animateFloatAsState |
20 | 19 | import androidx.compose.foundation.BorderStroke |
21 | 20 | import androidx.compose.foundation.background |
22 | 21 | import androidx.compose.foundation.layout.Arrangement |
23 | 22 | import androidx.compose.foundation.layout.Box |
24 | 23 | import androidx.compose.foundation.layout.Column |
25 | 24 | import androidx.compose.foundation.layout.PaddingValues |
26 | | -import androidx.compose.foundation.layout.Row |
27 | 25 | import androidx.compose.foundation.layout.fillMaxWidth |
28 | | -import androidx.compose.foundation.layout.height |
29 | | -import androidx.compose.foundation.layout.heightIn |
30 | 26 | import androidx.compose.foundation.layout.padding |
31 | 27 | import androidx.compose.foundation.layout.size |
32 | | -import androidx.compose.foundation.selection.toggleable |
33 | | -import androidx.compose.foundation.shape.RoundedCornerShape |
34 | 28 | import androidx.compose.material3.AlertDialog |
35 | 29 | import androidx.compose.material3.ButtonDefaults |
36 | | -import androidx.compose.material3.LinearProgressIndicator |
37 | 30 | import androidx.compose.material3.Text |
38 | 31 | import androidx.compose.material3.TextButton |
39 | 32 | import androidx.compose.runtime.Composable |
40 | 33 | import androidx.compose.runtime.LaunchedEffect |
41 | 34 | import androidx.compose.runtime.MutableState |
42 | | -import androidx.compose.runtime.getValue |
43 | 35 | import androidx.compose.runtime.mutableStateOf |
44 | 36 | import androidx.compose.runtime.remember |
45 | 37 | import androidx.compose.ui.Alignment |
46 | 38 | import androidx.compose.ui.Modifier |
47 | | -import androidx.compose.ui.draw.clip |
48 | 39 | import androidx.compose.ui.focus.FocusRequester |
49 | 40 | import androidx.compose.ui.focus.focusRequester |
50 | | -import androidx.compose.ui.graphics.StrokeCap |
51 | 41 | import androidx.compose.ui.platform.LocalContext |
52 | 42 | import androidx.compose.ui.res.pluralStringResource |
53 | 43 | import androidx.compose.ui.res.stringResource |
54 | | -import androidx.compose.ui.semantics.Role |
55 | | -import androidx.compose.ui.semantics.clearAndSetSemantics |
56 | | -import androidx.compose.ui.semantics.contentDescription |
57 | | -import androidx.compose.ui.semantics.hideFromAccessibility |
58 | | -import androidx.compose.ui.semantics.semantics |
59 | | -import androidx.compose.ui.text.style.TextOverflow |
60 | 44 | import androidx.compose.ui.tooling.preview.Preview |
61 | 45 | import androidx.compose.ui.unit.dp |
62 | 46 | import io.getstream.chat.android.compose.R |
63 | | -import io.getstream.chat.android.compose.ui.components.avatar.AvatarSize |
64 | | -import io.getstream.chat.android.compose.ui.components.avatar.UserAvatarStack |
65 | 47 | import io.getstream.chat.android.compose.ui.components.button.StreamButtonSize |
66 | 48 | import io.getstream.chat.android.compose.ui.components.button.StreamButtonStyle |
67 | 49 | import io.getstream.chat.android.compose.ui.components.button.StreamButtonStyleDefaults |
68 | 50 | import io.getstream.chat.android.compose.ui.components.button.StreamTextButton |
69 | | -import io.getstream.chat.android.compose.ui.components.common.RadioCheck |
70 | 51 | import io.getstream.chat.android.compose.ui.components.composer.InputField |
71 | 52 | import io.getstream.chat.android.compose.ui.components.poll.AddAnswerDialog |
| 53 | +import io.getstream.chat.android.compose.ui.components.poll.PollOptionVotingRow |
72 | 54 | import io.getstream.chat.android.compose.ui.theme.ChatTheme |
73 | 55 | import io.getstream.chat.android.compose.ui.theme.MessageBubbleParams |
74 | 56 | import io.getstream.chat.android.compose.ui.theme.MessageFailedIconParams |
75 | 57 | import io.getstream.chat.android.compose.ui.theme.MessageStyling |
76 | 58 | import io.getstream.chat.android.compose.ui.theme.MessageStyling.PollStyle |
77 | 59 | import io.getstream.chat.android.compose.ui.theme.StreamTokens |
78 | | -import io.getstream.chat.android.compose.ui.util.applyIf |
79 | 60 | import io.getstream.chat.android.compose.ui.util.isErrorOrFailed |
80 | 61 | import io.getstream.chat.android.compose.ui.util.passiveRipple |
81 | 62 | import io.getstream.chat.android.compose.util.extensions.toSet |
82 | 63 | import io.getstream.chat.android.models.ChannelCapabilities |
83 | 64 | import io.getstream.chat.android.models.Message |
84 | 65 | import io.getstream.chat.android.models.Option |
85 | 66 | import io.getstream.chat.android.models.Poll |
86 | | -import io.getstream.chat.android.models.User |
87 | 67 | import io.getstream.chat.android.models.Vote |
88 | | -import io.getstream.chat.android.models.VotingVisibility |
89 | 68 | import io.getstream.chat.android.previewdata.PreviewMessageData |
90 | 69 | import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState |
91 | 70 | import io.getstream.chat.android.ui.common.state.messages.poll.PollSelectionType |
@@ -260,7 +239,7 @@ private fun PollMessageContent( |
260 | 239 | ) |
261 | 240 | val voteCount = poll.voteCountsByOption[option.id] ?: 0 |
262 | 241 |
|
263 | | - PollOptionItem( |
| 242 | + PollOptionVotingRow( |
264 | 243 | modifier = Modifier.padding(padding), |
265 | 244 | poll = poll, |
266 | 245 | option = option, |
@@ -418,115 +397,6 @@ private fun NewOptionDialog( |
418 | 397 | ) |
419 | 398 | } |
420 | 399 |
|
421 | | -@Suppress("LongParameterList", "LongMethod") |
422 | | -@Composable |
423 | | -private fun PollOptionItem( |
424 | | - modifier: Modifier = Modifier, |
425 | | - poll: Poll, |
426 | | - option: Option, |
427 | | - voteCount: Int, |
428 | | - totalVoteCount: Int, |
429 | | - users: List<User>, |
430 | | - checkedCount: Int, |
431 | | - checked: Boolean, |
432 | | - style: PollStyle, |
433 | | - onCastVote: () -> Unit, |
434 | | - onRemoveVote: () -> Unit, |
435 | | -) { |
436 | | - val typography = ChatTheme.typography |
437 | | - val toggleRole = if (poll.maxVotesAllowed == 1) Role.RadioButton else Role.Checkbox |
438 | | - val onToggle: (Boolean) -> Unit = { enabled -> |
439 | | - val canVote = poll.maxVotesAllowed?.let { checkedCount < it } ?: true |
440 | | - if (enabled && canVote && !checked) { |
441 | | - onCastVote.invoke() |
442 | | - } else if (!enabled) { |
443 | | - onRemoveVote.invoke() |
444 | | - } |
445 | | - } |
446 | | - |
447 | | - Row( |
448 | | - modifier = modifier |
449 | | - .fillMaxWidth() |
450 | | - .applyIf(!poll.closed) { |
451 | | - toggleable( |
452 | | - value = checked, |
453 | | - role = toggleRole, |
454 | | - onValueChange = onToggle, |
455 | | - ) |
456 | | - } |
457 | | - .semantics(mergeDescendants = true) {}, |
458 | | - horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingSm), |
459 | | - verticalAlignment = Alignment.CenterVertically, |
460 | | - ) { |
461 | | - if (!poll.closed) { |
462 | | - RadioCheck( |
463 | | - modifier = Modifier.semantics { hideFromAccessibility() }, |
464 | | - checked = checked, |
465 | | - onCheckedChange = onToggle, |
466 | | - borderColor = style.outlineColor, |
467 | | - ) |
468 | | - } |
469 | | - |
470 | | - Column(verticalArrangement = Arrangement.spacedBy(StreamTokens.spacing2xs)) { |
471 | | - Row(Modifier.heightIn(min = AvatarSize.ExtraSmall)) { |
472 | | - Text( |
473 | | - modifier = Modifier.weight(1f), |
474 | | - text = option.text, |
475 | | - style = typography.captionDefault, |
476 | | - color = style.textColor, |
477 | | - maxLines = 2, |
478 | | - overflow = TextOverflow.Ellipsis, |
479 | | - ) |
480 | | - |
481 | | - if (users.isNotEmpty() && poll.votingVisibility != VotingVisibility.ANONYMOUS) { |
482 | | - UserAvatarStack( |
483 | | - overlap = StreamTokens.spacingXs, |
484 | | - users = users.take(MaxStackedAvatars), |
485 | | - avatarSize = AvatarSize.ExtraSmall, |
486 | | - modifier = Modifier.padding(start = StreamTokens.spacingXs, end = StreamTokens.spacing2xs), |
487 | | - ) |
488 | | - } |
489 | | - |
490 | | - val voteCountDescription = pluralStringResource( |
491 | | - R.plurals.stream_compose_poll_vote_counts, |
492 | | - voteCount, |
493 | | - voteCount, |
494 | | - ) |
495 | | - Text( |
496 | | - modifier = Modifier |
497 | | - .align(Alignment.CenterVertically) |
498 | | - .semantics { contentDescription = voteCountDescription }, |
499 | | - text = voteCount.toString(), |
500 | | - style = typography.metadataDefault, |
501 | | - color = style.textColor, |
502 | | - ) |
503 | | - } |
504 | | - |
505 | | - val progress by animateFloatAsState( |
506 | | - targetValue = if (voteCount == 0 || totalVoteCount == 0) { |
507 | | - 0f |
508 | | - } else { |
509 | | - voteCount / totalVoteCount.toFloat() |
510 | | - }, |
511 | | - ) |
512 | | - |
513 | | - LinearProgressIndicator( |
514 | | - modifier = Modifier |
515 | | - .fillMaxWidth() |
516 | | - .height(8.dp) |
517 | | - .clip(RoundedCornerShape(4.dp)) |
518 | | - .clearAndSetSemantics {}, |
519 | | - progress = { progress }, |
520 | | - color = style.progressColor, |
521 | | - trackColor = style.trackColor, |
522 | | - gapSize = 0.dp, |
523 | | - strokeCap = StrokeCap.Square, |
524 | | - drawStopIndicator = { /* Don't draw the stop indicator */ }, |
525 | | - ) |
526 | | - } |
527 | | - } |
528 | | -} |
529 | | - |
530 | 400 | @Composable |
531 | 401 | private fun EndPollConfirmationDialog( |
532 | 402 | onConfirm: () -> Unit, |
@@ -568,8 +438,6 @@ private fun EndPollConfirmationDialog( |
568 | 438 | ) |
569 | 439 | } |
570 | 440 |
|
571 | | -private const val MaxStackedAvatars = 3 |
572 | | - |
573 | 441 | @Composable |
574 | 442 | private fun PollOptionButton( |
575 | 443 | text: String, |
|
0 commit comments