From 7a586a1ba49b1a8bb05943e8654cd86e1e522c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jc=20Mi=C3=B1arro?= Date: Wed, 1 May 2024 00:10:49 +0200 Subject: [PATCH] Add ReactionSorting (#5248) * Create new `ReactionGroup` class * Add ReactionGroup to Message entity * Create DB Entities * Create DTOs for ReactionGroup * Update CHANGELOG.md * Sort reactions using ReactionGroups by FirstReactionAt * Create a mother method to generate random ReactionGroup * Create `ReactionSorting` interface and default implementations * Use an injected `ReactionSorting` to sort reactions * Create styles to inject a `ReactionSorting` to be used to sort reactions * Update CHANGELOG.md * fix api dump --------- Co-authored-by: kanat --- CHANGELOG.md | 3 + .../compose/sample/ui/MessagesActivity.kt | 2 + .../api/stream-chat-android-compose.api | 10 +- .../compose/ui/messages/MessagesScreen.kt | 5 + .../ui/messages/list/MessageContainer.kt | 7 ++ .../compose/ui/messages/list/MessageItem.kt | 16 ++- .../compose/ui/messages/list/MessageList.kt | 13 +++ .../api/stream-chat-android-core.api | 27 +++++ .../chat/android/models/ReactionSorting.kt | 63 +++++++++++ .../android/models/ReactionSortingTest.kt | 92 ++++++++++++++++ .../io/getstream/chat/android/Mother.kt | 11 ++ .../home/ComponentBrowserHomeFragment.kt | 32 +++++- .../ComponentBrowserViewReactionsFragment.kt | 100 ++++++++++++++---- .../api/stream-chat-android-ui-components.api | 9 +- .../messages/list/MessageListItemStyle.kt | 3 + .../internal/MessageListItemDiffCallback.kt | 4 +- .../reactions/view/ViewReactionsViewStyle.kt | 33 ++++++ .../view/internal/ViewReactionsView.kt | 10 +- .../android/ui/utils/extensions/Message.kt | 8 +- .../res/values/attrs_message_list_view.xml | 7 ++ .../res/values/attrs_view_reactions_view.xml | 7 ++ .../customattachments/MessagesActivity.kt | 2 + 22 files changed, 420 insertions(+), 44 deletions(-) create mode 100644 stream-chat-android-core/src/main/java/io/getstream/chat/android/models/ReactionSorting.kt create mode 100644 stream-chat-android-core/src/test/java/io/getstream/chat/android/models/ReactionSortingTest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 412216d87ce..33c107e0a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Added `reason` and `custom` fields to flag message endpoint.[#5242](https://github.com/GetStream/stream-chat-android/pull/5242) - Added `reason` and `custom` fields to flag user endpoint.[#5242](https://github.com/GetStream/stream-chat-android/pull/5242) - Added `reactionGroups` field to `Message` entity. [#5247](https://github.com/GetStream/stream-chat-android/pull/5247) +- Added `ReactionSorting` interface to allow custom sorting of reactions. [#5248](https://github.com/GetStream/stream-chat-android/pull/5248) ### ⚠️ Changed @@ -67,6 +68,7 @@ ### ✅ Added - Added a Button to jump to the first unread message in the channel. [#5236](https://github.com/GetStream/stream-chat-android/pull/5236) +- Added `ViewReactionsViewStyle.reactionSorting` field to customize the sorting of reactions. [#5248](https://github.com/GetStream/stream-chat-android/pull/5248) ### ⚠️ Changed @@ -78,6 +80,7 @@ ### ⬆️ Improved - Channel List is not updated with new messages within a thread. [#5245](https://github.com/GetStream/stream-chat-android/pull/5245) +- MessageList accept `reactionSorting` parameter to customize the sorting of reactions. [#5248](https://github.com/GetStream/stream-chat-android/pull/5248) ### ✅ Added diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt index f09393f9b08..1d6a251c3ae 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt @@ -70,6 +70,7 @@ import io.getstream.chat.android.compose.viewmodel.messages.AttachmentsPickerVie import io.getstream.chat.android.compose.viewmodel.messages.MessageComposerViewModel import io.getstream.chat.android.compose.viewmodel.messages.MessageListViewModel import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory +import io.getstream.chat.android.models.ReactionSortingByFirstReactionAt import io.getstream.chat.android.ui.common.state.messages.MessageMode import io.getstream.chat.android.ui.common.state.messages.Reply import io.getstream.chat.android.ui.common.state.messages.list.DeletedMessageVisibility @@ -172,6 +173,7 @@ class MessagesActivity : BaseConnectedActivity() { .background(ChatTheme.colors.appBackground) .fillMaxSize(), viewModel = listViewModel, + reactionSorting = ReactionSortingByFirstReactionAt, messagesLazyListState = if (listViewModel.isInThread) rememberMessageListState() else lazyListState, onThreadClick = { message -> composerViewModel.setMessageMode(MessageMode.MessageThread(message)) diff --git a/stream-chat-android-compose/api/stream-chat-android-compose.api b/stream-chat-android-compose/api/stream-chat-android-compose.api index dab051e0ec0..8eefccab77b 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -1256,7 +1256,7 @@ public final class io/getstream/chat/android/compose/ui/composables/AudioWaveSee } public final class io/getstream/chat/android/compose/ui/messages/MessagesScreenKt { - public static final fun MessagesScreen (Lio/getstream/chat/android/compose/viewmodel/messages/MessagesViewModelFactory;ZLkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ZZLio/getstream/chat/android/compose/ui/messages/list/ThreadMessagesStart;Lio/getstream/chat/android/compose/state/messages/attachments/StatefulStreamMediaRecorder;Landroidx/compose/runtime/Composer;II)V + public static final fun MessagesScreen (Lio/getstream/chat/android/compose/viewmodel/messages/MessagesViewModelFactory;ZLio/getstream/chat/android/models/ReactionSorting;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;ZZLio/getstream/chat/android/compose/ui/messages/list/ThreadMessagesStart;Lio/getstream/chat/android/compose/state/messages/attachments/StatefulStreamMediaRecorder;Landroidx/compose/runtime/Composer;III)V } public final class io/getstream/chat/android/compose/ui/messages/attachments/AttachmentsPickerKt { @@ -1409,17 +1409,17 @@ public final class io/getstream/chat/android/compose/ui/messages/list/Composable } public final class io/getstream/chat/android/compose/ui/messages/list/MessageContainerKt { - public static final fun MessageContainer (Lio/getstream/chat/android/ui/common/state/messages/list/MessageListItemState;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V + public static final fun MessageContainer (Lio/getstream/chat/android/ui/common/state/messages/list/MessageListItemState;Lio/getstream/chat/android/models/ReactionSorting;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;III)V } public final class io/getstream/chat/android/compose/ui/messages/list/MessageItemKt { public static final field HighlightFadeOutDurationMillis I - public static final fun MessageItem (Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;III)V + public static final fun MessageItem (Lio/getstream/chat/android/ui/common/state/messages/list/MessageItemState;Lio/getstream/chat/android/models/ReactionSorting;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;III)V } public final class io/getstream/chat/android/compose/ui/messages/list/MessageListKt { - public static final fun MessageList (Lio/getstream/chat/android/compose/viewmodel/messages/MessageListViewModel;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/PaddingValues;Lio/getstream/chat/android/compose/ui/messages/list/MessagesLazyListState;Lio/getstream/chat/android/compose/ui/messages/list/ThreadMessagesStart;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;IIII)V - public static final fun MessageList (Lio/getstream/chat/android/ui/common/state/messages/list/MessageListState;Lio/getstream/chat/android/compose/ui/messages/list/ThreadMessagesStart;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/PaddingValues;Lio/getstream/chat/android/compose/ui/messages/list/MessagesLazyListState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;IIII)V + public static final fun MessageList (Lio/getstream/chat/android/compose/viewmodel/messages/MessageListViewModel;Lio/getstream/chat/android/models/ReactionSorting;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/PaddingValues;Lio/getstream/chat/android/compose/ui/messages/list/MessagesLazyListState;Lio/getstream/chat/android/compose/ui/messages/list/ThreadMessagesStart;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;IIII)V + public static final fun MessageList (Lio/getstream/chat/android/ui/common/state/messages/list/MessageListState;Lio/getstream/chat/android/compose/ui/messages/list/ThreadMessagesStart;Lio/getstream/chat/android/models/ReactionSorting;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/layout/PaddingValues;Lio/getstream/chat/android/compose/ui/messages/list/MessagesLazyListState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;IIII)V } public final class io/getstream/chat/android/compose/ui/messages/list/MessagesKt { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt index 807744b582c..d68cdac0f5d 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/MessagesScreen.kt @@ -75,6 +75,8 @@ import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFac import io.getstream.chat.android.models.Channel import io.getstream.chat.android.models.LinkPreview import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.ReactionSorting +import io.getstream.chat.android.models.ReactionSortingByFirstReactionAt import io.getstream.chat.android.ui.common.state.messages.Delete import io.getstream.chat.android.ui.common.state.messages.Edit import io.getstream.chat.android.ui.common.state.messages.Flag @@ -102,6 +104,7 @@ import io.getstream.chat.android.ui.common.state.messages.updateMessage * You can customize the behavior of the list through its parameters. For default behavior, * simply create an instance and pass in just the channel ID and the context. * @param showHeader If we're showing the header or not. + * @param reactionSorting The sorting type for reactions. Default is [ReactionSortingByFirstReactionAt]. * @param onBackPressed Handler for when the user taps on the Back button and/or the system * back button. * @param onHeaderTitleClick Handler for when the user taps on the header section. @@ -117,6 +120,7 @@ import io.getstream.chat.android.ui.common.state.messages.updateMessage public fun MessagesScreen( viewModelFactory: MessagesViewModelFactory, showHeader: Boolean = true, + reactionSorting: ReactionSorting = ReactionSortingByFirstReactionAt, onBackPressed: () -> Unit = {}, onHeaderTitleClick: (channel: Channel) -> Unit = {}, onChannelAvatarClick: () -> Unit = {}, @@ -232,6 +236,7 @@ public fun MessagesScreen( .background(ChatTheme.colors.appBackground) .padding(it), viewModel = listViewModel, + reactionSorting = reactionSorting, messagesLazyListState = rememberMessageListState(parentMessageId = currentState.parentMessageId), threadMessagesStart = threadMessagesStart, onThreadClick = remember(composerViewModel, listViewModel) { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt index 9be462be2a8..5f7a9b1f467 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageContainer.kt @@ -36,6 +36,7 @@ import io.getstream.chat.android.compose.state.mediagallerypreview.MediaGalleryP import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.viewmodel.messages.MessagesViewModelFactory import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.ReactionSorting import io.getstream.chat.android.ui.common.feature.messages.list.MessageListController import io.getstream.chat.android.ui.common.state.messages.list.DateSeparatorItemState import io.getstream.chat.android.ui.common.state.messages.list.EmptyThreadPlaceholderItemState @@ -52,6 +53,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.UnreadSeparatorIt * Represents the message item container that allows us to customize each type of item in the MessageList. * * @param messageListItemState The state of the message list item. + * @param reactionSorting The sorting of reactions for the message. * @param onLongItemClick Handler when the user long taps on an item. * @param onReactionsClick Handler when the user taps on message reactions. * @param onThreadClick Handler when the user taps on a thread within a message item. @@ -70,6 +72,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.UnreadSeparatorIt @Composable public fun MessageContainer( messageListItemState: MessageListItemState, + reactionSorting: ReactionSorting, onLongItemClick: (Message) -> Unit = {}, onReactionsClick: (Message) -> Unit = {}, onThreadClick: (Message) -> Unit = {}, @@ -91,6 +94,7 @@ public fun MessageContainer( messageItemContent: @Composable (MessageItemState) -> Unit = { DefaultMessageItem( messageItem = it, + reactionSorting = reactionSorting, onLongItemClick = onLongItemClick, onReactionsClick = onReactionsClick, onThreadClick = onThreadClick, @@ -233,9 +237,11 @@ internal fun DefaultSystemMessageContent(systemMessageState: SystemMessageItemSt * @param onQuotedMessageClick Handler for quoted message click action. * @param onMediaGalleryPreviewResult Handler when the user receives a result from the Media Gallery Preview. */ +@Suppress("LongParameterList") @Composable internal fun DefaultMessageItem( messageItem: MessageItemState, + reactionSorting: ReactionSorting, onLongItemClick: (Message) -> Unit, onReactionsClick: (Message) -> Unit = {}, onThreadClick: (Message) -> Unit, @@ -245,6 +251,7 @@ internal fun DefaultMessageItem( ) { MessageItem( messageItem = messageItem, + reactionSorting = reactionSorting, onLongItemClick = onLongItemClick, onReactionsClick = onReactionsClick, onThreadClick = onThreadClick, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageItem.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageItem.kt index fb0e844896d..952e5cb403d 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageItem.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageItem.kt @@ -74,6 +74,7 @@ import io.getstream.chat.android.compose.ui.util.isEmojiOnlyWithoutBubble import io.getstream.chat.android.compose.ui.util.isErrorOrFailed import io.getstream.chat.android.compose.ui.util.isUploading import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.ReactionSorting import io.getstream.chat.android.models.User import io.getstream.chat.android.ui.common.state.messages.list.DeletedMessageVisibility import io.getstream.chat.android.ui.common.state.messages.list.GiphyAction @@ -93,6 +94,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.MessagePosition * * @param messageItem The message item to show, which holds the message and the group position, if the message is in * a group of messages from the same user. + * @param reactionSorting The sorting for the reactions, if we have any. * @param onLongItemClick Handler when the user selects a message, on long tap. * @param modifier Modifier for styling. * @param onReactionsClick Handler when the user taps on message reactions. @@ -116,6 +118,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.MessagePosition @Composable public fun MessageItem( messageItem: MessageItemState, + reactionSorting: ReactionSorting, onLongItemClick: (Message) -> Unit, modifier: Modifier = Modifier, onReactionsClick: (Message) -> Unit = {}, @@ -129,6 +132,7 @@ public fun MessageItem( headerContent: @Composable ColumnScope.(MessageItemState) -> Unit = { DefaultMessageItemHeaderContent( messageItem = it, + reactionSorting = reactionSorting, onReactionsClick = onReactionsClick, ) }, @@ -253,11 +257,14 @@ internal fun RowScope.DefaultMessageItemLeadingContent( * By default, we show if the message is pinned and a list of reactions for the message. * * @param messageItem The message item to show the content for. + * @param reactionSorting The sorting for the reactions, if we have any. * @param onReactionsClick Handler when the user taps on message reactions. */ +@Suppress("LongMethod") @Composable internal fun DefaultMessageItemHeaderContent( messageItem: MessageItemState, + reactionSorting: ReactionSorting, onReactionsClick: (Message) -> Unit = {}, ) { val message = messageItem.message @@ -297,13 +304,14 @@ internal fun DefaultMessageItemHeaderContent( if (!message.isDeleted()) { val ownReactions = message.ownReactions - val reactionCounts = message.reactionCounts.ifEmpty { return } + val reactionGroups = message.reactionGroups.ifEmpty { return } val iconFactory = ChatTheme.reactionIconFactory - reactionCounts + reactionGroups .filter { iconFactory.isReactionSupported(it.key) } .takeIf { it.isNotEmpty() } - ?.map { it.key } - ?.map { type -> + ?.toList() + ?.sortedWith { o1, o2 -> reactionSorting.compare(o1.second, o2.second) } + ?.map { (type, _) -> val isSelected = ownReactions.any { it.type == type } val reactionIcon = iconFactory.createReactionIcon(type) ReactionOptionItemState( diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageList.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageList.kt index 83e90b7d5d4..5f867d2303f 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageList.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageList.kt @@ -35,6 +35,8 @@ import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.ui.util.rememberMessageListState import io.getstream.chat.android.compose.viewmodel.messages.MessageListViewModel import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.ReactionSorting +import io.getstream.chat.android.models.ReactionSortingByFirstReactionAt import io.getstream.chat.android.ui.common.state.messages.list.GiphyAction import io.getstream.chat.android.ui.common.state.messages.list.MessageListItemState import io.getstream.chat.android.ui.common.state.messages.list.MessageListState @@ -47,6 +49,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.MessageListState * @param viewModel The ViewModel that stores all the data and business logic required to show a * list of messages. The user has to provide one in this case, as we require the channelId to start * the operations. + * @param reactionSorting The sorting of the reactions. Default: [ReactionSortingByFirstReactionAt]. * @param modifier Modifier for styling. * @param contentPadding Padding values to be applied to the message list surrounding the content inside. * @param messagesLazyListState State of the lazy list that represents the list of messages. Useful for controlling the @@ -77,6 +80,7 @@ import io.getstream.chat.android.ui.common.state.messages.list.MessageListState @Composable public fun MessageList( viewModel: MessageListViewModel, + reactionSorting: ReactionSorting = ReactionSortingByFirstReactionAt, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(vertical = 16.dp), messagesLazyListState: MessagesLazyListState = @@ -118,6 +122,7 @@ public fun MessageList( itemContent: @Composable (MessageListItemState) -> Unit = { messageListItem -> DefaultMessageContainer( messageListItemState = messageListItem, + reactionSorting = reactionSorting, onMediaGalleryPreviewResult = onMediaGalleryPreviewResult, onThreadClick = onThreadClick, onLongItemClick = onLongItemClick, @@ -128,6 +133,7 @@ public fun MessageList( }, ) { MessageList( + reactionSorting = reactionSorting, modifier = modifier, contentPadding = contentPadding, currentState = viewModel.currentMessagesState, @@ -154,6 +160,7 @@ public fun MessageList( * The default message container item. * * @param messageListItemState The state of the message list item. + * @param reactionSorting The sorting of the reactions. * @param onMediaGalleryPreviewResult Handler when the user receives a result from the Media Gallery Preview. * @param onThreadClick Handler when the user taps on a thread within a message item. * @param onLongItemClick Handler when the user long taps on an item. @@ -161,9 +168,11 @@ public fun MessageList( * @param onGiphyActionClick Handler when the user taps on Giphy message actions. * @param onQuotedMessageClick Handler for quoted message click action. */ +@Suppress("LongParameterList") @Composable internal fun DefaultMessageContainer( messageListItemState: MessageListItemState, + reactionSorting: ReactionSorting, onMediaGalleryPreviewResult: (MediaGalleryPreviewResult?) -> Unit = {}, onThreadClick: (Message) -> Unit, onLongItemClick: (Message) -> Unit, @@ -173,6 +182,7 @@ internal fun DefaultMessageContainer( ) { MessageContainer( messageListItemState = messageListItemState, + reactionSorting = reactionSorting, onLongItemClick = onLongItemClick, onReactionsClick = onReactionsClick, onThreadClick = onThreadClick, @@ -219,6 +229,7 @@ internal fun DefaultMessageListEmptyContent(modifier: Modifier) { * @param currentState The state of the component, represented by [MessageListState]. * @param threadMessagesStart Thread messages start at the bottom or top of the screen. * Default: [ThreadMessagesStart.BOTTOM]. + * @param reactionSorting The sorting of the reactions. * @param modifier Modifier for styling. * @param contentPadding Padding values to be applied to the message list surrounding the content inside. * @param messagesLazyListState State of the lazy list that represents the list of messages. Useful for controlling the @@ -248,6 +259,7 @@ internal fun DefaultMessageListEmptyContent(modifier: Modifier) { public fun MessageList( currentState: MessageListState, threadMessagesStart: ThreadMessagesStart = ThreadMessagesStart.BOTTOM, + reactionSorting: ReactionSorting, modifier: Modifier = Modifier, contentPadding: PaddingValues = PaddingValues(vertical = 16.dp), messagesLazyListState: MessagesLazyListState = @@ -279,6 +291,7 @@ public fun MessageList( itemContent: @Composable (MessageListItemState) -> Unit = { DefaultMessageContainer( messageListItemState = it, + reactionSorting = reactionSorting, onLongItemClick = onLongItemClick, onThreadClick = onThreadClick, onReactionsClick = onReactionsClick, diff --git a/stream-chat-android-core/api/stream-chat-android-core.api b/stream-chat-android-core/api/stream-chat-android-core.api index dcbc8307c19..391139b1131 100644 --- a/stream-chat-android-core/api/stream-chat-android-core.api +++ b/stream-chat-android-core/api/stream-chat-android-core.api @@ -1455,6 +1455,33 @@ public final class io/getstream/chat/android/models/ReactionGroup$Builder { public final fun sumScore (I)Lio/getstream/chat/android/models/ReactionGroup$Builder; } +public abstract interface class io/getstream/chat/android/models/ReactionSorting : java/util/Comparator { +} + +public final class io/getstream/chat/android/models/ReactionSortingByCount : io/getstream/chat/android/models/ReactionSorting { + public static final field INSTANCE Lio/getstream/chat/android/models/ReactionSortingByCount; + public fun compare (Lio/getstream/chat/android/models/ReactionGroup;Lio/getstream/chat/android/models/ReactionGroup;)I + public synthetic fun compare (Ljava/lang/Object;Ljava/lang/Object;)I +} + +public final class io/getstream/chat/android/models/ReactionSortingByFirstReactionAt : io/getstream/chat/android/models/ReactionSorting { + public static final field INSTANCE Lio/getstream/chat/android/models/ReactionSortingByFirstReactionAt; + public fun compare (Lio/getstream/chat/android/models/ReactionGroup;Lio/getstream/chat/android/models/ReactionGroup;)I + public synthetic fun compare (Ljava/lang/Object;Ljava/lang/Object;)I +} + +public final class io/getstream/chat/android/models/ReactionSortingByLastReactionAt : io/getstream/chat/android/models/ReactionSorting { + public static final field INSTANCE Lio/getstream/chat/android/models/ReactionSortingByLastReactionAt; + public fun compare (Lio/getstream/chat/android/models/ReactionGroup;Lio/getstream/chat/android/models/ReactionGroup;)I + public synthetic fun compare (Ljava/lang/Object;Ljava/lang/Object;)I +} + +public final class io/getstream/chat/android/models/ReactionSortingBySumScore : io/getstream/chat/android/models/ReactionSorting { + public static final field INSTANCE Lio/getstream/chat/android/models/ReactionSortingBySumScore; + public fun compare (Lio/getstream/chat/android/models/ReactionGroup;Lio/getstream/chat/android/models/ReactionGroup;)I + public synthetic fun compare (Ljava/lang/Object;Ljava/lang/Object;)I +} + public final class io/getstream/chat/android/models/SearchMessagesResult { public fun ()V public fun (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/models/SearchWarning;)V diff --git a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/ReactionSorting.kt b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/ReactionSorting.kt new file mode 100644 index 00000000000..5b262c1fbe7 --- /dev/null +++ b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/ReactionSorting.kt @@ -0,0 +1,63 @@ +/* + * 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.models + +/** + * A comparator used to sort [ReactionGroup]s. + * + */ +public interface ReactionSorting : Comparator + +/** + * Sorts [ReactionGroup]s by the sum of their scores. + * + */ +public object ReactionSortingBySumScore : ReactionSorting { + override fun compare(o1: ReactionGroup, o2: ReactionGroup): Int { + return o1.sumScore.compareTo(o2.sumScore) + } +} + +/** + * Sorts [ReactionGroup]s by their count. + * + */ +public object ReactionSortingByCount : ReactionSorting { + override fun compare(o1: ReactionGroup, o2: ReactionGroup): Int { + return o1.count.compareTo(o2.count) + } +} + +/** + * Sorts [ReactionGroup]s by the date of their last reaction. + * + */ +public object ReactionSortingByLastReactionAt : ReactionSorting { + override fun compare(o1: ReactionGroup, o2: ReactionGroup): Int { + return o1.lastReactionAt.compareTo(o2.lastReactionAt) + } +} + +/** + * Sorts [ReactionGroup]s by the date of their first reaction. + * + */ +public object ReactionSortingByFirstReactionAt : ReactionSorting { + override fun compare(o1: ReactionGroup, o2: ReactionGroup): Int { + return o1.firstReactionAt.compareTo(o2.firstReactionAt) + } +} diff --git a/stream-chat-android-core/src/test/java/io/getstream/chat/android/models/ReactionSortingTest.kt b/stream-chat-android-core/src/test/java/io/getstream/chat/android/models/ReactionSortingTest.kt new file mode 100644 index 00000000000..83205472a3b --- /dev/null +++ b/stream-chat-android-core/src/test/java/io/getstream/chat/android/models/ReactionSortingTest.kt @@ -0,0 +1,92 @@ +/* + * 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.models + +import io.getstream.chat.android.randomReactionGroup +import org.amshove.kluent.`should be equal to` +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.util.Date +import java.util.stream.Stream + +internal class ReactionSortingTest { + + companion object { + @JvmStatic + fun provideTestCases(): Stream { + return Stream.of( + listOf( + randomReactionGroup(sumScore = 5), + randomReactionGroup(sumScore = 7), + randomReactionGroup(sumScore = 10), + ).let { + ReactionSortingTestCase( + it.shuffled(), + ReactionSortingBySumScore, + it, + ) + }, + listOf( + randomReactionGroup(count = 5), + randomReactionGroup(count = 7), + randomReactionGroup(count = 10), + ).let { + ReactionSortingTestCase( + it.shuffled(), + ReactionSortingByCount, + it, + ) + }, + listOf( + randomReactionGroup(lastReactionAt = Date(0)), + randomReactionGroup(lastReactionAt = Date(5000)), + randomReactionGroup(lastReactionAt = Date(10000)), + ).let { + ReactionSortingTestCase( + it.shuffled(), + ReactionSortingByLastReactionAt, + it, + ) + }, + listOf( + randomReactionGroup(firstReactionAt = Date(0)), + randomReactionGroup(firstReactionAt = Date(5000)), + randomReactionGroup(firstReactionAt = Date(10000)), + ).let { + ReactionSortingTestCase( + it.shuffled(), + ReactionSortingByFirstReactionAt, + it, + ) + }, + ) + } + } + + @ParameterizedTest + @MethodSource("provideTestCases") + fun `ReactionSorting sorts correctly`(testCase: ReactionSortingTestCase) { + val result = testCase.input.sortedWith(testCase.sorting) + result `should be equal to` testCase.expected + } + + data class ReactionSortingTestCase( + val input: List, + val sorting: ReactionSorting, + val expected: List, + ) +} diff --git a/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt b/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt index f3391985f75..ec80bc06e79 100644 --- a/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt +++ b/stream-chat-android-core/src/testFixtures/kotlin/io/getstream/chat/android/Mother.kt @@ -513,3 +513,14 @@ public fun randomChatNetworkError( statusCode = statusCode, cause = cause, ) +public fun randomReactionGroup( + sumScore: Int = randomInt(), + count: Int = randomInt(), + firstReactionAt: Date = randomDate(), + lastReactionAt: Date = randomDate(), +): ReactionGroup = ReactionGroup( + sumScore = sumScore, + count = count, + firstReactionAt = firstReactionAt, + lastReactionAt = lastReactionAt, +) diff --git a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/home/ComponentBrowserHomeFragment.kt b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/home/ComponentBrowserHomeFragment.kt index e16a477b8d7..aca481539dc 100644 --- a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/home/ComponentBrowserHomeFragment.kt +++ b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/home/ComponentBrowserHomeFragment.kt @@ -24,6 +24,7 @@ import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.models.Reaction +import io.getstream.chat.android.models.ReactionGroup import io.getstream.chat.android.ui.helper.SupportedReactions.DefaultReactionTypes.LOL import io.getstream.chat.android.ui.helper.SupportedReactions.DefaultReactionTypes.LOVE import io.getstream.chat.android.ui.helper.SupportedReactions.DefaultReactionTypes.THUMBS_UP @@ -35,6 +36,7 @@ import io.getstream.chat.ui.sample.feature.componentbrowser.utils.randomChannel import io.getstream.chat.ui.sample.feature.componentbrowser.utils.randomMember import io.getstream.chat.ui.sample.feature.componentbrowser.utils.randomMessage import io.getstream.chat.ui.sample.feature.componentbrowser.utils.randomUser +import java.util.Date @InternalStreamChatApi class ComponentBrowserHomeFragment : Fragment() { @@ -116,11 +118,31 @@ class ComponentBrowserHomeFragment : Fragment() { private fun setupViewReactionsView() { binding.viewReactionsView.setMessage( message = randomMessage().copy( - reactionCounts = mutableMapOf( - LOVE to 10, - WUT to 20, - LOL to 20, - THUMBS_UP to 20, + reactionGroups = mutableMapOf( + LOVE to ReactionGroup( + count = 10, + sumScore = 10, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + WUT to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + LOL to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + THUMBS_UP to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), ), ownReactions = mutableListOf( Reaction(type = LOVE), diff --git a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/reactions/ComponentBrowserViewReactionsFragment.kt b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/reactions/ComponentBrowserViewReactionsFragment.kt index 2f12d97137b..6c21c9b6253 100644 --- a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/reactions/ComponentBrowserViewReactionsFragment.kt +++ b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/componentbrowser/reactions/ComponentBrowserViewReactionsFragment.kt @@ -23,12 +23,14 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.models.Reaction +import io.getstream.chat.android.models.ReactionGroup import io.getstream.chat.android.ui.helper.SupportedReactions.DefaultReactionTypes.LOL import io.getstream.chat.android.ui.helper.SupportedReactions.DefaultReactionTypes.LOVE import io.getstream.chat.android.ui.helper.SupportedReactions.DefaultReactionTypes.THUMBS_UP import io.getstream.chat.android.ui.helper.SupportedReactions.DefaultReactionTypes.WUT import io.getstream.chat.ui.sample.databinding.FragmentComponentBrowserViewReactionsViewBinding import io.getstream.chat.ui.sample.feature.componentbrowser.utils.randomMessage +import java.util.Date const val CUSTOM_REACTIONS = "CUSTOM_REACTIONS" @@ -59,8 +61,13 @@ class ComponentBrowserViewReactionsFragment : Fragment() { binding.apply { viewReactionsView1.setMessage( message = randomMessage().copy( - reactionCounts = mutableMapOf( - LOVE to 1, + reactionGroups = mutableMapOf( + LOVE to ReactionGroup( + count = 1, + sumScore = 1, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), ), ownReactions = mutableListOf( Reaction(type = LOVE), @@ -70,8 +77,13 @@ class ComponentBrowserViewReactionsFragment : Fragment() { ) viewReactionsView2.setMessage( message = randomMessage().copy( - reactionCounts = mutableMapOf( - LOVE to 1, + reactionGroups = mutableMapOf( + LOVE to ReactionGroup( + count = 1, + sumScore = 1, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), ), ownReactions = mutableListOf( Reaction(type = LOVE), @@ -81,8 +93,13 @@ class ComponentBrowserViewReactionsFragment : Fragment() { ) viewReactionsView3.setMessage( message = randomMessage().copy( - reactionCounts = mutableMapOf( - LOVE to 1, + reactionGroups = mutableMapOf( + LOVE to ReactionGroup( + count = 1, + sumScore = 1, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), ), ownReactions = mutableListOf(), ), @@ -90,8 +107,13 @@ class ComponentBrowserViewReactionsFragment : Fragment() { ) viewReactionsView4.setMessage( message = randomMessage().copy( - reactionCounts = mutableMapOf( - LOVE to 1, + reactionGroups = mutableMapOf( + LOVE to ReactionGroup( + count = 1, + sumScore = 1, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), ), ownReactions = mutableListOf(), ), @@ -99,11 +121,31 @@ class ComponentBrowserViewReactionsFragment : Fragment() { ) viewReactionsView5.setMessage( message = randomMessage().copy( - reactionCounts = mutableMapOf( - LOVE to 10, - WUT to 20, - LOL to 20, - THUMBS_UP to 20, + reactionGroups = mutableMapOf( + LOVE to ReactionGroup( + count = 10, + sumScore = 10, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + WUT to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + LOL to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + THUMBS_UP to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), ), ownReactions = mutableListOf( Reaction(type = LOVE), @@ -114,19 +156,39 @@ class ComponentBrowserViewReactionsFragment : Fragment() { ) val customReactions = if (reactions != null) { - reactions as Map + reactions as Map } else { mutableMapOf( - LOVE to 10, - WUT to 20, - LOL to 20, - THUMBS_UP to 20, + LOVE to ReactionGroup( + count = 10, + sumScore = 10, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + WUT to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + LOL to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), + THUMBS_UP to ReactionGroup( + count = 20, + sumScore = 20, + firstReactionAt = Date(), + lastReactionAt = Date(), + ), ) } viewReactionsView6.setMessage( message = randomMessage().copy( - reactionCounts = customReactions.toMutableMap(), + reactionGroups = customReactions.toMutableMap(), ownReactions = mutableListOf( Reaction(type = LOVE), Reaction(type = WUT), diff --git a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api index 80fd8529e18..eccee644361 100644 --- a/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api +++ b/stream-chat-android-ui-components/api/stream-chat-android-ui-components.api @@ -3323,7 +3323,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/reactions/ } public final class io/getstream/chat/android/ui/feature/messages/list/reactions/view/ViewReactionsViewStyle : io/getstream/chat/android/ui/helper/ViewStyle { - public fun (ILjava/lang/Integer;IIFLjava/lang/Float;IIIIIIIIIIIII)V + public fun (ILjava/lang/Integer;IIFLjava/lang/Float;IIIIIIIIIIIIILio/getstream/chat/android/models/ReactionSorting;)V public final fun component1 ()I public final fun component10 ()I public final fun component11 ()I @@ -3336,6 +3336,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/reactions/ public final fun component18 ()I public final fun component19 ()I public final fun component2 ()Ljava/lang/Integer; + public final fun component20 ()Lio/getstream/chat/android/models/ReactionSorting; public final fun component3 ()I public final fun component4 ()I public final fun component5 ()F @@ -3343,8 +3344,8 @@ public final class io/getstream/chat/android/ui/feature/messages/list/reactions/ public final fun component7 ()I public final fun component8 ()I public final fun component9 ()I - public final fun copy (ILjava/lang/Integer;IIFLjava/lang/Float;IIIIIIIIIIIII)Lio/getstream/chat/android/ui/feature/messages/list/reactions/view/ViewReactionsViewStyle; - public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/reactions/view/ViewReactionsViewStyle;ILjava/lang/Integer;IIFLjava/lang/Float;IIIIIIIIIIIIIILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/reactions/view/ViewReactionsViewStyle; + public final fun copy (ILjava/lang/Integer;IIFLjava/lang/Float;IIIIIIIIIIIIILio/getstream/chat/android/models/ReactionSorting;)Lio/getstream/chat/android/ui/feature/messages/list/reactions/view/ViewReactionsViewStyle; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/feature/messages/list/reactions/view/ViewReactionsViewStyle;ILjava/lang/Integer;IIFLjava/lang/Float;IIIIIIIIIIIIILio/getstream/chat/android/models/ReactionSorting;ILjava/lang/Object;)Lio/getstream/chat/android/ui/feature/messages/list/reactions/view/ViewReactionsViewStyle; public fun equals (Ljava/lang/Object;)Z public final fun getBubbleBorderColorMine ()I public final fun getBubbleBorderColorTheirs ()Ljava/lang/Integer; @@ -3360,6 +3361,7 @@ public final class io/getstream/chat/android/ui/feature/messages/list/reactions/ public final fun getLargeTailBubbleOffset ()I public final fun getLargeTailBubbleRadius ()I public final fun getMessageOptionsUserReactionOrientation ()I + public final fun getReactionSorting ()Lio/getstream/chat/android/models/ReactionSorting; public final fun getSmallTailBubbleCy ()I public final fun getSmallTailBubbleOffset ()I public final fun getSmallTailBubbleRadius ()I @@ -3856,6 +3858,7 @@ public final class io/getstream/chat/android/ui/utils/extensions/MessageKt { public static final fun getCreatedAtOrThrow (Lio/getstream/chat/android/models/Message;)Ljava/util/Date; public static final fun getSupportedLatestReactions (Lio/getstream/chat/android/models/Message;)Ljava/util/List; public static final fun getSupportedReactionCounts (Lio/getstream/chat/android/models/Message;)Ljava/util/Map; + public static final fun getSupportedReactionGroups (Lio/getstream/chat/android/models/Message;)Ljava/util/Map; public static final fun getUpdatedAtOrNull (Lio/getstream/chat/android/models/Message;)Ljava/util/Date; public static final fun hasNoAttachments (Lio/getstream/chat/android/models/Message;)Z public static final fun hasReactions (Lio/getstream/chat/android/models/Message;)Z diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle.kt index da0b2888b08..36c76941b91 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/MessageListItemStyle.kt @@ -500,6 +500,9 @@ public data class MessageListItemStyle( .bubbleBorderWidthTheirs(R.styleable.MessageListView_streamUiMessageReactionsBubbleBorderWidthTheirs) .bubbleColorMine(R.styleable.MessageListView_streamUiMessageReactionsBubbleColorMine) .bubbleColorTheirs(R.styleable.MessageListView_streamUiMessageReactionsBubbleColorTheirs) + .messageOptionsReactionSorting( + R.styleable.MessageListView_streamUiMessageOptionsReactionSorting, + ) .build() val editReactionsViewStyle = EditReactionsViewStyle.Builder(attributes, context) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemDiffCallback.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemDiffCallback.kt index 669bda0fe45..0fe0f5d4d59 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemDiffCallback.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/adapter/internal/MessageListItemDiffCallback.kt @@ -36,7 +36,7 @@ internal object MessageListItemDiffCallback : DiffUtil.ItemCallback ReactionSortingByFirstReactionAt + REACTION_SORTING_BY_LAST_REACTION_AT -> ReactionSortingByLastReactionAt + REACTION_SORTING_BY_SUM_SCORE -> ReactionSortingBySumScore + REACTION_SORTING_BY_COUNT -> ReactionSortingByCount + else -> ReactionSortingByFirstReactionAt + } + } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/reactions/view/internal/ViewReactionsView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/reactions/view/internal/ViewReactionsView.kt index c3a43d1f9d4..015818c8969 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/reactions/view/internal/ViewReactionsView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/list/reactions/view/internal/ViewReactionsView.kt @@ -31,7 +31,7 @@ import io.getstream.chat.android.ui.feature.messages.list.reactions.internal.Rea import io.getstream.chat.android.ui.feature.messages.list.reactions.view.ViewReactionsViewStyle import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper import io.getstream.chat.android.ui.utils.extensions.hasSingleReaction -import io.getstream.chat.android.ui.utils.extensions.supportedReactionCounts +import io.getstream.chat.android.ui.utils.extensions.supportedReactionGroups @InternalStreamChatApi public class ViewReactionsView : RecyclerView { @@ -110,8 +110,10 @@ public class ViewReactionsView : RecyclerView { } private fun createReactionItems(message: Message): List { - return message.supportedReactionCounts.keys - .mapNotNull { type -> + return message.supportedReactionGroups + .toList() + .sortedWith { o1, o2 -> reactionsViewStyle.reactionSorting.compare(o1.second, o2.second) } + .mapNotNull { (type, _) -> ChatUI.supportedReactions.getReactionDrawable(type)?.let { ReactionItem( type = type, @@ -119,6 +121,6 @@ public class ViewReactionsView : RecyclerView { reactionDrawable = it, ) } - }.sortedBy { if (isMyMessage) it.isMine else !it.isMine } + } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/Message.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/Message.kt index 2efe05dd2fd..0c0f9333718 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/Message.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/utils/extensions/Message.kt @@ -24,6 +24,7 @@ import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.models.Message import io.getstream.chat.android.models.Reaction +import io.getstream.chat.android.models.ReactionGroup import io.getstream.chat.android.models.SyncStatus import io.getstream.chat.android.models.User import io.getstream.chat.android.ui.ChatUI @@ -56,11 +57,11 @@ public fun Message.getCreatedAtOrThrow(): Date = checkNotNull(getCreatedAtOrNull } public fun Message.hasSingleReaction(): Boolean { - return supportedReactionCounts.size == 1 + return supportedReactionGroups.size == 1 } public fun Message.hasReactions(): Boolean { - return supportedReactionCounts.isNotEmpty() + return supportedReactionGroups.isNotEmpty() } public val Message.supportedLatestReactions: List @@ -72,6 +73,9 @@ public val Message.supportedLatestReactions: List } } +public val Message.supportedReactionGroups: Map + get() = reactionGroups.filterKeys { ChatUI.supportedReactions.isReactionTypeSupported(it) } + public val Message.supportedReactionCounts: Map get() { return if (reactionCounts.isEmpty()) { diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_message_list_view.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_message_list_view.xml index 840556a23c5..c637257a7e9 100644 --- a/stream-chat-android-ui-components/src/main/res/values/attrs_message_list_view.xml +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_message_list_view.xml @@ -454,6 +454,13 @@ + + + + + + + diff --git a/stream-chat-android-ui-components/src/main/res/values/attrs_view_reactions_view.xml b/stream-chat-android-ui-components/src/main/res/values/attrs_view_reactions_view.xml index 75f7e82d808..b8822db22e3 100644 --- a/stream-chat-android-ui-components/src/main/res/values/attrs_view_reactions_view.xml +++ b/stream-chat-android-ui-components/src/main/res/values/attrs_view_reactions_view.xml @@ -35,5 +35,12 @@ + + + + + + + diff --git a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt index f45ed9cb3de..f8e5702ecab 100644 --- a/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt +++ b/stream-chat-android-ui-guides/src/main/java/io/getstream/chat/android/guides/catalog/compose/customattachments/MessagesActivity.kt @@ -56,6 +56,7 @@ import io.getstream.chat.android.guides.R import io.getstream.chat.android.guides.catalog.compose.customattachments.factory.dateAttachmentFactory import io.getstream.chat.android.guides.catalog.compose.customattachments.factory.quotedDateAttachmentFactory import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.models.ReactionSortingByFirstReactionAt import io.getstream.chat.android.ui.common.state.messages.MessageMode import io.getstream.chat.android.ui.common.state.messages.Reply import io.getstream.sdk.chat.audio.recording.DefaultStreamMediaRecorder @@ -157,6 +158,7 @@ class MessagesActivity : AppCompatActivity() { .background(ChatTheme.colors.appBackground) .fillMaxSize(), viewModel = messageListViewModel, + reactionSorting = ReactionSortingByFirstReactionAt, onThreadClick = { message -> composerViewModel.setMessageMode(MessageMode.MessageThread(message)) messageListViewModel.openMessageThread(message)