Skip to content

Commit

Permalink
feat: conversation list pagination, pt2 - pager and use case [WPB-943…
Browse files Browse the repository at this point in the history
…3] (#3059)

* feat: conversation list pagination, pt1 - queries [WPB-9433]

* feat: conversation list pagination, pt2 - pagers [WPB-9433]

* fixed detekt issues

* fixed detekt issues

* fixed tests

* added test for paginated use case
  • Loading branch information
saleniuk authored Oct 15, 2024
1 parent a47607d commit 5e31dec
Show file tree
Hide file tree
Showing 11 changed files with 646 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Wire
* Copyright (C) 2024 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.conversation

val ConversationScope.getPaginatedFlowOfConversationDetailsWithEventsBySearchQuery
get() = GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase(dispatcher, conversationRepository)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Wire
* Copyright (C) 2024 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.conversation

import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.conversation.ConversationQueryConfig
import com.wire.kalium.util.KaliumDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn

/**
* This use case will observe and return a flow of paginated searched conversation details with last message and unread events counts.
* @see PagingData
* @see ConversationDetailsWithEvents
*/
class GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase internal constructor(
private val dispatcher: KaliumDispatcher,
private val conversationRepository: ConversationRepository,
) {
suspend operator fun invoke(
queryConfig: ConversationQueryConfig,
pagingConfig: PagingConfig,
startingOffset: Long,
): Flow<PagingData<ConversationDetailsWithEvents>> = conversationRepository.extensions
.getPaginatedConversationDetailsWithEventsBySearchQuery(queryConfig, pagingConfig, startingOffset).flowOn(dispatcher.io)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Wire
* Copyright (C) 2024 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.conversation

import androidx.paging.PagingData
import app.cash.paging.PagingConfig
import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents
import com.wire.kalium.logic.data.conversation.ConversationQueryConfig
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.conversation.ConversationRepositoryExtensions
import com.wire.kalium.logic.test_util.TestKaliumDispatcher
import io.mockative.Mock
import io.mockative.any
import io.mockative.coEvery
import io.mockative.coVerify
import io.mockative.every
import io.mockative.mock
import io.mockative.once
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test

class GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCaseTest {
private val dispatcher = TestKaliumDispatcher

@Test
fun givenSearchQuery_whenGettingPaginatedList_thenCallUseCaseWithProperParams() = runTest(dispatcher.default) {
// Given
val (arrangement, useCase) = Arrangement().withPaginatedConversationResult(emptyFlow()).arrange()
with(arrangement) {
// When
useCase(queryConfig = queryConfig, pagingConfig = pagingConfig, startingOffset = startingOffset)
// Then
coVerify {
conversationRepository.extensions
.getPaginatedConversationDetailsWithEventsBySearchQuery(queryConfig, pagingConfig, startingOffset)
}.wasInvoked(exactly = once)
}
}

inner class Arrangement {
@Mock
val conversationRepository = mock(ConversationRepository::class)

@Mock
val conversationRepositoryExtensions = mock(ConversationRepositoryExtensions::class)

val queryConfig = ConversationQueryConfig("search")
val pagingConfig = PagingConfig(20)
val startingOffset = 0L

init {
every {
conversationRepository.extensions
}.returns(conversationRepositoryExtensions)
}

suspend fun withPaginatedConversationResult(result: Flow<PagingData<ConversationDetailsWithEvents>>) = apply {
coEvery {
conversationRepositoryExtensions.getPaginatedConversationDetailsWithEventsBySearchQuery(any(), any(), any())
}.returns(result)
}

fun arrange() = this to GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase(dispatcher, conversationRepository)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ import kotlinx.datetime.Instant

@Suppress("TooManyFunctions")
interface ConversationRepository {
val extensions: ConversationRepositoryExtensions

// region Get/Observe by id

suspend fun observeConversationById(conversationId: ConversationId): Flow<Either<StorageFailure, Conversation>>
Expand Down Expand Up @@ -327,6 +329,8 @@ internal class ConversationDataSource internal constructor(
private val messageMapper: MessageMapper = MapperProvider.messageMapper(selfUserId),
private val receiptModeMapper: ReceiptModeMapper = MapperProvider.receiptModeMapper()
) : ConversationRepository {
override val extensions: ConversationRepositoryExtensions =
ConversationRepositoryExtensionsImpl(conversationDAO, conversationMapper, messageMapper)

// region Get/Observe by id

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Wire
* Copyright (C) 2024 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.conversation

import app.cash.paging.PagingConfig
import app.cash.paging.PagingData
import app.cash.paging.map
import com.wire.kalium.logic.data.message.MessageMapper
import com.wire.kalium.logic.data.message.UnreadEventType
import com.wire.kalium.persistence.dao.conversation.ConversationDAO
import com.wire.kalium.persistence.dao.conversation.ConversationDetailsWithEventsEntity
import com.wire.kalium.persistence.dao.conversation.ConversationExtensions.QueryConfig
import com.wire.kalium.persistence.dao.message.KaliumPager
import com.wire.kalium.persistence.dao.unread.UnreadEventTypeEntity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

interface ConversationRepositoryExtensions {
suspend fun getPaginatedConversationDetailsWithEventsBySearchQuery(
queryConfig: ConversationQueryConfig,
pagingConfig: PagingConfig,
startingOffset: Long,
): Flow<PagingData<ConversationDetailsWithEvents>>
}

class ConversationRepositoryExtensionsImpl internal constructor(
private val conversationDAO: ConversationDAO,
private val conversationMapper: ConversationMapper,
private val messageMapper: MessageMapper,
) : ConversationRepositoryExtensions {
override suspend fun getPaginatedConversationDetailsWithEventsBySearchQuery(
queryConfig: ConversationQueryConfig,
pagingConfig: PagingConfig,
startingOffset: Long
): Flow<PagingData<ConversationDetailsWithEvents>> {
val pager: KaliumPager<ConversationDetailsWithEventsEntity> = with(queryConfig) {
conversationDAO.platformExtensions.getPagerForConversationDetailsWithEventsSearch(
queryConfig = QueryConfig(searchQuery, fromArchive, onlyInteractionEnabled, newActivitiesOnTop),
pagingConfig = pagingConfig
)
}

return pager.pagingDataFlow.map {
it.map {
ConversationDetailsWithEvents(
conversationDetails = conversationMapper.fromDaoModelToDetails(it.conversationViewEntity),
lastMessage = when {
it.messageDraft != null -> messageMapper.fromDraftToMessagePreview(it.messageDraft!!)
it.lastMessage != null -> messageMapper.fromEntityToMessagePreview(it.lastMessage!!)
else -> null
},
unreadEventCount = it.unreadEvents.unreadEvents.mapKeys {
when (it.key) {
UnreadEventTypeEntity.KNOCK -> UnreadEventType.KNOCK
UnreadEventTypeEntity.MISSED_CALL -> UnreadEventType.MISSED_CALL
UnreadEventTypeEntity.MENTION -> UnreadEventType.MENTION
UnreadEventTypeEntity.REPLY -> UnreadEventType.REPLY
UnreadEventTypeEntity.MESSAGE -> UnreadEventType.MESSAGE
}
},
hasNewActivitiesToShow = it.hasNewActivitiesToShow,
)
}
}
}
}

data class ConversationQueryConfig(
val searchQuery: String = "",
val fromArchive: Boolean = false,
val onlyInteractionEnabled: Boolean = false,
val newActivitiesOnTop: Boolean = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ import com.wire.kalium.logic.feature.team.DeleteTeamConversationUseCaseImpl
import com.wire.kalium.logic.sync.SyncManager
import com.wire.kalium.logic.sync.receiver.conversation.RenamedConversationEventHandler
import com.wire.kalium.logic.sync.receiver.handler.CodeUpdateHandlerImpl
import com.wire.kalium.util.KaliumDispatcher
import com.wire.kalium.util.KaliumDispatcherImpl
import kotlinx.coroutines.CoroutineScope

@Suppress("LongParameterList")
class ConversationScope internal constructor(
private val conversationRepository: ConversationRepository,
internal val conversationRepository: ConversationRepository,
private val conversationGroupRepository: ConversationGroupRepository,
private val connectionRepository: ConnectionRepository,
private val userRepository: UserRepository,
Expand All @@ -101,7 +102,8 @@ class ConversationScope internal constructor(
private val scope: CoroutineScope,
private val kaliumLogger: KaliumLogger,
private val refreshUsersWithoutMetadata: RefreshUsersWithoutMetadataUseCase,
private val serverConfigLinks: ServerConfig.Links
private val serverConfigLinks: ServerConfig.Links,
internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl,
) {

val getConversations: GetConversationsUseCase
Expand Down
Loading

0 comments on commit 5e31dec

Please sign in to comment.