Skip to content

Commit b346b1d

Browse files
authored
Add MessageReactionList and MessageReactionItem component factories (#5925)
* Expose message reactions in component factories * CHANGELOG * Refactor message reaction components to use parameter objects for improved maintainability
1 parent d1922d2 commit b346b1d

File tree

6 files changed

+158
-29
lines changed

6 files changed

+158
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
- Improved accessibility of the message list items. [#5911](https://github.com/GetStream/stream-chat-android/pull/5911)
9191

9292
### ✅ Added
93+
- Added `MessageReactionList` and `MessageReactionItem` component factories. [#5925](https://github.com/GetStream/stream-chat-android/pull/5925)
9394

9495
### ⚠️ Changed
9596

stream-chat-android-compose/api/stream-chat-android-compose.api

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2859,6 +2859,8 @@ public abstract interface class io/getstream/chat/android/compose/ui/theme/ChatC
28592859
public abstract fun MessageMenuOptionsItem (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/messageoptions/MessageOptionItemState;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
28602860
public abstract fun MessageMenuOptionsItemLeadingContent (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/messageoptions/MessageOptionItemState;Landroidx/compose/runtime/Composer;I)V
28612861
public abstract fun MessageQuotedContent (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
2862+
public abstract fun MessageReactionItem (Landroidx/compose/foundation/layout/RowScope;Lio/getstream/chat/android/compose/ui/theme/MessageReactionItemParams;Landroidx/compose/runtime/Composer;I)V
2863+
public abstract fun MessageReactionList (Lio/getstream/chat/android/compose/ui/theme/MessageReactionListParams;Landroidx/compose/runtime/Composer;I)V
28622864
public abstract fun MessageReactionPicker (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V
28632865
public abstract fun MessageReactionPickerCenterContent (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Ljava/util/Map;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V
28642866
public abstract fun MessageReactionPickerHeaderContent (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V
@@ -3037,6 +3039,8 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatComponentFacto
30373039
public static fun MessageMenuOptionsItem (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/messageoptions/MessageOptionItemState;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
30383040
public static fun MessageMenuOptionsItemLeadingContent (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/messageoptions/MessageOptionItemState;Landroidx/compose/runtime/Composer;I)V
30393041
public static fun MessageQuotedContent (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;I)V
3042+
public static fun MessageReactionItem (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/foundation/layout/RowScope;Lio/getstream/chat/android/compose/ui/theme/MessageReactionItemParams;Landroidx/compose/runtime/Composer;I)V
3043+
public static fun MessageReactionList (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Lio/getstream/chat/android/compose/ui/theme/MessageReactionListParams;Landroidx/compose/runtime/Composer;I)V
30403044
public static fun MessageReactionPicker (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V
30413045
public static fun MessageReactionPickerCenterContent (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Ljava/util/Map;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V
30423046
public static fun MessageReactionPickerHeaderContent (Lio/getstream/chat/android/compose/ui/theme/ChatComponentFactory;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V
@@ -3442,6 +3446,40 @@ public final class io/getstream/chat/android/compose/ui/theme/MessageOptionsThem
34423446
public static synthetic fun defaultTheme$default (Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme$Companion;Lio/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;
34433447
}
34443448

3449+
public final class io/getstream/chat/android/compose/ui/theme/MessageReactionItemParams {
3450+
public static final field $stable I
3451+
public fun <init> (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/reactionoptions/ReactionOptionItemState;)V
3452+
public synthetic fun <init> (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/reactionoptions/ReactionOptionItemState;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
3453+
public final fun component1 ()Landroidx/compose/ui/Modifier;
3454+
public final fun component2 ()Lio/getstream/chat/android/compose/state/reactionoptions/ReactionOptionItemState;
3455+
public final fun copy (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/reactionoptions/ReactionOptionItemState;)Lio/getstream/chat/android/compose/ui/theme/MessageReactionItemParams;
3456+
public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageReactionItemParams;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/compose/state/reactionoptions/ReactionOptionItemState;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageReactionItemParams;
3457+
public fun equals (Ljava/lang/Object;)Z
3458+
public final fun getModifier ()Landroidx/compose/ui/Modifier;
3459+
public final fun getState ()Lio/getstream/chat/android/compose/state/reactionoptions/ReactionOptionItemState;
3460+
public fun hashCode ()I
3461+
public fun toString ()Ljava/lang/String;
3462+
}
3463+
3464+
public final class io/getstream/chat/android/compose/ui/theme/MessageReactionListParams {
3465+
public static final field $stable I
3466+
public fun <init> (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V
3467+
public synthetic fun <init> (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
3468+
public final fun component1 ()Landroidx/compose/ui/Modifier;
3469+
public final fun component2 ()Lio/getstream/chat/android/models/Message;
3470+
public final fun component3 ()Ljava/util/List;
3471+
public final fun component4 ()Lkotlin/jvm/functions/Function1;
3472+
public final fun copy (Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Ljava/util/List;Lkotlin/jvm/functions/Function1;)Lio/getstream/chat/android/compose/ui/theme/MessageReactionListParams;
3473+
public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/MessageReactionListParams;Landroidx/compose/ui/Modifier;Lio/getstream/chat/android/models/Message;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/MessageReactionListParams;
3474+
public fun equals (Ljava/lang/Object;)Z
3475+
public final fun getMessage ()Lio/getstream/chat/android/models/Message;
3476+
public final fun getModifier ()Landroidx/compose/ui/Modifier;
3477+
public final fun getOnClick ()Lkotlin/jvm/functions/Function1;
3478+
public final fun getReactions ()Ljava/util/List;
3479+
public fun hashCode ()I
3480+
public fun toString ()Ljava/lang/String;
3481+
}
3482+
34453483
public final class io/getstream/chat/android/compose/ui/theme/MessageTheme {
34463484
public static final field $stable I
34473485
public static final field Companion Lio/getstream/chat/android/compose/ui/theme/MessageTheme$Companion;

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messages/MessageReactions.kt

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import androidx.compose.foundation.background
2020
import androidx.compose.foundation.layout.Row
2121
import androidx.compose.foundation.layout.RowScope
2222
import androidx.compose.foundation.layout.padding
23-
import androidx.compose.foundation.layout.size
2423
import androidx.compose.foundation.shape.RoundedCornerShape
2524
import androidx.compose.runtime.Composable
2625
import androidx.compose.ui.Alignment
@@ -35,6 +34,7 @@ import io.getstream.chat.android.compose.R
3534
import io.getstream.chat.android.compose.previewdata.PreviewReactionOptionData
3635
import io.getstream.chat.android.compose.state.reactionoptions.ReactionOptionItemState
3736
import io.getstream.chat.android.compose.ui.theme.ChatTheme
37+
import io.getstream.chat.android.compose.ui.theme.MessageReactionItemParams
3838

3939
/**
4040
* Represents a reaction bubble with a list of reactions this message has.
@@ -47,18 +47,14 @@ import io.getstream.chat.android.compose.ui.theme.ChatTheme
4747
public fun MessageReactions(
4848
options: List<ReactionOptionItemState>,
4949
modifier: Modifier = Modifier,
50-
itemContent: @Composable RowScope.(ReactionOptionItemState) -> Unit = { option ->
51-
MessageReactionItem(
52-
modifier = Modifier
53-
.semantics {
54-
testTag = "Stream_MessageReaction_${option.type}"
55-
contentDescription = option.type
56-
}
57-
.size(20.dp)
58-
.padding(2.dp)
59-
.align(Alignment.CenterVertically),
60-
option = option,
61-
)
50+
itemContent: @Composable RowScope.(ReactionOptionItemState) -> Unit = { state ->
51+
with(ChatTheme.componentFactory) {
52+
MessageReactionItem(
53+
params = MessageReactionItemParams(
54+
state = state,
55+
),
56+
)
57+
}
6258
},
6359
) {
6460
val description = pluralStringResource(

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/list/MessageItem.kt

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ import androidx.compose.foundation.layout.width
4040
import androidx.compose.foundation.layout.widthIn
4141
import androidx.compose.foundation.layout.wrapContentHeight
4242
import androidx.compose.foundation.layout.wrapContentWidth
43-
import androidx.compose.foundation.shape.RoundedCornerShape
44-
import androidx.compose.material3.minimumInteractiveComponentSize
4543
import androidx.compose.runtime.Composable
4644
import androidx.compose.runtime.LaunchedEffect
4745
import androidx.compose.runtime.getValue
@@ -53,7 +51,6 @@ import androidx.compose.ui.Alignment
5351
import androidx.compose.ui.Alignment.Companion.BottomEnd
5452
import androidx.compose.ui.Alignment.Companion.End
5553
import androidx.compose.ui.Modifier
56-
import androidx.compose.ui.draw.clip
5754
import androidx.compose.ui.graphics.Shape
5855
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
5956
import androidx.compose.ui.input.pointer.pointerInput
@@ -78,12 +75,11 @@ import io.getstream.chat.android.compose.state.mediagallerypreview.MediaGalleryP
7875
import io.getstream.chat.android.compose.state.reactionoptions.ReactionOptionItemState
7976
import io.getstream.chat.android.compose.ui.components.messages.MessageContent
8077
import io.getstream.chat.android.compose.ui.components.messages.MessageHeaderLabel
81-
import io.getstream.chat.android.compose.ui.components.messages.MessageReactions
8278
import io.getstream.chat.android.compose.ui.components.messages.PollMessageContent
8379
import io.getstream.chat.android.compose.ui.components.messages.factory.MessageContentFactory
8480
import io.getstream.chat.android.compose.ui.components.messages.getMessageBubbleColor
8581
import io.getstream.chat.android.compose.ui.theme.ChatTheme
86-
import io.getstream.chat.android.compose.ui.util.clickable
82+
import io.getstream.chat.android.compose.ui.theme.MessageReactionListParams
8783
import io.getstream.chat.android.compose.ui.util.isEmojiOnlyWithoutBubble
8884
import io.getstream.chat.android.compose.ui.util.isErrorOrFailed
8985
import io.getstream.chat.android.compose.ui.util.isUploading
@@ -408,24 +404,26 @@ internal fun DefaultMessageItemHeaderContent(
408404

409405
if (!message.isDeleted()) {
410406
val ownReactions = message.ownReactions
411-
val reactionGroups = message.reactionGroups.ifEmpty { return }
412407
val iconFactory = ChatTheme.reactionIconFactory
413-
reactionGroups.filter { iconFactory.isReactionSupported(it.key) }.takeIf { it.isNotEmpty() }?.toList()
414-
?.sortedWith { o1, o2 -> reactionSorting.compare(o1.second, o2.second) }?.map { (type, _) ->
408+
message.reactionGroups
409+
.filter { iconFactory.isReactionSupported(it.key) }
410+
.takeIf { it.isNotEmpty() }
411+
?.toList()
412+
?.sortedWith { o1, o2 -> reactionSorting.compare(o1.second, o2.second) }
413+
?.map { (type, _) ->
415414
val isSelected = ownReactions.any { it.type == type }
416415
val reactionIcon = iconFactory.createReactionIcon(type)
417416
ReactionOptionItemState(
418417
painter = reactionIcon.getPainter(isSelected),
419418
type = type,
420419
)
421-
}?.let { options ->
422-
MessageReactions(
423-
modifier = Modifier
424-
.minimumInteractiveComponentSize()
425-
.clip(shape = RoundedCornerShape(16.dp))
426-
.clickable { onReactionsClick(message) }
427-
.padding(horizontal = 4.dp, vertical = 2.dp),
428-
options = options,
420+
}?.let { reactions ->
421+
ChatTheme.componentFactory.MessageReactionList(
422+
params = MessageReactionListParams(
423+
message = message,
424+
reactions = reactions,
425+
onClick = onReactionsClick,
426+
),
429427
)
430428
}
431429
}

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatComponentFactory.kt

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import androidx.compose.foundation.lazy.LazyListState
4040
import androidx.compose.foundation.lazy.grid.GridCells
4141
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
4242
import androidx.compose.foundation.lazy.grid.LazyGridState
43+
import androidx.compose.foundation.shape.RoundedCornerShape
4344
import androidx.compose.material.icons.Icons
4445
import androidx.compose.material.icons.twotone.Warning
4546
import androidx.compose.material3.ButtonDefaults
@@ -50,12 +51,14 @@ import androidx.compose.material3.Icon
5051
import androidx.compose.material3.IconButton
5152
import androidx.compose.material3.Text
5253
import androidx.compose.material3.TopAppBarDefaults
54+
import androidx.compose.material3.minimumInteractiveComponentSize
5355
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
5456
import androidx.compose.material3.pulltorefresh.PullToRefreshState
5557
import androidx.compose.runtime.Composable
5658
import androidx.compose.runtime.remember
5759
import androidx.compose.ui.Alignment
5860
import androidx.compose.ui.Modifier
61+
import androidx.compose.ui.draw.clip
5962
import androidx.compose.ui.graphics.Color
6063
import androidx.compose.ui.graphics.Shape
6164
import androidx.compose.ui.graphics.painter.Painter
@@ -64,6 +67,9 @@ import androidx.compose.ui.platform.LocalContext
6467
import androidx.compose.ui.platform.LocalInspectionMode
6568
import androidx.compose.ui.res.painterResource
6669
import androidx.compose.ui.res.stringResource
70+
import androidx.compose.ui.semantics.contentDescription
71+
import androidx.compose.ui.semantics.semantics
72+
import androidx.compose.ui.semantics.testTag
6773
import androidx.compose.ui.text.TextStyle
6874
import androidx.compose.ui.unit.Dp
6975
import androidx.compose.ui.unit.DpOffset
@@ -117,6 +123,8 @@ import io.getstream.chat.android.compose.ui.components.messages.DefaultMessageCo
117123
import io.getstream.chat.android.compose.ui.components.messages.DefaultMessageDeletedContent
118124
import io.getstream.chat.android.compose.ui.components.messages.DefaultMessageGiphyContent
119125
import io.getstream.chat.android.compose.ui.components.messages.MessageFooter
126+
import io.getstream.chat.android.compose.ui.components.messages.MessageReactionItem
127+
import io.getstream.chat.android.compose.ui.components.messages.MessageReactions
120128
import io.getstream.chat.android.compose.ui.components.messages.MessageText
121129
import io.getstream.chat.android.compose.ui.components.messages.MessageThreadFooter
122130
import io.getstream.chat.android.compose.ui.components.messages.OwnedMessageVisibilityContent
@@ -1178,6 +1186,47 @@ public interface ChatComponentFactory {
11781186
DefaultMessageItemTrailingContent(messageItem = messageItem)
11791187
}
11801188

1189+
/**
1190+
* The default list of reactions displayed above the message bubble and is part of [MessageItemHeaderContent].
1191+
*/
1192+
@Composable
1193+
public fun MessageReactionList(
1194+
params: MessageReactionListParams,
1195+
) {
1196+
MessageReactions(
1197+
modifier = params.modifier
1198+
.minimumInteractiveComponentSize()
1199+
.clip(shape = RoundedCornerShape(16.dp))
1200+
.run {
1201+
params.onClick?.let { onClick ->
1202+
clickable { onClick(params.message) }
1203+
} ?: this
1204+
}
1205+
.padding(horizontal = 4.dp, vertical = 2.dp),
1206+
options = params.reactions,
1207+
)
1208+
}
1209+
1210+
/**
1211+
* The default individual reaction item shown inside [MessageReactionList].
1212+
*/
1213+
@Composable
1214+
public fun RowScope.MessageReactionItem(
1215+
params: MessageReactionItemParams,
1216+
) {
1217+
MessageReactionItem(
1218+
modifier = params.modifier
1219+
.semantics {
1220+
testTag = "Stream_MessageReaction_${params.state.type}"
1221+
contentDescription = params.state.type
1222+
}
1223+
.size(20.dp)
1224+
.padding(2.dp)
1225+
.align(Alignment.CenterVertically),
1226+
option = params.state,
1227+
)
1228+
}
1229+
11811230
/**
11821231
* The default Giphy message content.
11831232
*/
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.compose.ui.theme
18+
19+
import androidx.compose.ui.Modifier
20+
import io.getstream.chat.android.compose.state.reactionoptions.ReactionOptionItemState
21+
import io.getstream.chat.android.models.Message
22+
23+
/**
24+
* Parameters for the [ChatComponentFactory.MessageReactionList] component.
25+
*
26+
* @param modifier Modifier for styling.
27+
* @param message The message for which the reactions are displayed.
28+
* @param reactions The list of reaction options to display.
29+
* @param onClick Handler when the reaction list is clicked. The message is provided as a parameter.
30+
*/
31+
public data class MessageReactionListParams(
32+
val modifier: Modifier = Modifier,
33+
val message: Message,
34+
val reactions: List<ReactionOptionItemState>,
35+
val onClick: ((message: Message) -> Unit)? = null,
36+
)
37+
38+
/**
39+
* Parameters for the [ChatComponentFactory.MessageReactionItem] component.
40+
*
41+
* @param modifier Modifier for styling.
42+
* @param state The reaction option state, holding all information required to render the icon.
43+
*/
44+
public data class MessageReactionItemParams(
45+
val modifier: Modifier = Modifier,
46+
val state: ReactionOptionItemState,
47+
)

0 commit comments

Comments
 (0)