diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c29062045b..87e59330adc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Add support for moderation V2. Add `moderation` field in `Message` model to support the new version of moderation. [#5493](https://github.com/GetStream/stream-chat-android/pull/5493) ### ⚠️ Changed +- 🚨 Breaking change: ViewModels related with "searching messages" feature provide a `List` instead of a `List`. [#5500](https://github.com/GetStream/stream-chat-android/pull/5500) ### ❌ Removed @@ -83,8 +84,10 @@ - Add `ChatTheme.keyboardBehaviour` property to customize different keyboard behaviours. [#5506](https://github.com/GetStream/stream-chat-android/pull/5506) - Add `MessageOptionItemVisibility.isBlockUserVisible` property to show/hide the block user option. [#5512](https://github.com/GetStream/stream-chat-android/pull/5512) - Add `ChatTheme.channelOptionsTheme` property to customize the channel options. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) +- Add `SearchResultItemState.channel` property containing the cached info of the channel where the message was sent. [#5500](https://github.com/GetStream/stream-chat-android/pull/5500) ### ⚠️ Changed +- 🚨 Breaking change: The `SearchResultNameFormatter.formatMessageTitle` method receives a `SearchResultItemState` instead of a `Message`. [#5500](https://github.com/GetStream/stream-chat-android/pull/5500) ### ❌ Removed 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 38108b7e653..9edeae77a0f 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -104,11 +104,13 @@ public final class io/getstream/chat/android/compose/state/channels/list/ItemSta public final class io/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState : io/getstream/chat/android/compose/state/channels/list/ItemState { public static final field $stable I - public fun (Lio/getstream/chat/android/models/Message;)V + public fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Channel;)V public final fun component1 ()Lio/getstream/chat/android/models/Message; - public final fun copy (Lio/getstream/chat/android/models/Message;)Lio/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState; - public static synthetic fun copy$default (Lio/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState;Lio/getstream/chat/android/models/Message;ILjava/lang/Object;)Lio/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState; + public final fun component2 ()Lio/getstream/chat/android/models/Channel; + public final fun copy (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Channel;)Lio/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Channel;ILjava/lang/Object;)Lio/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState; public fun equals (Ljava/lang/Object;)Z + public final fun getChannel ()Lio/getstream/chat/android/models/Channel; public fun getKey ()Ljava/lang/String; public final fun getMessage ()Lio/getstream/chat/android/models/Message; public fun hashCode ()I @@ -743,10 +745,8 @@ public final class io/getstream/chat/android/compose/ui/channels/list/Composable public final class io/getstream/chat/android/compose/ui/channels/list/ComposableSingletons$SearchResultItemKt { public static final field INSTANCE Lio/getstream/chat/android/compose/ui/channels/list/ComposableSingletons$SearchResultItemKt; public static field lambda-1 Lkotlin/jvm/functions/Function4; - public static field lambda-2 Lkotlin/jvm/functions/Function4; public fun ()V public final fun getLambda-1$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function4; - public final fun getLambda-2$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function4; } public final class io/getstream/chat/android/compose/ui/channels/list/SearchResultItemKt { @@ -3420,7 +3420,7 @@ public final class io/getstream/chat/android/compose/ui/util/ReactionIconFactory public abstract interface class io/getstream/chat/android/compose/ui/util/SearchResultNameFormatter { public static final field Companion Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter$Companion; - public abstract fun formatMessageTitle (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/User;)Landroidx/compose/ui/text/AnnotatedString; + public abstract fun formatMessageTitle (Lio/getstream/chat/android/compose/state/channels/list/ItemState$SearchResultItemState;Lio/getstream/chat/android/models/User;Landroidx/compose/runtime/Composer;I)Landroidx/compose/ui/text/AnnotatedString; } public final class io/getstream/chat/android/compose/ui/util/SearchResultNameFormatter$Companion { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/state/channels/list/ItemState.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/state/channels/list/ItemState.kt index 3d29b307b16..a3e22486feb 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/state/channels/list/ItemState.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/state/channels/list/ItemState.kt @@ -45,9 +45,12 @@ public sealed class ItemState { * Represents each search result item we show in the list of channels. * * @param message The message to show. + * @param channel The channel where the message was sent. + * It can be null if the channel was not found on the local cache. */ public data class SearchResultItemState( val message: Message, + val channel: Channel?, ) : ItemState() { override val key: String = message.id } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt index 3f525cced97..3a909cb6278 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/list/SearchResultItem.kt @@ -67,6 +67,7 @@ public fun SearchResultItem( leadingContent: @Composable RowScope.(ItemState.SearchResultItemState) -> Unit = { DefaultSearchResultItemLeadingContent( searchResultItemState = it, + currentUser = currentUser, ) }, centerContent: @Composable RowScope.(ItemState.SearchResultItemState) -> Unit = { @@ -107,22 +108,33 @@ public fun SearchResultItem( * the message. * * @param searchResultItemState The state of the search result item. + * @param currentUser The currently logged in user. */ @Composable internal fun DefaultSearchResultItemLeadingContent( searchResultItemState: ItemState.SearchResultItemState, + currentUser: User?, ) { - UserAvatar( - user = searchResultItemState.message.user, - modifier = Modifier - .padding( - start = ChatTheme.dimens.channelItemHorizontalPadding, - end = 4.dp, - top = ChatTheme.dimens.channelItemVerticalPadding, - bottom = ChatTheme.dimens.channelItemVerticalPadding, + ( + searchResultItemState + .channel + ?.takeIf { it.members.size == 2 } + ?.let { it.members.firstOrNull { it.getUserId() != currentUser?.id }?.user } + ?: searchResultItemState.message.user + ) + .let { user -> + UserAvatar( + user = user, + modifier = Modifier + .padding( + start = ChatTheme.dimens.channelItemHorizontalPadding, + end = 4.dp, + top = ChatTheme.dimens.channelItemVerticalPadding, + bottom = ChatTheme.dimens.channelItemVerticalPadding, + ) + .size(ChatTheme.dimens.channelAvatarSize), ) - .size(ChatTheme.dimens.channelAvatarSize), - ) + } } /** @@ -145,7 +157,7 @@ internal fun RowScope.DefaultSearchResultItemCenterContent( verticalArrangement = Arrangement.Center, ) { Text( - text = ChatTheme.searchResultNameFormatter.formatMessageTitle(searchResultItemState.message, currentUser), + text = ChatTheme.searchResultNameFormatter.formatMessageTitle(searchResultItemState, currentUser), style = ChatTheme.typography.bodyBold, fontSize = 16.sp, maxLines = 1, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/pinned/PinnedMessageList.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/pinned/PinnedMessageList.kt index 3e01bc80978..7958a4a4469 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/pinned/PinnedMessageList.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/pinned/PinnedMessageList.kt @@ -108,7 +108,7 @@ public fun PinnedMessageList( state.results.isEmpty() && state.isLoading -> loadingContent() state.results.isEmpty() && !state.isLoading -> emptyContent() else -> PinnedMessages( - messages = state.results, + messages = state.results.map { it.message }, modifier = modifier, itemContent = itemContent, itemDivider = itemDivider, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/SearchResultNameFormatter.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/SearchResultNameFormatter.kt index d6d9611661a..2a69796fe34 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/SearchResultNameFormatter.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/util/SearchResultNameFormatter.kt @@ -16,12 +16,16 @@ package io.getstream.chat.android.compose.ui.util +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.withStyle -import io.getstream.chat.android.models.Message +import io.getstream.chat.android.compose.R +import io.getstream.chat.android.compose.state.channels.list.ItemState +import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.models.User /** @@ -30,12 +34,16 @@ import io.getstream.chat.android.models.User public fun interface SearchResultNameFormatter { /** - * Generates a title text for the given message. + * Generates a title text for the given search result item. * - * @param message The message whose data is used to generate the preview text. - * @return The formatted text representation for the given message. + * @param searchResultItem The search result item whose data is used to generate the preview text. + * @param currentUser The currently logged in user. */ - public fun formatMessageTitle(message: Message, currentUser: User?): AnnotatedString + @Composable + public fun formatMessageTitle( + searchResultItem: ItemState.SearchResultItemState, + currentUser: User?, + ): AnnotatedString public companion object { /** @@ -52,19 +60,31 @@ public fun interface SearchResultNameFormatter { } private object DefaultSearchResultNameFormatter : SearchResultNameFormatter { - override fun formatMessageTitle(message: Message, currentUser: User?): AnnotatedString = + @Composable + override fun formatMessageTitle( + searchResultItem: ItemState.SearchResultItemState, + currentUser: User?, + ): AnnotatedString = buildAnnotatedString { - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(message.user.name) - } - message.channelInfo - ?.takeIf { it.memberCount > 2 } - ?.name - ?.let { - append(" in ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(it) - } + if (searchResultItem.channel?.isOneToOne(currentUser) == true) { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(searchResultItem.channel.getOtherUsers(currentUser).firstOrNull()?.name) + } + } else { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(searchResultItem.message.user.name) } + ( + searchResultItem.channel + ?.let { ChatTheme.channelNameFormatter.formatChannelName(it, currentUser) } + ?: searchResultItem.message.channelInfo?.name + ) + ?.let { channelName -> + append(stringResource(R.string.stream_compose_in)) + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(channelName) + } + } + } } } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt index de19bb4ad8f..0b7008155d9 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt @@ -256,12 +256,19 @@ public class ChannelListViewModel( logger.d { "[observeSearchMessages] query: '$query'" } searchMessageState.filterNotNull().collectLatest { logger.v { "[observeSearchMessages] state: ${it.stringify()}" } + val channels = chatClient.repositoryFacade.selectChannels(it.messages.map { message -> message.cid }) channelsState = channelsState.copy( searchQuery = searchQuery.value, isLoading = it.isLoading, isLoadingMore = it.isLoadingMore, endOfChannels = !it.canLoadMore, - channelItems = it.messages.map(ItemState::SearchResultItemState), + channelItems = it.messages.map { + val channel = channels.firstOrNull { channel -> channel.cid == it.cid } + ItemState.SearchResultItemState( + message = it, + channel = channel, + ) + }, ) } }.onFailure { diff --git a/stream-chat-android-compose/src/main/res/values/strings.xml b/stream-chat-android-compose/src/main/res/values/strings.xml index 2d33e5391e5..63e9d35d48a 100644 --- a/stream-chat-android-compose/src/main/res/values/strings.xml +++ b/stream-chat-android-compose/src/main/res/values/strings.xml @@ -242,6 +242,7 @@ No threads here yet... + " in " %d new thread %d new threads diff --git a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api index 6022f90fecb..852ee665c68 100644 --- a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api +++ b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api @@ -558,6 +558,20 @@ public final class io/getstream/chat/android/ui/common/images/resizing/StreamCdn public final fun defaultStreamCdnImageResizing ()Lio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing; } +public final class io/getstream/chat/android/ui/common/model/MessageResult { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Channel;)V + public final fun component1 ()Lio/getstream/chat/android/models/Message; + public final fun component2 ()Lio/getstream/chat/android/models/Channel; + public final fun copy (Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Channel;)Lio/getstream/chat/android/ui/common/model/MessageResult; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/model/MessageResult;Lio/getstream/chat/android/models/Message;Lio/getstream/chat/android/models/Channel;ILjava/lang/Object;)Lio/getstream/chat/android/ui/common/model/MessageResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getChannel ()Lio/getstream/chat/android/models/Channel; + public final fun getMessage ()Lio/getstream/chat/android/models/Message; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/ui/common/notifications/StreamCoilUserIconBuilder : io/getstream/chat/android/client/notifications/handler/UserIconBuilder { public static final field $stable I public fun (Landroid/content/Context;)V diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListController.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListController.kt index 622b849e57b..921a03ce9ab 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListController.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListController.kt @@ -23,6 +23,7 @@ import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.core.internal.coroutines.DispatcherProvider import io.getstream.chat.android.models.Message import io.getstream.chat.android.models.querysort.QuerySortByField +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.chat.android.ui.common.state.pinned.PinnedMessageListState import io.getstream.log.TaggedLogger import io.getstream.log.taggedLogger @@ -97,7 +98,7 @@ public class PinnedMessageListController( _state.update { current -> current.copy( isLoading = true, - results = current.results + Message(), + results = current.results + MessageResult(Message(), null), ) } loadPinnedMessages() @@ -118,7 +119,14 @@ public class PinnedMessageListController( logger.d { "Loaded ${messages.size} pinned messages" } _state.update { current -> current.copy( - results = (current.results + messages).filter { it.id.isNotEmpty() }, + results = ( + current.results + messages.map { message -> + MessageResult( + message, + null, + ) + } + ).filter { it.message.id.isNotEmpty() }, isLoading = false, canLoadMore = messages.size == QUERY_LIMIT, nextDate = messages.lastOrNull()?.pinnedAt ?: nextDate, diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/model/MessageResult.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/model/MessageResult.kt new file mode 100644 index 00000000000..c417cf23b22 --- /dev/null +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/model/MessageResult.kt @@ -0,0 +1,25 @@ +/* + * 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.ui.common.model + +import io.getstream.chat.android.models.Channel +import io.getstream.chat.android.models.Message + +public data class MessageResult( + val message: Message, + val channel: Channel?, +) diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/pinned/PinnedMessageListState.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/pinned/PinnedMessageListState.kt index 406b89bc3a5..685d61d31a1 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/pinned/PinnedMessageListState.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/pinned/PinnedMessageListState.kt @@ -16,7 +16,7 @@ package io.getstream.chat.android.ui.common.state.pinned -import io.getstream.chat.android.models.Message +import io.getstream.chat.android.ui.common.model.MessageResult import java.util.Date /** @@ -29,7 +29,7 @@ import java.util.Date */ public data class PinnedMessageListState( val canLoadMore: Boolean, - val results: List, + val results: List, val isLoading: Boolean, val nextDate: Date, ) diff --git a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListControllerTest.kt b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListControllerTest.kt index acbfe49b1c3..cb7425c39a5 100644 --- a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListControllerTest.kt +++ b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/pinned/PinnedMessageListControllerTest.kt @@ -21,6 +21,7 @@ import io.getstream.chat.android.models.Message import io.getstream.chat.android.test.TestCall import io.getstream.chat.android.test.TestCoroutineRule import io.getstream.chat.android.test.callFrom +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.result.Error import io.getstream.result.Result import io.getstream.result.call.Call @@ -80,6 +81,7 @@ internal class PinnedMessageListControllerTest { // given val pinnedMessages = generatePinnedMessages(count = 1) val returnValue = callFrom { pinnedMessages } + val expectedResult = pinnedMessages.map { MessageResult(it, null) } val controller = PinnedMessageListController(cid, mockChannelClient(returnValue)) // when val loadingState = controller.state.value @@ -93,7 +95,7 @@ internal class PinnedMessageListControllerTest { // verify loaded state loadedState.canLoadMore `should be equal to` false loadedState.isLoading `should be equal to` false - loadedState.results `should be equal to` pinnedMessages + loadedState.results `should be equal to` expectedResult } @Test @@ -101,6 +103,7 @@ internal class PinnedMessageListControllerTest { // given val pinnedMessages = generatePinnedMessages(count = 30) val returnValue = callFrom { pinnedMessages } + val expectedResult = pinnedMessages.map { MessageResult(it, null) } val controller = PinnedMessageListController(cid, mockChannelClient(returnValue)) // when val loadingState = controller.state.value @@ -114,7 +117,7 @@ internal class PinnedMessageListControllerTest { // verify loaded state loadedState.canLoadMore `should be equal to` true loadedState.isLoading `should be equal to` false - loadedState.results `should be equal to` pinnedMessages + loadedState.results `should be equal to` expectedResult } @Test @@ -122,6 +125,7 @@ internal class PinnedMessageListControllerTest { // given val pinnedMessages = generatePinnedMessages(count = 1) val returnValue = callFrom { pinnedMessages } + val expectedResult = pinnedMessages.map { MessageResult(it, null) } val channelClient = mockChannelClient(returnValue) val controller = PinnedMessageListController(cid, channelClient) // when @@ -132,7 +136,7 @@ internal class PinnedMessageListControllerTest { // verify loaded state loadedState.canLoadMore `should be equal to` false loadedState.isLoading `should be equal to` false - loadedState.results `should be equal to` pinnedMessages + loadedState.results `should be equal to` expectedResult // verify channelClient.getPinnedMessages was called only twice verify(channelClient, times(1)).getPinnedMessages(any(), any(), any()) } 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 b4ed41a030a..3f54f91fa73 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 @@ -4232,6 +4232,8 @@ public final class io/getstream/chat/android/ui/viewmodel/channels/ChannelListVi public final class io/getstream/chat/android/ui/viewmodel/mentions/MentionListViewModel : androidx/lifecycle/ViewModel { public fun ()V + public fun (Lio/getstream/chat/android/client/ChatClient;)V + public synthetic fun (Lio/getstream/chat/android/client/ChatClient;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getErrorEvents ()Landroidx/lifecycle/LiveData; public final fun getState ()Landroidx/lifecycle/LiveData; public final fun loadMore ()V @@ -4904,6 +4906,8 @@ public final class io/getstream/chat/android/ui/viewmodel/pinned/PinnedMessageLi public final class io/getstream/chat/android/ui/viewmodel/search/SearchViewModel : androidx/lifecycle/ViewModel { public fun ()V + public fun (Lio/getstream/chat/android/client/ChatClient;)V + public synthetic fun (Lio/getstream/chat/android/client/ChatClient;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getErrorEvents ()Landroidx/lifecycle/LiveData; public final fun getState ()Landroidx/lifecycle/LiveData; public final fun loadMore ()V diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/internal/MessageResultDiffCallback.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/internal/MessageResultDiffCallback.kt new file mode 100644 index 00000000000..06aa4f8c62a --- /dev/null +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/internal/MessageResultDiffCallback.kt @@ -0,0 +1,32 @@ +/* + * 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.ui.feature.internal + +import androidx.recyclerview.widget.DiffUtil +import io.getstream.chat.android.ui.common.model.MessageResult + +internal object MessageResultDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: MessageResult, + newItem: MessageResult, + ): Boolean = oldItem.message.id == newItem.message.id + + override fun areContentsTheSame( + oldItem: MessageResult, + newItem: MessageResult, + ): Boolean = oldItem == newItem +} diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/MentionListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/MentionListView.kt index 679e49a55ce..6049ea0c88c 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/MentionListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/MentionListView.kt @@ -25,6 +25,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import io.getstream.chat.android.models.Message import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.chat.android.ui.databinding.StreamUiMentionListViewBinding import io.getstream.chat.android.ui.feature.mentions.list.internal.MentionListAdapter import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper @@ -85,7 +86,7 @@ public class MentionListView : ViewFlipper { } } - public fun showMessages(messages: List) { + public fun showMessages(messages: List) { val isEmpty = messages.isEmpty() displayedChild = if (isEmpty) Flipper.EMPTY else Flipper.RESULTS diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/internal/MentionListAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/internal/MentionListAdapter.kt index 85994012c7b..dcbdd701179 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/internal/MentionListAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/mentions/list/internal/MentionListAdapter.kt @@ -17,21 +17,19 @@ package io.getstream.chat.android.ui.feature.mentions.list.internal import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.getstream.chat.android.models.Message -import io.getstream.chat.android.ui.ChatUI -import io.getstream.chat.android.ui.common.extensions.internal.context +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.chat.android.ui.databinding.StreamUiItemMentionListBinding +import io.getstream.chat.android.ui.feature.internal.MessageResultDiffCallback import io.getstream.chat.android.ui.feature.mentions.list.MentionListView.MentionSelectedListener import io.getstream.chat.android.ui.feature.mentions.list.internal.MentionListAdapter.MessagePreviewViewHolder import io.getstream.chat.android.ui.feature.messages.preview.MessagePreviewStyle import io.getstream.chat.android.ui.feature.messages.preview.internal.MessagePreviewView -import io.getstream.chat.android.ui.utils.extensions.asMention import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater -internal class MentionListAdapter : ListAdapter(MessageDiffCallback) { +internal class MentionListAdapter : ListAdapter(MessageResultDiffCallback) { private var mentionSelectedListener: MentionSelectedListener? = null @@ -66,24 +64,9 @@ internal class MentionListAdapter : ListAdapter() { - override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean { - // Comparing only properties used by the ViewHolder - return oldItem.id == newItem.id && - oldItem.createdAt == newItem.createdAt && - oldItem.createdLocallyAt == newItem.createdLocallyAt && - oldItem.text == newItem.text && - oldItem.user == newItem.user + internal fun bind(messageResult: MessageResult) { + this.message = messageResult.message + view.renderMessageResult(messageResult) } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/preview/internal/MessagePreviewView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/preview/internal/MessagePreviewView.kt index 2c1332c4652..ad778a606d9 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/preview/internal/MessagePreviewView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/preview/internal/MessagePreviewView.kt @@ -25,9 +25,12 @@ import io.getstream.chat.android.models.Message import io.getstream.chat.android.ui.ChatUI import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.common.extensions.internal.singletonList +import io.getstream.chat.android.ui.common.model.MessageResult +import io.getstream.chat.android.ui.common.utils.extensions.isDirectMessaging import io.getstream.chat.android.ui.databinding.StreamUiMessagePreviewItemBinding import io.getstream.chat.android.ui.feature.messages.preview.MessagePreviewStyle import io.getstream.chat.android.ui.font.setTextStyle +import io.getstream.chat.android.ui.utils.extensions.asMention import io.getstream.chat.android.ui.utils.extensions.bold import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper import io.getstream.chat.android.ui.utils.extensions.getAttachmentsText @@ -70,25 +73,52 @@ internal class MessagePreviewView : FrameLayout { } } - fun setMessage(message: Message, currentUserMention: String? = null) { - binding.userAvatarView.setUser(message.user) - binding.senderNameLabel.text = formatChannelName(message) - binding.messageLabel.text = formatMessagePreview(message, currentUserMention) + fun renderMessageResult(messageResult: MessageResult) { + renderMessage(messageResult.message) + renderChannel(messageResult) + } + + private fun renderDate(message: Message) { binding.messageTimeLabel.text = ChatUI.dateFormatter.formatDate(message.createdAt ?: message.createdLocallyAt) } - private fun formatChannelName(message: Message): CharSequence { - val channel = message.channelInfo - return if (channel?.name != null && channel.memberCount > 2) { - Html.fromHtml( - context.getString( - R.string.stream_ui_message_preview_sender, - message.user.name, - channel.name, - ), - ) + private fun renderMessage(message: Message) { + renderDate(message) + binding.messageLabel.text = formatMessagePreview( + message, + ChatUI.currentUserProvider.getCurrentUser()?.asMention(context), + ) + } + + private fun renderChannel(messageResult: MessageResult) { + val isDirectMessaging = messageResult.channel?.isDirectMessaging() == true + val currentUser = ChatUI.currentUserProvider.getCurrentUser() + binding.userAvatarView.setUser( + messageResult + .channel + ?.takeIf { isDirectMessaging } + ?.let { it.members.firstOrNull { it.getUserId() != currentUser?.id }?.user } + ?: messageResult.message.user, + ) + + binding.senderNameLabel.text = if (isDirectMessaging) { + messageResult.channel?.members?.first { it.getUserId() != currentUser?.id }?.user?.name?.bold() } else { - message.user.name.bold() + ( + messageResult.channel + ?.let { ChatUI.channelNameFormatter.formatChannelName(it, currentUser) } + ?: messageResult.message.channelInfo?.name + ) + ?.let { + Html.fromHtml( + context.getString( + R.string.stream_ui_message_preview_sender, + messageResult.message.user.name, + it, + ), + ) + } + ?: messageResult.message.user.name.bold() } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/PinnedMessageListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/PinnedMessageListView.kt index 011c835bb8e..c2866f32406 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/PinnedMessageListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/PinnedMessageListView.kt @@ -26,6 +26,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import io.getstream.chat.android.models.Message import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.chat.android.ui.databinding.StreamUiPinnedMessageListViewBinding import io.getstream.chat.android.ui.feature.pinned.list.internal.PinnedMessageListAdapter import io.getstream.chat.android.ui.utils.extensions.createStreamThemeWrapper @@ -90,12 +91,12 @@ public class PinnedMessageListView : ViewFlipper { ) } - public fun showMessages(messages: List) { - val isEmpty = messages.isEmpty() + public fun showMessages(messageResults: List) { + val isEmpty = messageResults.isEmpty() displayedChild = if (isEmpty) Flipper.EMPTY else Flipper.RESULTS - adapter.submitList(messages) + adapter.submitList(messageResults) scrollListener.enablePagination() } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/internal/PinnedMessageListAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/internal/PinnedMessageListAdapter.kt index 1d15237c335..c47ceb45df1 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/internal/PinnedMessageListAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/pinned/list/internal/PinnedMessageListAdapter.kt @@ -17,20 +17,19 @@ package io.getstream.chat.android.ui.feature.pinned.list.internal import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.getstream.chat.android.models.Message -import io.getstream.chat.android.ui.ChatUI -import io.getstream.chat.android.ui.common.extensions.internal.context +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.chat.android.ui.databinding.StreamUiItemMentionListBinding import io.getstream.chat.android.ui.databinding.StreamUiPinnedMessageListLoadingMoreViewBinding +import io.getstream.chat.android.ui.feature.internal.MessageResultDiffCallback import io.getstream.chat.android.ui.feature.messages.preview.MessagePreviewStyle import io.getstream.chat.android.ui.feature.pinned.list.PinnedMessageListView.PinnedMessageSelectedListener -import io.getstream.chat.android.ui.utils.extensions.asMention import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater -internal class PinnedMessageListAdapter : ListAdapter(MessageDiffCallback) { +internal class PinnedMessageListAdapter : + ListAdapter(MessageResultDiffCallback) { private var pinnedMessageSelectedListener: PinnedMessageSelectedListener? = null @@ -60,7 +59,7 @@ internal class PinnedMessageListAdapter : ListAdapter() { - override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean { - // Comparing only properties used by the ViewHolder - return oldItem.id == newItem.id && - oldItem.createdAt == newItem.createdAt && - oldItem.createdLocallyAt == newItem.createdLocallyAt && - oldItem.text == newItem.text && - oldItem.user == newItem.user + internal fun bind(messageResult: MessageResult) { + this.message = messageResult.message + binding.root.renderMessageResult(messageResult) } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/internal/SearchResultListAdapter.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/internal/SearchResultListAdapter.kt index 41741d557e0..35f027a8965 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/internal/SearchResultListAdapter.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/internal/SearchResultListAdapter.kt @@ -18,22 +18,20 @@ package io.getstream.chat.android.ui.feature.search.internal import android.view.ViewGroup import androidx.core.view.updateLayoutParams -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import io.getstream.chat.android.models.Message -import io.getstream.chat.android.ui.ChatUI -import io.getstream.chat.android.ui.common.extensions.internal.context +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.chat.android.ui.databinding.StreamUiItemMentionListBinding +import io.getstream.chat.android.ui.feature.internal.MessageResultDiffCallback import io.getstream.chat.android.ui.feature.search.internal.SearchResultListAdapter.MessagePreviewViewHolder import io.getstream.chat.android.ui.feature.search.list.SearchResultListView.SearchResultSelectedListener import io.getstream.chat.android.ui.feature.search.list.SearchResultListViewStyle -import io.getstream.chat.android.ui.utils.extensions.asMention import io.getstream.chat.android.ui.utils.extensions.streamThemeInflater internal class SearchResultListAdapter( private val style: SearchResultListViewStyle, -) : ListAdapter(MessageDiffCallback) { +) : ListAdapter(MessageResultDiffCallback) { private var searchResultSelectedListener: SearchResultSelectedListener? = null @@ -84,24 +82,9 @@ internal class SearchResultListAdapter( } } - internal fun bind(message: Message) { - this.message = message - binding.root.setMessage(message, ChatUI.currentUserProvider.getCurrentUser()?.asMention(context)) - } - } - - private object MessageDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean { - // Comparing only properties used by the ViewHolder - return oldItem.id == newItem.id && - oldItem.createdAt == newItem.createdAt && - oldItem.createdLocallyAt == newItem.createdLocallyAt && - oldItem.text == newItem.text && - oldItem.user == newItem.user + internal fun bind(messageResult: MessageResult) { + this.message = messageResult.message + binding.root.renderMessageResult(messageResult) } } } diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/list/SearchResultListView.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/list/SearchResultListView.kt index f15c4637eb4..199af2d1fc1 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/list/SearchResultListView.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/search/list/SearchResultListView.kt @@ -24,6 +24,7 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import io.getstream.chat.android.models.Message import io.getstream.chat.android.ui.R +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.chat.android.ui.databinding.StreamUiSearchResultListViewBinding import io.getstream.chat.android.ui.feature.search.internal.SearchResultListAdapter import io.getstream.chat.android.ui.font.setTextStyle @@ -86,7 +87,7 @@ public class SearchResultListView : ViewFlipper { /** * Shows the list of search results. */ - public fun showMessages(query: String, messages: List) { + public fun showMessages(query: String, messages: List) { val isEmpty = messages.isEmpty() displayedChild = if (isEmpty) Flipper.EMPTY else Flipper.RESULTS diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/mentions/MentionListViewModel.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/mentions/MentionListViewModel.kt index c23a4da67ef..118679875cd 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/mentions/MentionListViewModel.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/mentions/MentionListViewModel.kt @@ -22,8 +22,8 @@ import androidx.lifecycle.ViewModel import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.core.internal.coroutines.DispatcherProvider import io.getstream.chat.android.models.Filters -import io.getstream.chat.android.models.Message import io.getstream.chat.android.state.utils.Event +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.log.taggedLogger import io.getstream.result.Result import kotlinx.coroutines.CoroutineScope @@ -31,11 +31,13 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch -public class MentionListViewModel : ViewModel() { +public class MentionListViewModel( + private val chatClient: ChatClient = ChatClient.instance(), +) : ViewModel() { public data class State( val canLoadMore: Boolean, - val results: List, + val results: List, val isLoading: Boolean, ) @@ -97,7 +99,7 @@ public class MentionListViewModel : ViewModel() { private suspend fun fetchServerResults() { val currentState = _state.value!! - val currentUser = requireNotNull(ChatClient.instance().clientState.user.value) + val currentUser = requireNotNull(chatClient.clientState.user.value) val channelFilter = Filters.`in`("members", listOf(currentUser.id)) val messageFilter = Filters.contains("mentioned_users.id", currentUser.id) @@ -105,7 +107,7 @@ public class MentionListViewModel : ViewModel() { "Getting mentions (offset: ${currentState.results.size}, limit: $QUERY_LIMIT, user ID: ${currentUser.id})" } - val result = ChatClient.instance() + val result = chatClient .searchMessages( channelFilter = channelFilter, messageFilter = messageFilter, @@ -117,9 +119,15 @@ public class MentionListViewModel : ViewModel() { when (result) { is Result.Success -> { val messages = result.value.messages + val channels = chatClient.repositoryFacade.selectChannels(messages.map { it.cid }) logger.d { "Got ${messages.size} messages" } _state.value = currentState.copy( - results = currentState.results + messages, + results = currentState.results + messages.map { message -> + MessageResult( + message = message, + channel = channels.firstOrNull { it.cid == message.cid }, + ) + }, isLoading = false, canLoadMore = messages.size == QUERY_LIMIT, ) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/pinned/PinnedMessageListViewModelBinding.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/pinned/PinnedMessageListViewModelBinding.kt index 157d77b42b4..8e8982d42b6 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/pinned/PinnedMessageListViewModelBinding.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/pinned/PinnedMessageListViewModelBinding.kt @@ -32,7 +32,7 @@ import io.getstream.chat.android.ui.feature.pinned.list.PinnedMessageListView @JvmName("bind") public fun PinnedMessageListViewModel.bindView(view: PinnedMessageListView, lifecycleOwner: LifecycleOwner) { state.observe(lifecycleOwner) { state -> - val isLoadingMore = state.results.isNotEmpty() && state.results.last().id == "" + val isLoadingMore = state.results.isNotEmpty() && state.results.last().message.id == "" when { isLoadingMore -> { diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt index 8ba9ef5f6e9..5357d3f8a92 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/viewmodel/search/SearchViewModel.kt @@ -24,6 +24,7 @@ import io.getstream.chat.android.core.internal.coroutines.DispatcherProvider import io.getstream.chat.android.models.Filters import io.getstream.chat.android.models.Message import io.getstream.chat.android.state.utils.Event +import io.getstream.chat.android.ui.common.model.MessageResult import io.getstream.log.taggedLogger import io.getstream.result.Error import io.getstream.result.Result @@ -35,7 +36,9 @@ import kotlinx.coroutines.launch /** * ViewModel responsible for searching for messages that match a particular search query. */ -public class SearchViewModel : ViewModel() { +public class SearchViewModel( + private val chatClient: ChatClient = ChatClient.instance(), +) : ViewModel() { private val _state: MutableLiveData = MutableLiveData(State()) @@ -133,11 +136,17 @@ public class SearchViewModel : ViewModel() { /** * Notifies the UI about the search results and enables the pagination. */ - private fun handleSearchMessageSuccess(messages: List) { + private suspend fun handleSearchMessageSuccess(messages: List) { logger.d { "Found messages: ${messages.size}" } val currentState = _state.value!! + val channels = chatClient.repositoryFacade.selectChannels(messages.map { it.cid }) _state.value = currentState.copy( - results = currentState.results + messages, + results = currentState.results + messages.map { + MessageResult( + message = it, + channel = channels.firstOrNull { channel -> channel.cid == it.cid }, + ) + }, isLoading = false, isLoadingMore = false, canLoadMore = messages.size == QUERY_LIMIT, @@ -165,9 +174,9 @@ public class SearchViewModel : ViewModel() { */ private suspend fun searchMessages(query: String, offset: Int): Result> { logger.d { "Searching for \"$query\" with offset: $offset" } - val currentUser = requireNotNull(ChatClient.instance().clientState.user.value) + val currentUser = requireNotNull(chatClient.clientState.user.value) // TODO: use the pagination based on "limit" nad "next" params here - return ChatClient.instance() + return chatClient .searchMessages( channelFilter = Filters.`in`("members", listOf(currentUser.id)), messageFilter = Filters.autocomplete("text", query), @@ -190,7 +199,7 @@ public class SearchViewModel : ViewModel() { public data class State( val query: String = "", val canLoadMore: Boolean = true, - val results: List = emptyList(), + val results: List = emptyList(), val isLoading: Boolean = false, val isLoadingMore: Boolean = false, )