Skip to content

Commit

Permalink
Implement PollMessageContent (3) (#5311)
Browse files Browse the repository at this point in the history
* Implement AttachmentsPickerPollTabFactory and add it into the AttachmentsPickerTabFactory

* Implement animating content systems for the full screen attachment

* Implement PollOptionHeader and relevant actions

* Implement PollOptionInput and PollOptionQuestions composable components

* Improve full screen attachment height size animation and fix flickering bugs

* Implement maxLength parameter on PollOptionInput

* Rename poll components name

* Implement reoderable PollQuestionList and nested scroll

* Implement poll question list item following the design guidelines

* Improve reordering performance for PollQuestionList

* Refactor PollQuestionList to use PollOptionItem

* Refactor PollQuestionList

* implement eror handling for option item list creation

* Refactor to hold poll option item list

* Check if the title is not blank

* Implemetn PollCreationDiscardDialog Composable

* Implement the skeleton of the PollSwitchList and relevant data classes

* Complete PollSwitchList composable

* Implement poll switch inputs

* Add default options

* Update detekt baseline

* Fix docs module to work

* Import paparazzi plugin and implement BaseComposeTest

* Implement PollUITest and generate snapshot images

* Update detekt

* Implement AttachmentsPickerPollTabFactory and make poll tab items customizable

* Add missing docs for poll data classes

* Implement AttachmentPickerAction interface

* Update detektBaseline

* Update attachment docs

* Implement poll creation business logic for Compose UI

* Add default lambda to the onAttachmentPickerAction composable

* Update detektBaseline

* Implement PollMessageContent composable

* Implement PreviewPollData and the skeleton of the PollMessageContent

* Implement casting a vote using a checkbox on the option

* Implement PollOptionItem following the design guidelines

* Check if the option is voted by mine

* Update poll item uis based on the given models

* Fix docs that are not related to v6 for attachment lambdas

* Implement closing a poll

* Fix ktlint and code formats

* Implement removing a vote

* Implement PollState and the skeleton of the PollMoreOptionDialog composable

* Sepearate into the PollMoreOptionsDialog composable

* Implement PollMoreOptionsTitle

* Implement PollMoreOptionsDialog

* Implement updating poll information

* Fix poll relevant events

* Implement UserAvatarRow

* Implement PollSelectionType

* Implement the sekeleton of PollViewResultDialog

* Update detekt rules

* Implement PollViewResultItem

* Implement UserAvatar on the poll vote items

* Remove unused listViewModel parameter on PollViewResultDialog

* Implement poll created and closed message preview

* Use AnimatedVisibility for preventing duplicated animation updates

* Update detekt rules

* Improve UserAvatarRow

* Run spotless

* Add initial doc files for the poll components

* Add poll creation docs and relevant resources

* Fix typo

* Fix typo

* Add docs for PollMessageContent

* Implement Poll previews & screenshot unit tests (4) (#5330)

* Implement previews for poll dialogs

* Add ui tests for Poll dialogs

* Generate screenshots from the ui test

---------

Co-authored-by: Kanat Kiialbaev <[email protected]>
  • Loading branch information
skydoves and kanat authored Aug 2, 2024
1 parent 51b43d9 commit e8da84a
Show file tree
Hide file tree
Showing 43 changed files with 2,633 additions and 22 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docusaurus/docs/Android/assets/polls_header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docusaurus/docs/Android/assets/polls_switches.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
426 changes: 426 additions & 0 deletions docusaurus/docs/Android/compose/polls.mdx

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions stream-chat-android-client/api/stream-chat-android-client.api
Original file line number Diff line number Diff line change
Expand Up @@ -2860,6 +2860,8 @@ public final class io/getstream/chat/android/client/utils/message/MessageUtils {
public static final fun isGiphyEphemeral (Lio/getstream/chat/android/models/Message;)Z
public static final fun isModerationError (Lio/getstream/chat/android/models/Message;Ljava/lang/String;)Z
public static final fun isPinnedAndNotDeleted (Lio/getstream/chat/android/models/Message;)Z
public static final fun isPoll (Lio/getstream/chat/android/models/Message;)Z
public static final fun isPollClosed (Lio/getstream/chat/android/models/Message;)Z
public static final fun isRegular (Lio/getstream/chat/android/models/Message;)Z
public static final fun isReply (Lio/getstream/chat/android/models/Message;)Z
public static final fun isSystem (Lio/getstream/chat/android/models/Message;)Z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ public fun Message.isError(): Boolean = type == MessageType.ERROR
*/
public fun Message.isGiphy(): Boolean = command == AttachmentType.GIPHY

/**
* @return If the message is related to the poll.
*/
public fun Message.isPoll(): Boolean = poll != null

/**
* @return If the message is related to the poll.
*/
public fun Message.isPollClosed(): Boolean = poll?.closed == true

/**
* @return If the message is a temporary message to select a gif.
*/
Expand Down
99 changes: 94 additions & 5 deletions stream-chat-android-compose/api/stream-chat-android-compose.api

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion stream-chat-android-compose/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComplexCondition:MessageItem.kt$!messageItem.isMine &amp;&amp; ( messageItem.showMessageFooter || messageItem.groupPosition.contains(MessagePosition.BOTTOM) || messageItem.groupPosition.contains(MessagePosition.NONE) )</ID>
<ID>ComplexCondition:MessageItem.kt$!messageItem.isMine &amp;&amp; ( messageItem.showMessageFooter || messageItem.groupPosition.contains(MessagePosition.BOTTOM) || messageItem.groupPosition.contains( MessagePosition.NONE, ) )</ID>
<ID>ComplexCondition:MessageTranslatedLabel.kt$!isGiphy &amp;&amp; !isDeleted &amp;&amp; userLanguage != i18nLanguage &amp;&amp; translatedText != messageItem.message.text</ID>
<ID>ComplexCondition:Messages.kt$!endOfMessages &amp;&amp; index == messages.lastIndex &amp;&amp; messages.isNotEmpty() &amp;&amp; lazyListState.isScrollInProgress</ID>
<ID>ForbiddenComment:MessageText.kt$// TODO: Fix emoji font padding once this is resolved and exposed: https://issuetracker.google.com/issues/171394808</ID>
Expand All @@ -24,6 +24,10 @@
<ID>LongMethod:MessageOptions.kt$@Composable public fun defaultMessageOptionsState( selectedMessage: Message, currentUser: User?, isInThread: Boolean, ownCapabilities: Set&lt;String>, ): List&lt;MessageOptionItemState></ID>
<ID>LongMethod:MessagesScreen.kt$@OptIn(ExperimentalAnimationApi::class) @Composable private fun BoxScope.AttachmentsPickerMenu( listViewModel: MessageListViewModel, attachmentsPickerViewModel: AttachmentsPickerViewModel, composerViewModel: MessageComposerViewModel, )</ID>
<ID>LongMethod:PollCreationDiscardDialog.kt$@Composable public fun PollCreationDiscardDialog( usePlatformDefaultWidth: Boolean = false, onCancelClicked: () -> Unit, onDiscardClicked: () -> Unit, )</ID>
<ID>LongMethod:PollMessageContent.kt$@Composable private fun PollMessageContent( message: Message, poll: Poll, isMine: Boolean, onClosePoll: (String) -> Unit, onCastVote: (Option) -> Unit, onRemoveVote: (Vote) -> Unit, selectPoll: (Message, Poll, PollSelectionType) -> Unit, )</ID>
<ID>LongMethod:PollMessageContent.kt$@Composable private fun PollOptionItem( modifier: Modifier = Modifier, poll: Poll, option: Option, voteCount: Int, totalVoteCount: Int, users: List&lt;User>, checkedCount: Int, checked: Boolean, onCastVote: () -> Unit, onRemoveVote: () -> Unit, )</ID>
<ID>LongMethod:PollMessageContent.kt$@Composable public fun PollMessageContent( modifier: Modifier, messageItem: MessageItemState, onCastVote: (Message, Poll, Option) -> Unit, onRemoveVote: (Message, Poll, Vote) -> Unit, selectPoll: (Message, Poll, PollSelectionType) -> Unit, onClosePoll: (String) -> Unit, onLongItemClick: (Message) -> Unit = {}, )</ID>
<ID>LongMethod:PollMoreOptionsDialog.kt$@Composable public fun PollMoreOptionsDialog( selectedPoll: SelectedPoll?, listViewModel: MessageListViewModel, onDismissRequest: () -> Unit, onBackPressed: () -> Unit, )</ID>
<ID>LongMethod:PollOptionList.kt$@Composable public fun PollOptionList( modifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), title: String = stringResource(id = R.string.stream_compose_poll_option_title), optionItems: List&lt;PollOptionItem> = emptyList(), onQuestionsChanged: (List&lt;PollOptionItem>) -> Unit, itemHeightSize: Dp = ChatTheme.dimens.pollOptionInputHeight, itemInnerPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 4.dp), )</ID>
<ID>LongMethod:PollSwitchList.kt$@Composable public fun PollSwitchList( modifier: Modifier = Modifier, pollSwitchItems: List&lt;PollSwitchItem>, onSwitchesChanged: (List&lt;PollSwitchItem>) -> Unit, itemHeightSize: Dp = ChatTheme.dimens.pollOptionInputHeight, itemInnerPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 16.dp), )</ID>
<ID>LongMethod:StreamTypography.kt$StreamTypography.Companion$public fun defaultTypography(fontFamily: FontFamily? = null): StreamTypography</ID>
Expand All @@ -32,7 +36,12 @@
<ID>LongParameterList:MediaGalleryPreviewActivity.kt$MediaGalleryPreviewActivity$( context: Context, mediaGalleryPreviewAction: MediaGalleryPreviewAction, currentPage: Int, attachments: List&lt;Attachment>, writePermissionState: PermissionState, downloadPayload: MutableState&lt;Attachment?>, )</ID>
<ID>LongParameterList:MediaGalleryPreviewActivityAttachmentState.kt$MediaGalleryPreviewActivityAttachmentState$( val name: String?, val thumbUrl: String?, val imageUrl: String?, val assetUrl: String?, val originalWidth: Int?, val originalHeight: Int?, val type: String?, )</ID>
<ID>LongParameterList:MessageComposer.kt$( value: String, coolDownTime: Int, attachments: List&lt;Attachment>, validationErrors: List&lt;ValidationError>, ownCapabilities: Set&lt;String>, isInEditMode: Boolean, onSendMessage: (String, List&lt;Attachment>) -> Unit, onRecordingSaved: (Attachment) -> Unit, statefulStreamMediaRecorder: StatefulStreamMediaRecorder?, )</ID>
<ID>LongParameterList:MessageItem.kt$( messageItem: MessageItemState, onLongItemClick: (Message) -> Unit = {}, onGiphyActionClick: (GiphyAction) -> Unit = {}, onQuotedMessageClick: (Message) -> Unit = {}, onMediaGalleryPreviewResult: (MediaGalleryPreviewResult?) -> Unit = {}, onPollUpdated: (Message, Poll) -> Unit, onCastVote: (Message, Poll, Option) -> Unit, onRemoveVote: (Message, Poll, Vote) -> Unit, selectPoll: (Message, Poll, PollSelectionType) -> Unit, onClosePoll: (String) -> Unit, )</ID>
<ID>LongParameterList:MessagesScreen.kt$( listViewModel: MessageListViewModel, composerViewModel: MessageComposerViewModel, selectedMessageState: SelectedMessageState?, selectedMessage: Message, skipPushNotification: Boolean, skipEnrichUrl: Boolean, )</ID>
<ID>LongParameterList:PollMessageContent.kt$( message: Message, poll: Poll, isMine: Boolean, onClosePoll: (String) -> Unit, onCastVote: (Option) -> Unit, onRemoveVote: (Vote) -> Unit, selectPoll: (Message, Poll, PollSelectionType) -> Unit, )</ID>
<ID>LongParameterList:PollMessageContent.kt$( modifier: Modifier = Modifier, poll: Poll, option: Option, voteCount: Int, totalVoteCount: Int, users: List&lt;User>, checkedCount: Int, checked: Boolean, onCastVote: () -> Unit, onRemoveVote: () -> Unit, )</ID>
<ID>LongParameterList:PollMessageContent.kt$( modifier: Modifier, messageItem: MessageItemState, onCastVote: (Message, Poll, Option) -> Unit, onRemoveVote: (Message, Poll, Vote) -> Unit, selectPoll: (Message, Poll, PollSelectionType) -> Unit, onClosePoll: (String) -> Unit, onLongItemClick: (Message) -> Unit = {}, )</ID>
<ID>LongParameterList:PollMoreOptionsDialog.kt$( index: Int, poll: Poll, option: Option, voteCount: Int, checkedCount: Int, checked: Boolean, onCastVote: () -> Unit, onRemoveVote: () -> Unit, )</ID>
<ID>MagicNumber:AudioRecording.kt$2.5f</ID>
<ID>MagicNumber:AudioWaveSeekbar.kt$0.4</ID>
<ID>MagicNumber:AudioWaveSeekbar.kt$100F</ID>
Expand All @@ -51,16 +60,25 @@
<ID>MagicNumber:MessageComposer.kt$60_000</ID>
<ID>MagicNumber:Messages.kt$3</ID>
<ID>MagicNumber:Messages.kt$5</ID>
<ID>MagicNumber:PollMessageContent.kt$0.5f</ID>
<ID>MagicNumber:PollMessageContent.kt$10</ID>
<ID>MagicNumber:PollMoreOptionsDialog.kt$200</ID>
<ID>MagicNumber:PollMoreOptionsDialog.kt$400</ID>
<ID>MagicNumber:PollOptionList.kt$5</ID>
<ID>MagicNumber:PollOptionList.kt$8</ID>
<ID>MagicNumber:PollSwitchList.kt$8</ID>
<ID>MagicNumber:PollViewResultDialog.kt$200</ID>
<ID>MagicNumber:PollViewResultDialog.kt$400</ID>
<ID>MagicNumber:PreviewPollData.kt$PreviewPollData$3</ID>
<ID>MagicNumber:SearchInput.kt$8f</ID>
<ID>MagicNumber:TypingIndicatorAnimatedDot.kt$0.5f</ID>
<ID>MaxLineLength:AttachmentsPickerPollTabFactory.kt$AttachmentsPickerPollTabFactory$var switchItemList: List&lt;PollSwitchItem> by remember { mutableStateOf(pollSwitchItemFactory.providePollSwitchItemList()) }</ID>
<ID>MaxLineLength:AttachmentsPickerTabFactories.kt$AttachmentsPickerTabFactories$*</ID>
<ID>MaxLineLength:ChatTheme.kt$error("No attachments picker tab factories provided! Make sure to wrap all usages of Stream components in a ChatTheme.")</ID>
<ID>MaxLineLength:MediaAttachmentContent.kt$mediaGalleryPreviewLauncher: ManagedActivityResultLauncher&lt;MediaGalleryPreviewContract.Input, MediaGalleryPreviewResult?></ID>
<ID>MaxLineLength:MediaAttachmentFactory.kt$mediaGalleryPreviewLauncher: ManagedActivityResultLauncher&lt;MediaGalleryPreviewContract.Input, MediaGalleryPreviewResult?></ID>
<ID>MaxLineLength:MessageItem.kt$message.isDeleted() &amp;&amp; messageItem.deletedMessageVisibility == DeletedMessageVisibility.VISIBLE_FOR_CURRENT_USER</ID>
<ID>MaxLineLength:MessageItem.kt$messageItem.showMessageFooter || messageItem.groupPosition.contains(MessagePosition.BOTTOM)</ID>
<ID>MaxLineLength:MessageOptions.kt$iconPainter = painterResource(id = if (selectedMessage.pinned) R.drawable.stream_compose_ic_unpin_message else R.drawable.stream_compose_ic_pin_message)</ID>
<ID>MaxLineLength:MessageOptions.kt$title = if (selectedMessage.pinned) R.string.stream_compose_unpin_message else R.string.stream_compose_pin_message</ID>
<ID>MaxLineLength:MessagesScreen.kt$*</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,18 @@ internal object PreviewMessageData {
type = MessageType.REGULAR,
ownReactions = mutableListOf(Reaction(messageId = "message-id-3", type = "haha")),
)

val messageWithError: Message = Message(
id = "message-id-4",
text = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
createdAt = Date(),
type = MessageType.ERROR,
)

val messageWithPoll: Message = Message(
id = "message-id-5",
createdAt = Date(),
type = MessageType.REGULAR,
poll = PreviewPollData.poll1,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.chat.android.compose.previewdata

import io.getstream.chat.android.models.Option
import io.getstream.chat.android.models.Poll
import io.getstream.chat.android.models.Vote
import io.getstream.chat.android.models.VotingVisibility
import java.util.Date
import java.util.UUID

/**
* Provides sample poll that will be used to render previews.
*/
internal object PreviewPollData {

private val option1 = Option(
id = UUID.randomUUID().toString(),
text = "option1",
)

private val option2 = Option(
id = UUID.randomUUID().toString(),
text = "option2",
)

private val option3 = Option(
id = UUID.randomUUID().toString(),
text = "option3",
)

val poll1 = Poll(
id = UUID.randomUUID().toString(),
name = "Vote an option!",
description = "This is a poll",
options = listOf(option1, option2, option3),
votingVisibility = VotingVisibility.PUBLIC,
enforceUniqueVote = true,
maxVotesAllowed = 1,
allowUserSuggestedOptions = false,
allowAnswers = true,
voteCountsByOption = mapOf(
option1.id to 3,
option2.id to 1,
option3.id to 1,
),
votes = listOf(
Vote(
id = UUID.randomUUID().toString(),
pollId = UUID.randomUUID().toString(),
optionId = option1.id,
createdAt = Date(),
updatedAt = Date(),
user = PreviewUserData.user1,
),
Vote(
id = UUID.randomUUID().toString(),
pollId = UUID.randomUUID().toString(),
optionId = option1.id,
createdAt = Date(),
updatedAt = Date(),
user = PreviewUserData.user2,
),
Vote(
id = UUID.randomUUID().toString(),
pollId = UUID.randomUUID().toString(),
optionId = option1.id,
createdAt = Date(),
updatedAt = Date(),
user = PreviewUserData.user3,
),
Vote(
id = UUID.randomUUID().toString(),
pollId = UUID.randomUUID().toString(),
optionId = option2.id,
createdAt = Date(),
updatedAt = Date(),
user = PreviewUserData.user4,
),
Vote(
id = UUID.randomUUID().toString(),
pollId = UUID.randomUUID().toString(),
optionId = option3.id,
createdAt = Date(),
updatedAt = Date(),
user = PreviewUserData.user5,
),
),
ownVotes = listOf(),
createdAt = Date(),
updatedAt = Date(),
closed = false,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
Expand Down Expand Up @@ -58,6 +59,7 @@ public fun UserAvatar(
textStyle: TextStyle = ChatTheme.typography.title3Bold,
contentDescription: String? = null,
showOnlineIndicator: Boolean = true,
placeholderPainter: Painter? = null,
onlineIndicatorAlignment: OnlineIndicatorAlignment = OnlineIndicatorAlignment.TopEnd,
initialsAvatarOffset: DpOffset = DpOffset(0.dp, 0.dp),
onlineIndicator: @Composable BoxScope.() -> Unit = {
Expand All @@ -74,6 +76,7 @@ public fun UserAvatar(
shape = shape,
contentDescription = contentDescription,
onClick = onClick,
placeholderPainter = placeholderPainter,
initialsAvatarOffset = initialsAvatarOffset,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (c) 2014-2024 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.offset
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.Shape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import io.getstream.chat.android.compose.previewdata.PreviewUserData
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.models.User

/**
* Represents the [User] avatar that's shown on the Messages screen or in headers of DMs.
*
* Based on the state within the [User], we either show an image or their initials.
*
* @param users The list of users whose avatar we want to show.
* @param modifier Modifier for styling.
* @param maxAvatarCount The maximum count for displaying the avatar row.
* @param size The size of each user avatar.
* @param offset The offset of the
* @param shape The shape of the avatar.
* @param textStyle The [TextStyle] that will be used for the initials.
* @param contentDescription The content description of the avatar.
* @param initialsAvatarOffset The initials offset to apply to the avatar.
* @param onClick The handler when the user clicks on the avatar.
*/
@Composable
public fun UserAvatarRow(
users: List<User>,
modifier: Modifier = Modifier,
maxAvatarCount: Int = 3,
size: Dp = 20.dp,
offset: Int = 7,
shape: Shape = ChatTheme.shapes.avatar,
textStyle: TextStyle = ChatTheme.typography.title3Bold,
contentDescription: String? = null,
initialsAvatarOffset: DpOffset = DpOffset(0.dp, 0.dp),
onClick: (() -> Unit)? = null,
) {
if (users.isEmpty()) return

Row(
modifier = modifier.width(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy((-offset).dp),
) {
users.take(maxAvatarCount).forEachIndexed { index, user ->
UserAvatar(
modifier = Modifier
.size(size)
.zIndex((users.size - index).toFloat()),
user = user,
shape = shape,
textStyle = textStyle,
showOnlineIndicator = false,
contentDescription = contentDescription,
initialsAvatarOffset = initialsAvatarOffset,
onClick = onClick,
)
}
}
}

@Preview
@Composable
private fun UserAvatarRowPreview() {
ChatTheme {
Column(
horizontalAlignment = Alignment.End,
) {
UserAvatarRow(
users = listOf(
PreviewUserData.user1,
),
)
UserAvatarRow(
users = listOf(
PreviewUserData.user1,
PreviewUserData.user2,
),
)
UserAvatarRow(
users = listOf(
PreviewUserData.user1,
PreviewUserData.user2,
PreviewUserData.user3,
),
)
}
}
}
Loading

0 comments on commit e8da84a

Please sign in to comment.