From 950b84ca7fced209a330c79ff6ed91367821f1fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Toli=C4=87?= <37080097+MarinTolic@users.noreply.github.com> Date: Mon, 30 Jan 2023 13:28:43 +0100 Subject: [PATCH] [4608] [v5] Navigate to Thread Messages via PN (#4612) * [4608] Implemented the ability to navigate to thread messages by clicking on push notifications * [4608] Implemented the ability to navigate to thread messages by clicking on push notifications * [4608] Implement core and UI side changes allowing more reliable displaying of thread messages when opened by clicking on PNs. * [4608] Make the load notification worker attempt to fetch the thread parent message if the message that has arrived is a thread message. * [4608] Appease Detekt * [4608] Appease Detekt * [4608] Implement a feature flag for the new `MessageListViewModel` ability to navigate to threads via PNs * [4608] Implement a feature flag for the new `MessageListViewModel` ability to navigate to threads via PNs * [4608] Implement a feature flag for the new `MessageListViewModel` ability to navigate to threads via PNs * [4608] Update the changelog. * [4608] Switch to using the provided `ChatClient` class parameter * [4608] Remove leftover logging messages --- CHANGELOG.md | 1 + .../LoadNotificationDataWorker.kt | 10 +- .../android/offline/extensions/ChatClient.kt | 49 +++++++ .../logic/channel/internal/ChannelLogic.kt | 3 +- .../channel/thread/internal/ThreadLogic.kt | 4 +- .../plugin/logic/internal/LogicRegistry.kt | 19 +++ .../state/internal/ChatClientStateCalls.kt | 19 +++ .../ui/sample/feature/chat/ChatFragment.kt | 8 +- .../api/stream-chat-android-ui-components.api | 8 +- .../messages/MessageListViewModel.kt | 122 +++++++++++++++--- .../factory/MessageListViewModelFactory.kt | 20 ++- 11 files changed, 239 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9432b04cb8..6f76b7e3181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ ### ✅ Added - Added a feature flag to `ChatUI` called `showThreadSeparatorInEmptyThread`. You can use this to enable a thread separator if the thread is empty. [#4629](https://github.com/GetStream/stream-chat-android/pull/4629) - Added the `messageLimit` parameter to MessageListViewModel and MessageListViewModelFactory. [#4634](https://github.com/GetStream/stream-chat-android/pull/4634) +- Added the feature flag boolean `navigateToThreadViaNotification` to `MessageListViewModel` and `MessageListViewModelFactory`. If it is set to true and a thread message has been received via push notification, clicking on the notification will make the SDK automatically navigate to the thread. If set to false, the SDK will always navigate to the channel containing the thread without navigating to the thread itself. [#4612](https://github.com/GetStream/stream-chat-android/pull/4612) ### ⚠️ Changed diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/LoadNotificationDataWorker.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/LoadNotificationDataWorker.kt index 1b2b3e5a4ff..4413ac4aaad 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/LoadNotificationDataWorker.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/notifications/LoadNotificationDataWorker.kt @@ -56,8 +56,16 @@ internal class LoadNotificationDataWorker( val getChannel = client.queryChannel(channelType, channelId, QueryChannelRequest()) val result = getChannel.zipWith(getMessage).await() + if (result.isSuccess) { val (channel, message) = result.data() + val messageParentId = message.parentId + + if (messageParentId != null) { + logger.v { "[doWork] fetching thread parent message." } + client.getMessage(messageParentId).await() + } + ChatClient.displayNotification(channel = channel, message = message) Result.success() } else { @@ -120,7 +128,7 @@ internal class LoadNotificationDataWorker( context: Context, channelId: String, channelType: String, - messageId: String + messageId: String, ) { val syncMessagesWork = OneTimeWorkRequestBuilder() .setInputData( diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/extensions/ChatClient.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/extensions/ChatClient.kt index 72e12e2e31f..4cdc3bf2565 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/extensions/ChatClient.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/extensions/ChatClient.kt @@ -28,6 +28,7 @@ import io.getstream.chat.android.client.api.models.QueryChannelsRequest import io.getstream.chat.android.client.call.Call import io.getstream.chat.android.client.call.CoroutineCall import io.getstream.chat.android.client.errors.ChatError +import io.getstream.chat.android.client.events.ChatEventHandler import io.getstream.chat.android.client.extensions.cidToTypeAndId import io.getstream.chat.android.client.extensions.internal.isEphemeral import io.getstream.chat.android.client.models.Attachment @@ -37,6 +38,7 @@ import io.getstream.chat.android.client.utils.Result import io.getstream.chat.android.client.utils.internal.validateCidWithResult import io.getstream.chat.android.client.utils.map import io.getstream.chat.android.client.utils.toResultError +import io.getstream.chat.android.core.internal.InternalStreamChatApi import io.getstream.chat.android.core.internal.coroutines.DispatcherProvider import io.getstream.chat.android.offline.event.handler.chat.factory.ChatEventHandlerFactory import io.getstream.chat.android.offline.extensions.internal.logic @@ -144,6 +146,30 @@ public fun ChatClient.getRepliesAsState( return requestsAsState(coroutineScope).getReplies(messageId, messageLimit) } +/** + * Returns thread replies in the form of [ThreadState], however, unlike [getRepliesAsState] + * it will return it only after the API call made to get replies has ended. Thread state + * will be returned regardless if the API call has succeeded or failed, the only difference is + * in how up to date the replies in the thread state are. + * + * @param messageId The ID of the original message the replies were made to. + * @param messageLimit The number of messages that will be initially loaded. + * @param coroutineScope The [CoroutineScope] used for executing the request. + * + * @return [ThreadState] wrapped inside a [Call]. + */ +@InternalStreamChatApi +@JvmOverloads +public fun ChatClient.getRepliesAsStateCall( + messageId: String, + messageLimit: Int, + coroutineScope: CoroutineScope = CoroutineScope(DispatcherProvider.IO), +): Call { + return CoroutineCall(scope = state.scope) { + Result(requestsAsState(coroutineScope).getRepliesAsStateCall(messageId, messageLimit)) + } +} + /** * Provides an ease-of-use piece of functionality that checks if the user is available or not. If it's not, we don't emit * any state, but rather return an empty StateFlow. @@ -289,6 +315,29 @@ public fun ChatClient.cancelEphemeralMessage(message: Message): Call { } } +/** + * Attempts to fetch the message from offline cache before making an API call. + * + * @param messageId The id of the message we are fetching. + * + * @return The message with the corresponding iID wrapped inside a [Call]. + */ +@InternalStreamChatApi +@CheckResult +public fun ChatClient.getMessageUsingCache( + messageId: String, +): Call { + return CoroutineCall(state.scope) { + val message = logic.getMessageById(messageId) ?: logic.getMessageByIdFromDb(messageId) + + if (message != null) { + Result(data = message) + } else { + getMessage(messageId).await() + } + } +} + /** * Loads message for a given message id and channel id. * diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/internal/ChannelLogic.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/internal/ChannelLogic.kt index 9843234caba..0c573c5b702 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/internal/ChannelLogic.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/internal/ChannelLogic.kt @@ -153,7 +153,8 @@ internal class ChannelLogic( configs = configs, users = users.values.toList(), channels = listOf(channel), - messages = channel.messages + messages = channel.messages, + cacheForMessages = true ) } diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/thread/internal/ThreadLogic.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/thread/internal/ThreadLogic.kt index a1ebf47e2eb..8f09d5ab16d 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/thread/internal/ThreadLogic.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/channel/thread/internal/ThreadLogic.kt @@ -29,7 +29,9 @@ import io.getstream.chat.android.client.plugin.listeners.ThreadQueryListener import io.getstream.chat.android.offline.plugin.state.channel.thread.internal.ThreadMutableState /** Logic class for thread state management. Implements [ThreadQueryListener] as listener for LLC requests. */ -internal class ThreadLogic(private val threadStateLogic: ThreadStateLogic) { +internal class ThreadLogic( + private val threadStateLogic: ThreadStateLogic, +) { private val mutableState: ThreadMutableState = threadStateLogic.writeThreadState() diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/internal/LogicRegistry.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/internal/LogicRegistry.kt index 78b2d638ce2..2a8b8df3bb1 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/internal/LogicRegistry.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/logic/internal/LogicRegistry.kt @@ -124,11 +124,29 @@ internal class LogicRegistry internal constructor( } } + /** + * Attempts to fetch the message with the given ID from the mutable state. + * + * @see [getMessageByIdFromDb] if you need to search the database for the message as well. + * + * @param messageId The ID of the message we are attempting to retrieve. + * + * @return The message with the given id, if such exists, null otherwise. + */ fun getMessageById(messageId: String): Message? { return channelFromMessageId(messageId)?.getMessage(messageId) ?: threadFromMessageId(messageId)?.getMessage(messageId) } + /** + * Attempts to fetch the message with the given ID from the mutable database. + * + * @param messageId The ID of the message we are attempting to retrieve. + * + * @return The message with the given id, if such exists, null otherwise. + */ + suspend fun getMessageByIdFromDb(messageId: String): Message? = repos.selectMessage(messageId)?.copy() + /** * This method returns [ChannelLogic] if the messages passed is not only in a thread. Use this to avoid * updating [ChannelLogic] for a messages that is only inside [ThreadLogic]. If you get null as a result, @@ -177,6 +195,7 @@ internal class LogicRegistry internal constructor( return threads.getOrPut(messageId) { val mutableState = stateRegistry.mutableThread(messageId) val stateLogic = ThreadStateLogic(mutableState) + ThreadLogic(stateLogic) } } diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/state/internal/ChatClientStateCalls.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/state/internal/ChatClientStateCalls.kt index 7b10359c70e..be6bc34aaf1 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/state/internal/ChatClientStateCalls.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/offline/plugin/state/internal/ChatClientStateCalls.kt @@ -81,4 +81,23 @@ internal class ChatClientStateCalls( chatClient.getReplies(messageId, messageLimit).launch(scope) return state.thread(messageId) } + + /** + * Fetches replies from the backend and returns them in the form of a [ThreadState]. + * Unlike [getReplies] which makes an API call and instantly returns [ThreadState], this function + * will wait for the API call completion and then return [ThreadState]. + * + * This is useful in situations such as when we want to focus on the last message in a thread after a PN + * has been received, avoiding multiple [ThreadState] emissions simplifies handling it in the UI. + * + * @param messageId The id of the message we want to get replies for. + * @param messageLimit The upper limit of how many replies should be fetched. + * + * @return The replies in the form of [ThreadState]. + */ + internal suspend fun getRepliesAsStateCall(messageId: String, messageLimit: Int): ThreadState { + logger.d { "getting replied for message with id: $messageId" } + chatClient.getReplies(messageId, messageLimit).await() + return state.thread(messageId) + } } diff --git a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt index 4dee6851f4c..23463a15fd1 100644 --- a/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt +++ b/stream-chat-android-ui-components-sample/src/main/kotlin/io/getstream/chat/ui/sample/feature/chat/ChatFragment.kt @@ -62,7 +62,13 @@ class ChatFragment : Fragment() { private val args: ChatFragmentArgs by navArgs() - private val factory: MessageListViewModelFactory by lazy { MessageListViewModelFactory(args.cid, args.messageId) } + private val factory: MessageListViewModelFactory by lazy { + MessageListViewModelFactory( + cid = args.cid, + messageId = args.messageId, + navigateToThreadViaNotification = true + ) + } private val chatViewModelFactory: ChatViewModelFactory by lazy { ChatViewModelFactory(args.cid) } private val headerViewModel: MessageListHeaderViewModel by viewModels { factory } private val messageListViewModel: MessageListViewModel by viewModels { factory } 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 a01d5dcc9fb..a4b865cf72f 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 @@ -206,8 +206,8 @@ public final class com/getstream/sdk/chat/viewmodel/MessageInputViewModel : andr public final class com/getstream/sdk/chat/viewmodel/messages/MessageListViewModel : androidx/lifecycle/ViewModel { public static final field DEFAULT_MESSAGES_LIMIT I public static final field SEPARATOR_TIME I - public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/client/setup/state/ClientState;I)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/client/setup/state/ClientState;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/client/setup/state/ClientState;IZ)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/client/setup/state/ClientState;IZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getChannel ()Landroidx/lifecycle/LiveData; public final fun getChannelState ()Lkotlinx/coroutines/flow/StateFlow; public final fun getDeletedMessageVisibility ()Landroidx/lifecycle/LiveData; @@ -3772,7 +3772,8 @@ public final class io/getstream/chat/android/ui/message/list/viewmodel/factory/M public fun (Ljava/lang/String;)V public fun (Ljava/lang/String;Ljava/lang/String;)V public fun (Ljava/lang/String;Ljava/lang/String;I)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;IZ)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;IZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun create (Ljava/lang/Class;)Landroidx/lifecycle/ViewModel; } @@ -3782,6 +3783,7 @@ public final class io/getstream/chat/android/ui/message/list/viewmodel/factory/M public final fun cid (Ljava/lang/String;)Lio/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory$Builder; public final fun messageId (Ljava/lang/String;)Lio/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory$Builder; public final fun messageLimit (I)Lio/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory$Builder; + public final fun navigateToThreadViaNotification (Z)Lio/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory$Builder; } public final class io/getstream/chat/android/ui/message/preview/MessagePreviewStyle { diff --git a/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/viewmodel/messages/MessageListViewModel.kt b/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/viewmodel/messages/MessageListViewModel.kt index 7b709e2f3c9..dd484eebb34 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/viewmodel/messages/MessageListViewModel.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/com/getstream/sdk/chat/viewmodel/messages/MessageListViewModel.kt @@ -49,7 +49,9 @@ import io.getstream.chat.android.client.utils.Result import io.getstream.chat.android.common.state.DeletedMessageVisibility import io.getstream.chat.android.common.state.MessageFooterVisibility import io.getstream.chat.android.offline.extensions.cancelEphemeralMessage +import io.getstream.chat.android.offline.extensions.getMessageUsingCache import io.getstream.chat.android.offline.extensions.getRepliesAsState +import io.getstream.chat.android.offline.extensions.getRepliesAsStateCall import io.getstream.chat.android.offline.extensions.loadMessageById import io.getstream.chat.android.offline.extensions.loadNewerMessages import io.getstream.chat.android.offline.extensions.loadOlderMessages @@ -81,14 +83,18 @@ import io.getstream.chat.android.livedata.utils.Event as EventWrapper * @param clientState Client state of SDK that contains information such as the current user and connection state. * such as the current user, connection state, unread counts etc. * @param messageLimit The message limit when loading a new page. + * @param navigateToThreadViaNotification If true, when a thread message arrives in a push notification, + * clicking it will automatically open the thread in which the message is located. If false, the SDK will always + * navigate to the channel containing the thread but will not navigate to the thread itself. */ -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") public class MessageListViewModel( private val cid: String, private val messageId: String? = null, private val chatClient: ChatClient = ChatClient.instance(), private val clientState: ClientState = chatClient.clientState, - private val messageLimit: Int = DEFAULT_MESSAGES_LIMIT + private val messageLimit: Int = DEFAULT_MESSAGES_LIMIT, + private val navigateToThreadViaNotification: Boolean = false, ) : ViewModel() { /** @@ -617,19 +623,20 @@ public class MessageListViewModel( ?.message if (message != null) { - _targetMessage.value = message!! + focusMessage(message) } else { - chatClient.loadMessageById( - cid, - event.messageId - ).enqueue { result -> - if (result.isSuccess) { - _targetMessage.value = result.data() - } else { - val error = result.error() - logger.e { "Could not load message: ${error.message}. Cause: ${error.cause?.message}" } + chatClient.getMessageUsingCache(event.messageId) + .enqueue { result -> + if (result.isSuccess) { + focusMessage(result.data()) + } else { + val error = result.error() + logger.e { + "[Event.ShowMessage] Could not load message: ${error.message}." + + " Cause: ${error.cause?.message}" + } + } } - } } } is Event.RemoveAttachment -> { @@ -862,7 +869,7 @@ public class MessageListViewModel( * @param parentMessage The message with the thread we want to observe. */ private fun onThreadModeEntered(parentMessage: Message) { - loadThreadWithOfflinePlugin(parentMessage) + loadThread(parentMessage) } /** @@ -871,12 +878,26 @@ public class MessageListViewModel( * * @param parentMessage The message with the thread we want to observe. */ - private fun loadThreadWithOfflinePlugin(parentMessage: Message) { + private fun loadThread(parentMessage: Message) { val state = chatClient.getRepliesAsState(parentMessage.id, messageLimit) currentMode = Mode.Thread(parentMessage, state) setThreadMessages(state.messages.asLiveData()) } + // TODO better naming and write kdocs + private suspend fun loadThreadSuspending(parentMessage: Message) { + val result = chatClient.getRepliesAsStateCall(parentMessage.id, DEFAULT_MESSAGES_LIMIT).await() + val isSuccess = result.isSuccess + + if (isSuccess) { + currentMode = Mode.Thread(parentMessage, result.data()) + setThreadMessages(result.data().messages.asLiveData()) + } else { + val error = result.error() + logger.e { "Could not load thread: ${error.message}. Cause: ${error.cause?.message}" } + } + } + /** * Handles reacting to messages while taking into account if unique reactions are enforced. * @@ -947,6 +968,77 @@ public class MessageListViewModel( this.messageFooterVisibility.value = messageFooterVisibility } + /** + * Loads the message and puts it in focus. + * Capable of loading both channel and thread messages. + * + * Will try to fetch the message from cache before attempting an API call. + * + * @param message The message that should be put in focus. + */ + private fun focusMessage(message: Message) { + if (message.parentId != null && navigateToThreadViaNotification) { + focusThreadMessage(message) + } else { + focusChannelMessage(message.id) + } + } + + /** + * Loads the channel message with the surrounding messages and puts it in focus. + * Will try to fetch the messages from cache before attempting an API call. + * + * Use this method for channel messages only, for focusing thread messages see [focusThreadMessage]. + * + * @param messageId The ID of the message that should be put in focus. + */ + private fun focusChannelMessage(messageId: String) { + chatClient.loadMessageById( + cid, + messageId + ).enqueue { result -> + if (result.isSuccess) { + _targetMessage.value = result.data() + } else { + val error = result.error() + logger.e { + "[focusChannelMessage] Could not load message: ${error.message}." + + " Cause: ${error.cause?.message}" + } + } + } + } + + /** + * Loads the thread message with its parent thread and puts the message in focus. + * Will try to fetch the message and thread from cache before attempting an API call. + * + * Use this method for thread messages only, for focusing channel messages see [focusChannelMessage]. + * + * @param threadMessage The thread message that should be put in focus. + */ + private fun focusThreadMessage(threadMessage: Message) { + val parentId = threadMessage.parentId + + if (parentId != null) { + chatClient.getMessageUsingCache(parentId).enqueue { result -> + if (result.isSuccess) { + viewModelScope.launch { + loadThreadSuspending(result.data()) + + _targetMessage.value = threadMessage + } + } else { + val error = result.error() + logger.e { + "[focusThreadMessage] Could not load message: ${error.message}." + + " Cause: ${error.cause?.message}" + } + } + } + } + } + /** * The current state of the message list. */ diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory.kt index 5e2a9020e90..584d38ed2e3 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/message/list/viewmodel/factory/MessageListViewModelFactory.kt @@ -30,6 +30,9 @@ import io.getstream.chat.android.ui.message.list.header.viewmodel.MessageListHea * @param cid The channel id in the format messaging:123. * @param messageId The id of the target message to displayed. * @param messageLimit The message limit when loading a new page. + * @param navigateToThreadViaNotification If true, when a thread message arrives in a push notification, + * clicking it will automatically open the thread in which the message is located. If false, the SDK will always + * navigate to the channel containing the thread but will not navigate to the thread itself. * * @see MessageListViewModel * @see MessageListHeaderViewModel @@ -39,6 +42,7 @@ public class MessageListViewModelFactory @JvmOverloads constructor( private val cid: String, private val messageId: String? = null, private val messageLimit: Int = MessageListViewModel.DEFAULT_MESSAGES_LIMIT, + private val navigateToThreadViaNotification: Boolean = false, ) : ViewModelProvider.Factory { private val factories: Map, () -> ViewModel> = mapOf( @@ -48,7 +52,8 @@ public class MessageListViewModelFactory @JvmOverloads constructor( MessageListViewModel( cid = cid, messageId = messageId, - messageLimit = messageLimit + messageLimit = messageLimit, + navigateToThreadViaNotification = navigateToThreadViaNotification, ) }, MessageComposerViewModel::class.java to { MessageComposerViewModel(MessageComposerController(cid)) }, @@ -67,6 +72,7 @@ public class MessageListViewModelFactory @JvmOverloads constructor( private var cid: String? = null private var messageId: String? = null private var messageLimit: Int = MessageListViewModel.DEFAULT_MESSAGES_LIMIT + private var navigateToThreadViaNotification: Boolean = false /** * Sets the channel id in the format messaging:123. @@ -89,6 +95,15 @@ public class MessageListViewModelFactory @JvmOverloads constructor( this.messageLimit = messageLimit } + /** + * Changes the setting dictating whether the SDK should navigate to threads when + * a thread message push notification hsa been clicked. + */ + public fun navigateToThreadViaNotification(navigateToThreadViaNotification: Boolean): Builder = + apply { + this.navigateToThreadViaNotification = navigateToThreadViaNotification + } + /** * Builds [MessageListViewModelFactory] instance. */ @@ -96,7 +111,8 @@ public class MessageListViewModelFactory @JvmOverloads constructor( return MessageListViewModelFactory( cid = cid ?: error("Channel cid should not be null"), messageId = messageId, - messageLimit = messageLimit + messageLimit = messageLimit, + navigateToThreadViaNotification = navigateToThreadViaNotification, ) } }