diff --git a/data/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq b/data/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq index 659911536d0b..7db2b791dc0e 100644 --- a/data/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq +++ b/data/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Messages.sq @@ -440,6 +440,9 @@ SELECT * FROM MessageDetailsView WHERE id = ? AND conversationId = ?; countByConversationIdAndVisibility: SELECT count(*) FROM Message WHERE conversation_id = ? AND visibility IN ?; +selectOldestVisibleMessageTimestampByConversationId: +SELECT MIN(creation_date) FROM Message WHERE conversation_id = ? AND visibility = "VISIBLE"; + selectByConversationIdAndVisibility: SELECT * FROM MessageDetailsView WHERE conversationId = :conversationId AND visibility IN :visibility ORDER BY date DESC LIMIT :limit OFFSET :offset; diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt index 074483845fe8..445bcf97398a 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAO.kt @@ -74,6 +74,7 @@ interface MessageDAO { suspend fun updateMessagesStatus(status: MessageEntity.Status, id: List, conversationId: QualifiedIDEntity) suspend fun getMessageById(id: String, conversationId: QualifiedIDEntity): MessageEntity? suspend fun observeMessageById(id: String, conversationId: QualifiedIDEntity): Flow + suspend fun getOldestVisibleMessageTimestampByConversationId(conversationId: ConversationIDEntity): Long? suspend fun getMessagesByConversationAndVisibility( conversationId: QualifiedIDEntity, limit: Int, diff --git a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt index a266be2b4304..6f7a080c678d 100644 --- a/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt +++ b/data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/message/MessageDAOImpl.kt @@ -55,7 +55,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import kotlinx.datetime.Instant -@Suppress("TooManyFunctions", "LongParameterList") +@Suppress("TooManyFunctions", "LongParameterList", "LargeClass") internal class MessageDAOImpl internal constructor( private val queries: MessagesQueries, private val attachmentsQueries: MessageAttachmentsQueries, @@ -289,6 +289,14 @@ internal class MessageDAOImpl internal constructor( .distinctUntilChanged() .flowOn(readDispatcher.value) + override suspend fun getOldestVisibleMessageTimestampByConversationId( + conversationId: ConversationIDEntity + ): Long? = withContext(readDispatcher.value) { + queries.selectOldestVisibleMessageTimestampByConversationId(conversationId) + .executeAsOneOrNull() + ?.MIN?.toEpochMilliseconds() + } + override suspend fun getImageMessageAssets( conversationId: QualifiedIDEntity, mimeTypes: Set, diff --git a/domain/nomaddevice/src/commonMain/kotlin/com/wire/kalium/nomaddevice/NomadAllMessagesMapper.kt b/domain/nomaddevice/src/commonMain/kotlin/com/wire/kalium/nomaddevice/NomadAllMessagesMapper.kt index 3909f0398b1c..d8d1d0d1a057 100644 --- a/domain/nomaddevice/src/commonMain/kotlin/com/wire/kalium/nomaddevice/NomadAllMessagesMapper.kt +++ b/domain/nomaddevice/src/commonMain/kotlin/com/wire/kalium/nomaddevice/NomadAllMessagesMapper.kt @@ -37,15 +37,15 @@ import com.wire.kalium.protobuf.nomaddevice.NomadDeviceQualifiedId import kotlinx.datetime.Instant import kotlin.io.encoding.Base64 -internal data class NomadMappedMessages( +public data class NomadMappedMessages( val totalMessages: Int, val messages: List, val skippedMessages: Int, ) -internal class NomadAllMessagesMapper { +public class NomadAllMessagesMapper { - fun map( + public fun map( response: NomadAllMessagesResponse, ): NomadMappedMessages { var skipped = 0 diff --git a/logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/message/FetchOlderNomadMessagesByConversationUseCase.kt b/logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/message/FetchOlderNomadMessagesByConversationUseCase.kt new file mode 100644 index 000000000000..ca364a3109be --- /dev/null +++ b/logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/message/FetchOlderNomadMessagesByConversationUseCase.kt @@ -0,0 +1,56 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +package com.wire.kalium.logic.feature.message + +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.message.MessageRepository +import com.wire.kalium.util.KaliumDispatcher +import com.wire.kalium.util.KaliumDispatcherImpl +import kotlinx.coroutines.withContext + +public interface FetchOlderNomadMessagesByConversationUseCase { + /** + * Fetches older messages for a given conversation in a remote data source and stores them in the local database. + * This is typically used when the user scrolls up in the message list we want to load more messages from the past. + */ + public suspend operator fun invoke( + conversationId: ConversationId, + pageSize: Int = DEFAULT_PAGE_SIZE, + ) + + private companion object { + const val DEFAULT_PAGE_SIZE = 50 + } +} + +internal class FetchOlderNomadMessagesByConversationUseCaseImpl( + private val dispatcher: KaliumDispatcher = KaliumDispatcherImpl, + private val messageRepository: MessageRepository, +) : FetchOlderNomadMessagesByConversationUseCase { + + override suspend operator fun invoke( + conversationId: ConversationId, + pageSize: Int, + ): Unit = withContext(dispatcher.default) { + messageRepository.extensions.fetchOlderNomadMessagesByConversationId( + conversationId = conversationId, + pageSize = pageSize, + ) + } +} diff --git a/logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/message/MessageScopeExtensions.kt b/logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/message/MessageScopeExtensions.kt index e57e7618a16d..68f63c7065e0 100644 --- a/logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/message/MessageScopeExtensions.kt +++ b/logic/src/androidMain/kotlin/com/wire/kalium/logic/feature/message/MessageScopeExtensions.kt @@ -33,3 +33,6 @@ public val MessageScope.getPaginatedFlowOfAssetMessageByConversationId: GetPagin public val MessageScope.observePaginatedImageAssetMessageByConversationId: ObservePaginatedAssetImageMessages get() = ObservePaginatedAssetImageMessages(dispatcher, messageRepository) + +public val MessageScope.fetchOlderMessagesByConversationId: FetchOlderNomadMessagesByConversationUseCase + get() = FetchOlderNomadMessagesByConversationUseCaseImpl(dispatcher, messageRepository) diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt index a2c61c11ae5f..a1acca0b6178 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepository.kt @@ -48,6 +48,7 @@ import com.wire.kalium.logic.data.id.toDao import com.wire.kalium.logic.data.id.toModel import com.wire.kalium.logic.data.message.linkpreview.LinkPreviewMapper import com.wire.kalium.logic.data.message.mention.MessageMentionMapper +import com.wire.kalium.logic.data.message.paging.NomadMessagePagingCoordinator import com.wire.kalium.logic.data.notification.LocalNotification import com.wire.kalium.logic.data.notification.LocalNotificationMessageMapper import com.wire.kalium.logic.data.notification.LocalNotificationMessageMapperImpl @@ -330,6 +331,7 @@ internal class MessageDataSource internal constructor( private val messageApi: MessageApi, private val mlsMessageApi: MLSMessageApi, private val messageDAO: MessageDAO, + nomadMessagePagingCoordinator: NomadMessagePagingCoordinator? = null, private val sendMessageFailureMapper: SendMessageFailureMapper = MapperProvider.sendMessageFailureMapper(), private val messageMapper: MessageMapper = MapperProvider.messageMapper(selfUserId), private val linkPreviewMapper: LinkPreviewMapper = MapperProvider.linkPreviewMapper(), @@ -339,7 +341,11 @@ internal class MessageDataSource internal constructor( private val notificationMapper: LocalNotificationMessageMapper = LocalNotificationMessageMapperImpl() ) : MessageRepository { - override val extensions: MessageRepositoryExtensions = MessageRepositoryExtensionsImpl(messageDAO, messageMapper) + override val extensions: MessageRepositoryExtensions = MessageRepositoryExtensionsImpl( + messageDAO, + messageMapper, + nomadMessagePagingCoordinator, + ) override suspend fun getMessagesByConversationIdAndVisibility( conversationId: ConversationId, diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepositoryExtensions.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepositoryExtensions.kt index 496af3831d89..013c09db7a4d 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepositoryExtensions.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/MessageRepositoryExtensions.kt @@ -25,12 +25,14 @@ import com.wire.kalium.logic.data.asset.AssetMessage import com.wire.kalium.logic.data.asset.SUPPORTED_IMAGE_ASSET_MIME_TYPES import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.toDao +import com.wire.kalium.logic.data.message.paging.NomadMessagePagingCoordinator import com.wire.kalium.persistence.dao.asset.AssetMessageEntity import com.wire.kalium.persistence.dao.message.KaliumPager import com.wire.kalium.persistence.dao.message.MessageDAO import com.wire.kalium.persistence.dao.message.MessageEntity import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import kotlinx.datetime.Clock internal interface MessageRepositoryExtensions { suspend fun getPaginatedMessagesByConversationIdAndVisibility( @@ -58,11 +60,17 @@ internal interface MessageRepositoryExtensions { pagingConfig: PagingConfig, startingOffset: Long ): Flow> + + suspend fun fetchOlderNomadMessagesByConversationId( + conversationId: ConversationId, + pageSize: Int, + ) } internal class MessageRepositoryExtensionsImpl internal constructor( private val messageDAO: MessageDAO, private val messageMapper: MessageMapper, + private val nomadMessagePagingCoordinator: NomadMessagePagingCoordinator? = null, ) : MessageRepositoryExtensions { override suspend fun getPaginatedMessagesByConversationIdAndVisibility( @@ -75,7 +83,7 @@ internal class MessageRepositoryExtensionsImpl internal constructor( conversationId.toDao(), visibility.map { it.toEntityVisibility() }, pagingConfig, - startingOffset + startingOffset, ) return pager.pagingDataFlow.map { @@ -134,4 +142,20 @@ internal class MessageRepositoryExtensionsImpl internal constructor( it.map { messageEntity -> messageMapper.fromAssetEntityToAssetMessage(messageEntity) } } } + + override suspend fun fetchOlderNomadMessagesByConversationId( + conversationId: ConversationId, + pageSize: Int, + ) { + val coordinator = nomadMessagePagingCoordinator ?: return + val beforeTimestampMs = messageDAO.getOldestVisibleMessageTimestampByConversationId( + conversationId.toDao() + ) ?: Clock.System.now().toEpochMilliseconds() + coordinator.fetchOlderMessagesIfNeeded( + conversationId = conversationId, + pageSize = pageSize, + beforeTimestampMs = beforeTimestampMs, + onInvalidate = {} + ) + } } diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/paging/NomadMessagePagingCoordinator.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/paging/NomadMessagePagingCoordinator.kt new file mode 100644 index 000000000000..3a170429921c --- /dev/null +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/data/message/paging/NomadMessagePagingCoordinator.kt @@ -0,0 +1,220 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +package com.wire.kalium.logic.data.message.paging + +import co.touchlab.stately.collections.ConcurrentMutableMap +import com.wire.kalium.common.error.wrapApiRequest +import com.wire.kalium.common.error.wrapStorageRequest +import com.wire.kalium.common.functional.Either +import com.wire.kalium.common.logger.kaliumLogger +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.network.api.authenticated.nomaddevice.NomadAllMessagesResponse +import com.wire.kalium.network.api.authenticated.nomaddevice.NomadBatchRestoreRequest +import com.wire.kalium.network.api.authenticated.nomaddevice.NomadBatchRestoreResponse +import com.wire.kalium.network.api.authenticated.nomaddevice.NomadConversationWithMessages +import com.wire.kalium.network.api.base.authenticated.nomaddevice.NomadDeviceSyncApi +import com.wire.kalium.nomaddevice.NomadAllMessagesMapper +import com.wire.kalium.persistence.dao.backup.NomadMessageToInsert +import com.wire.kalium.persistence.dao.backup.NomadMessagesDAO +import io.mockative.Mockable +import kotlinx.datetime.Clock + +@Mockable +internal interface NomadMessagePagingCoordinator { + /** + * Fetches older messages for the given conversation if needed, based on the current paging state. + * @param conversationId The ID of the conversation to fetch messages for. + * @param pageSize The number of messages to fetch in one batch. + * @param beforeTimestampMs Optional timestamp to fetch messages before. + * @param onInvalidate Callback to be invoked if the paging state is updated and the UI pager consumer needs to be invalidated. + */ + suspend fun fetchOlderMessagesIfNeeded( + conversationId: ConversationId, + pageSize: Int, + beforeTimestampMs: Long?, + onInvalidate: () -> Unit + ) +} + +internal class NomadMessagePagingCoordinatorImpl( + private val selfUserId: UserId, + private val isNomadEnabled: () -> Boolean, + private val nomadDeviceSyncApi: NomadDeviceSyncApi, + private val nomadMessagesDAO: NomadMessagesDAO, + private val mapper: NomadAllMessagesMapper = NomadAllMessagesMapper(), + private val clock: Clock = Clock.System, +) : NomadMessagePagingCoordinator { + + private data class State( + val nextCursor: Long = 0L, + val nextTimestamp: Long, + val hasMore: Boolean = true, + val isFetching: Boolean = false, + ) + + private val stateByConversation = ConcurrentMutableMap() + + override suspend fun fetchOlderMessagesIfNeeded( + conversationId: ConversationId, + pageSize: Int, + beforeTimestampMs: Long?, + onInvalidate: () -> Unit + ) { + kaliumLogger.d("[$TAG] Nomad paging boundary reached for conversation '${conversationId.toLogString()}'") + + val currentState = stateByConversation.block { map -> + val existing = map[conversationId] ?: State( + nextTimestamp = beforeTimestampMs ?: clock.now().toEpochMilliseconds() + ) + if (!existing.hasMore || existing.isFetching) return@block null + if (!isNomadEnabled()) { + map[conversationId] = existing.copy(hasMore = false) + kaliumLogger.d("[$TAG] Nomad paging disabled for conversation '${conversationId.toLogString()}'") + return@block null + } + + val next = existing.copy(isFetching = true) + map[conversationId] = next + kaliumLogger.d( + "[$TAG] Nomad paging fetching conversation '${conversationId.toLogString()}' " + + "with cursor=${next.nextCursor} ts=${next.nextTimestamp}" + ) + next + } ?: return + + val responseResult = wrapApiRequest { + nomadDeviceSyncApi.restoreMessagesBatch( + NomadBatchRestoreRequest( + conversationIds = listOf(conversationId.value), + limit = pageSize, + beforeTimestamp = currentState.nextTimestamp, + nextCursor = currentState.nextCursor, + ) + ) + } + + when (responseResult) { + is Either.Left -> { + stateByConversation.block { map -> + val state = map[conversationId] ?: currentState + map.put(conversationId, state.copy(isFetching = false)) + } + kaliumLogger.w( + "[$TAG] Nomad batch restore failed for '${selfUserId.toLogString()}': ${responseResult.value}" + ) + } + + is Either.Right -> storeAndUpdateState( + conversationId = conversationId, + response = responseResult.value, + currentState = currentState, + pageSize = pageSize, + onInvalidate = onInvalidate, + ) + } + } + + private suspend fun storeAndUpdateState( + conversationId: ConversationId, + response: NomadBatchRestoreResponse, + currentState: State, + pageSize: Int, + onInvalidate: () -> Unit, + ) { + val mapped = mapper.map(response.toAllMessagesResponse()) + val storeResult = wrapStorageRequest { + nomadMessagesDAO.storeMessages( + messages = mapped.messages.filterByConversationId(conversationId), + batchSize = pageSize, + ) + } + + var shouldInvalidate = false + stateByConversation.block { map -> + val state = map[conversationId] ?: currentState + when (storeResult) { + is Either.Left -> { + map[conversationId] = state.copy(isFetching = false) + kaliumLogger.w( + "[$TAG] Nomad batch restore storage failed for '${selfUserId.toLogString()}': ${storeResult.value}" + ) + } + + is Either.Right -> { + val nextState = response.nextStateFor(conversationId, state) + map[conversationId] = nextState + kaliumLogger.d( + "[$TAG] Nomad paging stored ${storeResult.value.storedMessages} messages for conversation " + + "'${conversationId.toLogString()}', hasMore=${nextState.hasMore}, " + + "nextCursor=${nextState.nextCursor}, nextTimestamp=${nextState.nextTimestamp}" + ) + if (storeResult.value.storedMessages > 0) { + shouldInvalidate = true + } + } + } + } + if (shouldInvalidate) { + onInvalidate() + } + } + + private fun NomadBatchRestoreResponse.toAllMessagesResponse(): NomadAllMessagesResponse = + NomadAllMessagesResponse( + conversations = conversations.map { item -> + NomadConversationWithMessages( + conversation = item.conversation, + messages = item.messages, + ) + }, + hasMore = false, + nextCursor = null, + nextTimestamp = null, + ) + + private fun NomadBatchRestoreResponse.nextStateFor( + conversationId: ConversationId, + current: State, + ): State { + val entry = conversations.firstOrNull { + it.conversation.id == conversationId.value && it.conversation.domain == conversationId.domain + } + if (entry == null) { + return current.copy(isFetching = false, hasMore = false) + } + + return current.copy( + nextCursor = entry.nextCursor, + nextTimestamp = entry.nextTimestamp, + hasMore = entry.hasMore, + isFetching = false, + ) + } + + private fun List.filterByConversationId( + conversationId: ConversationId + ): List = filter { message -> + message.conversationId.value == conversationId.value && message.conversationId.domain == conversationId.domain + } + + companion object { + const val TAG = "NomadMessagePagingCoordinator" + } +} diff --git a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt index d73776121408..c9df72c11513 100644 --- a/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt +++ b/logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt @@ -161,6 +161,8 @@ import com.wire.kalium.logic.data.message.ProtoContentMapperImpl import com.wire.kalium.logic.data.message.SystemMessageInserterImpl import com.wire.kalium.logic.data.message.draft.MessageDraftDataSource import com.wire.kalium.logic.data.message.draft.MessageDraftRepository +import com.wire.kalium.logic.data.message.paging.NomadMessagePagingCoordinator +import com.wire.kalium.logic.data.message.paging.NomadMessagePagingCoordinatorImpl import com.wire.kalium.logic.data.message.reaction.ReactionRepositoryImpl import com.wire.kalium.logic.data.message.receipt.ReceiptRepositoryImpl import com.wire.kalium.logic.data.mls.ConversationProtocolGetterImpl @@ -537,8 +539,8 @@ import com.wire.kalium.messaging.hooks.ConversationClearEventData import com.wire.kalium.messaging.hooks.ConversationDeleteEventData import com.wire.kalium.messaging.hooks.CryptoStateChangeHookNotifier import com.wire.kalium.messaging.hooks.MessageDeleteEventData -import com.wire.kalium.messaging.hooks.PersistenceEventHookNotifier import com.wire.kalium.messaging.hooks.PersistedMessageData +import com.wire.kalium.messaging.hooks.PersistenceEventHookNotifier import com.wire.kalium.messaging.hooks.ReactionEventData import com.wire.kalium.messaging.hooks.ReadReceiptEventData import com.wire.kalium.network.NetworkStateObserver @@ -958,9 +960,22 @@ public class UserSessionScope internal constructor( messageApi = authenticatedNetworkContainer.messageApi, mlsMessageApi = authenticatedNetworkContainer.mlsMessageApi, messageDAO = userStorage.database.messageDAO, - selfUserId = userId + selfUserId = userId, + nomadMessagePagingCoordinator = nomadMessagePagingCoordinator, ) + private val nomadMessagePagingCoordinator: NomadMessagePagingCoordinator? + get() = if (nomadServiceUrl.isNullOrBlank()) { + null + } else { + NomadMessagePagingCoordinatorImpl( + selfUserId = userId, + isNomadEnabled = { nomadServiceUrl.isNotBlank() }, + nomadDeviceSyncApi = authenticatedNetworkContainer.nomadDeviceSyncApi, + nomadMessagesDAO = userStorage.database.nomadMessagesDAO, + ) + } + private val messageMetadataRepository: MessageMetadataRepository get() = MessageMetadataSource(messageMetaDataDAO = userStorage.database.messageMetaDataDAO) diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/fakes/FakeMessageRepositoryExtensions.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/fakes/FakeMessageRepositoryExtensions.kt index 825a969536af..6f45b77e0e3a 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/fakes/FakeMessageRepositoryExtensions.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/fakes/FakeMessageRepositoryExtensions.kt @@ -52,4 +52,11 @@ internal open class FakeMessageRepositoryExtensions : MessageRepositoryExtension pagingConfig: PagingConfig, startingOffset: Long ): Flow> = emptyFlow() + + override suspend fun fetchOlderNomadMessagesByConversationId( + conversationId: ConversationId, + pageSize: Int, + ) { + // no-op for tests + } } diff --git a/logic/src/commonTest/kotlin/com/wire/kalium/logic/message/MessageRepositoryExtensionsTest.kt b/logic/src/commonTest/kotlin/com/wire/kalium/logic/message/MessageRepositoryExtensionsTest.kt index 1543c63511db..aa324aadb717 100644 --- a/logic/src/commonTest/kotlin/com/wire/kalium/logic/message/MessageRepositoryExtensionsTest.kt +++ b/logic/src/commonTest/kotlin/com/wire/kalium/logic/message/MessageRepositoryExtensionsTest.kt @@ -26,6 +26,7 @@ import com.wire.kalium.logic.data.message.Message import com.wire.kalium.logic.data.message.MessageMapper import com.wire.kalium.logic.data.message.MessageRepositoryExtensions import com.wire.kalium.logic.data.message.MessageRepositoryExtensionsImpl +import com.wire.kalium.logic.data.message.paging.NomadMessagePagingCoordinator import com.wire.kalium.logic.framework.TestConversation import com.wire.kalium.logic.framework.TestMessage import com.wire.kalium.persistence.dao.message.KaliumPager @@ -87,6 +88,7 @@ class MessageRepositoryExtensionsTest { val messageDaoExtensions: MessageExtensions = mock(MessageExtensions::class) private val messageDAO: MessageDAO = mock(MessageDAO::class) private val messageMapper: MessageMapper = mock(MessageMapper::class) + private val pagingCoordinator: NomadMessagePagingCoordinator = mock(NomadMessagePagingCoordinator::class) init { @@ -108,7 +110,8 @@ class MessageRepositoryExtensionsTest { private val messageRepositoryExtensions: MessageRepositoryExtensions by lazy { MessageRepositoryExtensionsImpl( messageDAO, - messageMapper + messageMapper, + pagingCoordinator, ) }