Skip to content

Commit 6bfc276

Browse files
authored
Merge branch 'v7' into redesign/media-gallery
2 parents a0fd829 + 8c51a2d commit 6bfc276

3 files changed

Lines changed: 314 additions & 93 deletions

File tree

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1990,12 +1990,16 @@ public final class io/getstream/chat/android/compose/ui/components/selectedmessa
19901990
public final class io/getstream/chat/android/compose/ui/components/selectedmessage/ComposableSingletons$ReactionsMenuKt {
19911991
public static final field INSTANCE Lio/getstream/chat/android/compose/ui/components/selectedmessage/ComposableSingletons$ReactionsMenuKt;
19921992
public static field lambda-1 Lkotlin/jvm/functions/Function2;
1993-
public static field lambda-2 Lkotlin/jvm/functions/Function2;
1994-
public static field lambda-3 Lkotlin/jvm/functions/Function2;
1993+
public static field lambda-2 Lkotlin/jvm/functions/Function4;
1994+
public static field lambda-3 Lkotlin/jvm/functions/Function3;
1995+
public static field lambda-4 Lkotlin/jvm/functions/Function2;
1996+
public static field lambda-5 Lkotlin/jvm/functions/Function2;
19951997
public fun <init> ()V
19961998
public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
1997-
public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
1998-
public final fun getLambda-3$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
1999+
public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function4;
2000+
public final fun getLambda-3$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function3;
2001+
public final fun getLambda-4$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
2002+
public final fun getLambda-5$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2;
19992003
}
20002004

20012005
public final class io/getstream/chat/android/compose/ui/components/selectedmessage/ComposableSingletons$SelectedMessageMenuKt {

stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/selectedmessage/ReactionsMenu.kt

Lines changed: 172 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -17,35 +17,47 @@
1717
package io.getstream.chat.android.compose.ui.components.selectedmessage
1818

1919
import androidx.compose.foundation.background
20-
import androidx.compose.foundation.layout.Column
21-
import androidx.compose.foundation.layout.Spacer
20+
import androidx.compose.foundation.layout.Arrangement
21+
import androidx.compose.foundation.layout.Row
2222
import androidx.compose.foundation.layout.fillMaxHeight
2323
import androidx.compose.foundation.layout.fillMaxWidth
2424
import androidx.compose.foundation.layout.height
25-
import androidx.compose.foundation.rememberScrollState
25+
import androidx.compose.foundation.layout.padding
26+
import androidx.compose.foundation.layout.size
27+
import androidx.compose.foundation.layout.width
28+
import androidx.compose.foundation.lazy.LazyColumn
29+
import androidx.compose.foundation.lazy.items
30+
import androidx.compose.foundation.lazy.rememberLazyListState
31+
import androidx.compose.foundation.shape.CircleShape
2632
import androidx.compose.foundation.shape.RoundedCornerShape
27-
import androidx.compose.foundation.verticalScroll
2833
import androidx.compose.material3.BottomSheetDefaults
2934
import androidx.compose.material3.ExperimentalMaterial3Api
3035
import androidx.compose.material3.ModalBottomSheet
3136
import androidx.compose.material3.Text
3237
import androidx.compose.material3.rememberModalBottomSheetState
3338
import androidx.compose.runtime.Composable
39+
import androidx.compose.runtime.collectAsState
3440
import androidx.compose.runtime.getValue
35-
import androidx.compose.runtime.mutableStateOf
3641
import androidx.compose.runtime.remember
37-
import androidx.compose.runtime.setValue
3842
import androidx.compose.ui.Alignment
3943
import androidx.compose.ui.Modifier
40-
import androidx.compose.ui.platform.LocalResources
44+
import androidx.compose.ui.draw.clip
45+
import androidx.compose.ui.res.pluralStringResource
4146
import androidx.compose.ui.text.style.TextOverflow
4247
import androidx.compose.ui.tooling.preview.Preview
4348
import androidx.compose.ui.unit.dp
49+
import androidx.lifecycle.viewmodel.compose.viewModel
4450
import io.getstream.chat.android.compose.R
51+
import io.getstream.chat.android.compose.handlers.LoadMoreHandler
4552
import io.getstream.chat.android.compose.state.messages.MessageReactionItemState
4653
import io.getstream.chat.android.compose.state.userreactions.UserReactionItemState
54+
import io.getstream.chat.android.compose.ui.components.LoadingIndicator
55+
import io.getstream.chat.android.compose.ui.components.ShimmerProgressIndicator
56+
import io.getstream.chat.android.compose.ui.components.avatar.AvatarSize
4757
import io.getstream.chat.android.compose.ui.theme.ChatTheme
48-
import io.getstream.chat.android.compose.util.extensions.toSet
58+
import io.getstream.chat.android.compose.ui.theme.StreamTokens
59+
import io.getstream.chat.android.compose.ui.util.ViewModelStore
60+
import io.getstream.chat.android.compose.viewmodel.messages.ReactionsMenuViewModel
4961
import io.getstream.chat.android.models.ChannelCapabilities
5062
import io.getstream.chat.android.models.Message
5163
import io.getstream.chat.android.models.Reaction
@@ -103,7 +115,7 @@ public fun SelectedReactionsMenu(
103115
* Default content for the reactions menu bottom sheet.
104116
*
105117
* Composes the reaction count title, the reaction count row (chips), and the user reactions list
106-
* inside a scrollable column.
118+
* inside a scrollable column with pagination support.
107119
*
108120
* @param message The selected message.
109121
* @param currentUser The currently logged in user.
@@ -122,87 +134,152 @@ internal fun ReactionsMenuContent(
122134
onShowMoreReactionsSelected: () -> Unit,
123135
modifier: Modifier = Modifier,
124136
) {
125-
val reactionGroups = buildReactionGroups(message)
126-
val userReactions = buildUserReactionItems(
127-
message = message,
128-
currentUser = currentUser,
129-
)
130-
val resolver = ChatTheme.reactionResolver
131-
val onAddReactionClick = onShowMoreReactionsSelected
132-
.takeIf { ChannelCapabilities.SEND_REACTION in ownCapabilities }
133-
val onReactionOptionSelected: (String) -> Unit = { type ->
134-
onMessageAction(
135-
React(
136-
reaction = Reaction(
137-
messageId = message.id,
138-
type = type,
139-
emojiCode = resolver.emojiCode(type),
137+
ViewModelStore(message.id) {
138+
val viewModel = viewModel {
139+
ReactionsMenuViewModel(
140+
messageId = message.id,
141+
initialReactions = message.latestReactions,
142+
)
143+
}
144+
145+
val state by viewModel.state.collectAsState()
146+
147+
val reactionGroups = buildReactionGroups(message)
148+
val resolver = ChatTheme.reactionResolver
149+
val onAddReactionClick = onShowMoreReactionsSelected
150+
.takeIf { ChannelCapabilities.SEND_REACTION in ownCapabilities }
151+
val onReactionOptionSelected: (String) -> Unit = { type ->
152+
onMessageAction(
153+
React(
154+
reaction = Reaction(
155+
messageId = message.id,
156+
type = type,
157+
emojiCode = resolver.emojiCode(type),
158+
),
159+
message = message,
140160
),
141-
message = message,
142-
),
161+
)
162+
}
163+
164+
val userReactions = buildUserReactionItems(
165+
reactions = state.reactions,
166+
currentUser = currentUser,
143167
)
144-
}
145168

146-
var selectedReactionType by remember { mutableStateOf<String?>(null) }
147-
val filteredUserReactions = remember(userReactions, selectedReactionType) {
148-
selectedReactionType?.let { type -> userReactions.filter { it.type == type } } ?: userReactions
169+
ReactionsMenuList(
170+
reactionGroups = reactionGroups,
171+
items = userReactions,
172+
selectedReactionType = state.selectedReactionType,
173+
isLoading = state.isLoading,
174+
isLoadingMore = state.isLoadingMore,
175+
onReactionSelected = viewModel::selectReaction,
176+
onReactionOptionSelected = onReactionOptionSelected,
177+
onAddReactionClick = onAddReactionClick,
178+
onLoadMore = viewModel::loadMore,
179+
modifier = modifier,
180+
)
149181
}
182+
}
183+
184+
@Suppress("LongParameterList")
185+
@Composable
186+
private fun ReactionsMenuList(
187+
reactionGroups: List<MessageReactionItemState>,
188+
items: List<UserReactionItemState>,
189+
selectedReactionType: String?,
190+
isLoading: Boolean,
191+
isLoadingMore: Boolean,
192+
onReactionSelected: (String) -> Unit,
193+
onReactionOptionSelected: (String) -> Unit,
194+
onAddReactionClick: (() -> Unit)?,
195+
onLoadMore: () -> Unit,
196+
modifier: Modifier = Modifier,
197+
) {
198+
val listState = rememberLazyListState()
150199

151-
val reactionCountText = LocalResources.current.getQuantityString(
152-
R.plurals.stream_compose_message_reactions,
153-
filteredUserReactions.size,
154-
filteredUserReactions.size,
200+
LoadMoreHandler(
201+
lazyListState = listState,
202+
loadMore = onLoadMore,
155203
)
156204

157-
Column(
205+
LazyColumn(
206+
state = listState,
158207
horizontalAlignment = Alignment.CenterHorizontally,
159208
modifier = modifier
160209
.fillMaxWidth()
161-
.background(ChatTheme.colors.backgroundElevationElevation1)
162-
.verticalScroll(rememberScrollState()),
210+
.background(ChatTheme.colors.backgroundElevationElevation1),
163211
) {
164-
Text(
165-
text = reactionCountText,
166-
style = ChatTheme.typography.headingMedium,
167-
maxLines = 1,
168-
overflow = TextOverflow.Ellipsis,
169-
color = ChatTheme.colors.textPrimary,
170-
)
212+
item(key = "Stream_header") {
213+
val totalCount = reactionGroups.sumOf(MessageReactionItemState::count)
214+
Text(
215+
text = pluralStringResource(R.plurals.stream_compose_message_reactions, totalCount, totalCount),
216+
style = ChatTheme.typography.headingMedium,
217+
maxLines = 1,
218+
overflow = TextOverflow.Ellipsis,
219+
color = ChatTheme.colors.textPrimary,
220+
)
221+
ReactionCountRow(
222+
reactionGroups = reactionGroups,
223+
selectedReactionType = selectedReactionType,
224+
onReactionSelected = onReactionSelected,
225+
onAddReactionClick = onAddReactionClick,
226+
)
227+
}
171228

172-
ReactionCountRow(
173-
reactionGroups = reactionGroups,
174-
selectedReactionType = selectedReactionType,
175-
onReactionSelected = { type ->
176-
selectedReactionType = if (selectedReactionType == type) null else type
177-
},
178-
onAddReactionClick = onAddReactionClick,
179-
)
229+
if (isLoading) {
230+
items(ShimmerItemCount, key = { "Stream_shimmer_$it" }) {
231+
UserReactionShimmerItem()
232+
}
233+
} else {
234+
items(
235+
items = items,
236+
key = { item -> "${item.user.id}_${item.type}" },
237+
) { item ->
238+
UserReactionRow(
239+
modifier = Modifier.padding(bottom = StreamTokens.spacingXs),
240+
item = item,
241+
onClick = if (item.isMine) {
242+
{ onReactionOptionSelected(item.type) }
243+
} else {
244+
null
245+
},
246+
)
247+
}
180248

181-
UserReactionsList(
182-
userReactions = filteredUserReactions,
183-
onReactionOptionSelected = onReactionOptionSelected,
184-
)
249+
if (isLoadingMore) {
250+
item(key = "Stream_loading_more") {
251+
LoadingIndicator(modifier = Modifier.size(24.dp))
252+
}
253+
}
254+
}
185255
}
186256
}
187257

188258
@Composable
189-
private fun UserReactionsList(
190-
userReactions: List<UserReactionItemState>,
191-
onReactionOptionSelected: (String) -> Unit,
192-
) {
193-
userReactions.forEach { item ->
194-
UserReactionRow(
195-
item = item,
196-
onClick = if (item.isMine) {
197-
{ onReactionOptionSelected(item.type) }
198-
} else {
199-
null
200-
},
259+
private fun UserReactionShimmerItem(modifier: Modifier = Modifier) {
260+
Row(
261+
modifier = modifier
262+
.fillMaxWidth()
263+
.padding(horizontal = StreamTokens.spacingSm, vertical = StreamTokens.spacingXs),
264+
verticalAlignment = Alignment.CenterVertically,
265+
horizontalArrangement = Arrangement.spacedBy(StreamTokens.spacingXs),
266+
) {
267+
ShimmerProgressIndicator(
268+
modifier = Modifier
269+
.size(AvatarSize.Medium)
270+
.clip(CircleShape),
271+
)
272+
ShimmerProgressIndicator(
273+
modifier = Modifier
274+
.width(200.dp)
275+
.height(16.dp)
276+
.clip(CircleShape),
201277
)
202-
Spacer(modifier = Modifier.height(8.dp))
203278
}
204279
}
205280

281+
private const val ShimmerItemCount = 8
282+
206283
/**
207284
* Builds a list of [MessageReactionItemState] from the message's reaction groups.
208285
*
@@ -227,46 +304,52 @@ private fun buildReactionGroups(message: Message): List<MessageReactionItemState
227304
}
228305

229306
/**
230-
* Builds a list of user reactions, based on the current user and the selected message.
307+
* Builds a list of user reactions from the loaded reactions list.
231308
*
232-
* @param message The message the reactions were left for.
309+
* @param reactions The list of reactions loaded from the API.
233310
* @param currentUser The currently logged in user.
234311
*/
235312
@Composable
236313
private fun buildUserReactionItems(
237-
message: Message,
314+
reactions: List<Reaction>,
238315
currentUser: User?,
239316
): List<UserReactionItemState> {
240317
val resolver = ChatTheme.reactionResolver
241-
return message.latestReactions
242-
.mapNotNull {
318+
return remember(reactions, currentUser, resolver) {
319+
reactions.mapNotNull {
243320
val user = it.user ?: return@mapNotNull null
244-
val type = it.type
245-
246321
UserReactionItemState(
247322
user = user,
248-
type = type,
323+
type = it.type,
249324
isMine = currentUser?.id == user.id,
250-
emojiCode = resolver.emojiCode(type),
325+
emojiCode = resolver.emojiCode(it.type),
251326
)
252327
}
328+
}
253329
}
254330

255331
@Composable
256-
private fun ReactionsMenuContentPreview(selectedMessage: Message) {
257-
ReactionsMenuContent(
258-
message = selectedMessage,
259-
currentUser = PreviewUserData.user1,
260-
onMessageAction = {},
261-
onShowMoreReactionsSelected = {},
262-
ownCapabilities = ChannelCapabilities.toSet(),
332+
private fun ReactionsMenuListPreview(message: Message) {
333+
ReactionsMenuList(
334+
reactionGroups = buildReactionGroups(message),
335+
items = buildUserReactionItems(
336+
reactions = message.latestReactions,
337+
currentUser = PreviewUserData.user1,
338+
),
339+
selectedReactionType = null,
340+
isLoading = false,
341+
isLoadingMore = false,
342+
onReactionSelected = {},
343+
onReactionOptionSelected = {},
344+
onAddReactionClick = {},
345+
onLoadMore = {},
263346
)
264347
}
265348

266349
@Composable
267350
internal fun ReactionsMenuContentOneReaction() {
268-
ReactionsMenuContentPreview(
269-
selectedMessage = PreviewMessageData.message1.copy(
351+
ReactionsMenuListPreview(
352+
message = PreviewMessageData.message1.copy(
270353
latestReactions = PreviewReactionData.oneReaction,
271354
reactionGroups = PreviewReactionData.oneReactionGroup,
272355
),
@@ -275,8 +358,8 @@ internal fun ReactionsMenuContentOneReaction() {
275358

276359
@Composable
277360
internal fun ReactionsMenuContentManyReactions() {
278-
ReactionsMenuContentPreview(
279-
PreviewMessageData.message1.copy(
361+
ReactionsMenuListPreview(
362+
message = PreviewMessageData.message1.copy(
280363
latestReactions = PreviewReactionData.manyReaction,
281364
reactionGroups = PreviewReactionData.manyReactionGroups,
282365
),

0 commit comments

Comments
 (0)