From 91578c91a2f46a26e41af128a957bd1ec543731f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BBerko?= Date: Fri, 15 Nov 2024 14:48:59 +0100 Subject: [PATCH] feat: conversation favorites [WPB-11637] (#3634) --- .../di/accountScoped/ConversationModule.kt | 10 +++ .../GetConversationsFromSearchUseCase.kt | 58 +++++++++++++----- .../ConversationListViewModel.kt | 21 ++----- .../GetConversationsFromSearchUseCaseTest.kt | 61 ++++++++++++++++++- .../ConversationListViewModelTest.kt | 3 +- kalium | 2 +- 6 files changed, 121 insertions(+), 34 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt index 5c6c05deed7..baf45e7b76b 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt @@ -317,4 +317,14 @@ class ConversationModule { @Provides fun provideGetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase(conversationScope: ConversationScope) = conversationScope.getPaginatedFlowOfConversationDetailsWithEventsBySearchQuery + + @ViewModelScoped + @Provides + fun provideObserveConversationsFromFolderUseCase(conversationScope: ConversationScope) = + conversationScope.observeConversationsFromFolder + + @ViewModelScoped + @Provides + fun provideGetFavoriteFolderUseCase(conversationScope: ConversationScope) = + conversationScope.getFavoriteFolder } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCase.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCase.kt index 71bf72b4478..c9663f89cfd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCase.kt @@ -18,6 +18,8 @@ package com.wire.android.ui.home.conversations.usecase +import androidx.paging.LoadState +import androidx.paging.LoadStates import androidx.paging.PagingConfig import androidx.paging.PagingData import androidx.paging.map @@ -29,13 +31,18 @@ import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.conversation.ConversationFilter import com.wire.kalium.logic.data.conversation.ConversationQueryConfig import com.wire.kalium.logic.feature.conversation.GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase +import com.wire.kalium.logic.feature.conversation.folder.GetFavoriteFolderUseCase +import com.wire.kalium.logic.feature.conversation.folder.ObserveConversationsFromFolderUseCase import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import javax.inject.Inject class GetConversationsFromSearchUseCase @Inject constructor( private val useCase: GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase, + private val getFavoriteFolderUseCase: GetFavoriteFolderUseCase, + private val observeConversationsFromFromFolder: ObserveConversationsFromFolderUseCase, private val wireSessionImageLoader: WireSessionImageLoader, private val userTypeMapper: UserTypeMapper, private val dispatchers: DispatcherProvider, @@ -53,21 +60,44 @@ class GetConversationsFromSearchUseCase @Inject constructor( initialLoadSize = INITIAL_LOAD_SIZE, enablePlaceholders = true, ) - return useCase( - queryConfig = ConversationQueryConfig( - searchQuery = searchQuery, - fromArchive = fromArchive, - newActivitiesOnTop = newActivitiesOnTop, - onlyInteractionEnabled = onlyInteractionEnabled, - conversationFilter = conversationFilter, - ), - pagingConfig = pagingConfig, - startingOffset = 0L, - ).map { pagingData -> - pagingData.map { - it.toConversationItem(wireSessionImageLoader, userTypeMapper, searchQuery) + return when (conversationFilter) { + ConversationFilter.ALL, + ConversationFilter.GROUPS, + ConversationFilter.ONE_ON_ONE -> useCase( + queryConfig = ConversationQueryConfig( + searchQuery = searchQuery, + fromArchive = fromArchive, + newActivitiesOnTop = newActivitiesOnTop, + onlyInteractionEnabled = onlyInteractionEnabled, + conversationFilter = conversationFilter, + ), + pagingConfig = pagingConfig, + startingOffset = 0L, + ) + + ConversationFilter.FAVORITES -> { + when (val result = getFavoriteFolderUseCase.invoke()) { + GetFavoriteFolderUseCase.Result.Failure -> flowOf(emptyList()) + is GetFavoriteFolderUseCase.Result.Success -> + observeConversationsFromFromFolder(result.folder.id) + } + .map { + PagingData.from( + it, + sourceLoadStates = LoadStates( + prepend = LoadState.NotLoading(true), + append = LoadState.NotLoading(true), + refresh = LoadState.NotLoading(true), + ) + ) + } } - }.flowOn(dispatchers.io()) + } + .map { pagingData -> + pagingData.map { + it.toConversationItem(wireSessionImageLoader, userTypeMapper, searchQuery) + } + }.flowOn(dispatchers.io()) } private companion object { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt index 0702f0b024f..cbea3a7fcdd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt @@ -218,7 +218,8 @@ class ConversationListViewModelImpl @AssistedInject constructor( .distinctUntilChanged() .flatMapLatest { searchQuery: String -> observeConversationListDetailsWithEvents( - fromArchive = conversationsSource == ConversationsSource.ARCHIVE + fromArchive = conversationsSource == ConversationsSource.ARCHIVE, + conversationFilter = conversationsSource.toFilter() ).map { it.map { conversationDetails -> conversationDetails.toConversationItem( @@ -230,15 +231,11 @@ class ConversationListViewModelImpl @AssistedInject constructor( } } .map { (conversationItems, searchQuery) -> - val filteredConversationItems = filterConversation( - conversationDetails = conversationItems, - filter = conversationsSource.toFilter() - ) if (searchQuery.isEmpty()) { - filteredConversationItems.withFolders(source = conversationsSource).toImmutableMap() + conversationItems.withFolders(source = conversationsSource).toImmutableMap() } else { searchConversation( - conversationDetails = filteredConversationItems, + conversationDetails = conversationItems, searchQuery = searchQuery ).withFolders(source = conversationsSource).toImmutableMap() } @@ -505,13 +502,3 @@ private fun searchConversation(conversationDetails: List, sear is ConversationItem.PrivateConversation -> details.conversationInfo.name.contains(searchQuery, true) } } - -private fun filterConversation(conversationDetails: List, filter: ConversationFilter): List = - conversationDetails.filter { details -> - when (filter) { - ConversationFilter.ALL -> true - ConversationFilter.FAVORITES -> false - ConversationFilter.GROUPS -> details is ConversationItem.GroupConversation - ConversationFilter.ONE_ON_ONE -> details is ConversationItem.PrivateConversation - } - } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCaseTest.kt index 2d7dd96a6ea..e18ded05dfe 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCaseTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/usecase/GetConversationsFromSearchUseCaseTest.kt @@ -26,8 +26,13 @@ import com.wire.android.mapper.UserTypeMapper import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents +import com.wire.kalium.logic.data.conversation.ConversationFilter +import com.wire.kalium.logic.data.conversation.ConversationFolder import com.wire.kalium.logic.data.conversation.ConversationQueryConfig +import com.wire.kalium.logic.data.conversation.FolderType import com.wire.kalium.logic.feature.conversation.GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase +import com.wire.kalium.logic.feature.conversation.folder.GetFavoriteFolderUseCase +import com.wire.kalium.logic.feature.conversation.folder.ObserveConversationsFromFolderUseCase import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify @@ -80,11 +85,48 @@ class GetConversationsFromSearchUseCaseTest { } } + @Test + fun givenFavoritesFilter_whenGettingConversations_thenObserveConversationsFromFolder() = runTest(dispatcherProvider.main()) { + // Given + val favoriteFolderId = "folder_id" + val folderResult = GetFavoriteFolderUseCase.Result.Success( + folder = ConversationFolder(id = favoriteFolderId, name = "", FolderType.FAVORITE) + ) + val conversationsList = listOf( + ConversationDetailsWithEvents(TestConversationDetails.CONVERSATION_ONE_ONE) + ) + + val (arrangement, useCase) = Arrangement() + .withFavoriteFolderResult(folderResult) + .withFolderConversationsResult(conversationsList) + .arrange() + + // When + useCase( + searchQuery = "", + fromArchive = false, + newActivitiesOnTop = false, + onlyInteractionEnabled = false, + conversationFilter = ConversationFilter.FAVORITES + ).asSnapshot() + + // Then + coVerify(exactly = 1) { arrangement.getFavoriteFolderUseCase.invoke() } + coVerify(exactly = 1) { arrangement.observeConversationsFromFolderUseCase.invoke(favoriteFolderId) } + coVerify(exactly = 0) { arrangement.useCase(any(), any(), any()) } + } + inner class Arrangement { @MockK lateinit var useCase: GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase + @MockK + lateinit var getFavoriteFolderUseCase: GetFavoriteFolderUseCase + + @MockK + lateinit var observeConversationsFromFolderUseCase: ObserveConversationsFromFolderUseCase + @MockK lateinit var wireSessionImageLoader: WireSessionImageLoader @@ -110,6 +152,23 @@ class GetConversationsFromSearchUseCaseTest { } returns flowOf(PagingData.from(conversations)) } - fun arrange() = this to GetConversationsFromSearchUseCase(useCase, wireSessionImageLoader, userTypeMapper, dispatcherProvider) + fun withFavoriteFolderResult(result: GetFavoriteFolderUseCase.Result) = apply { + coEvery { getFavoriteFolderUseCase.invoke() } returns result + } + + fun withFolderConversationsResult(conversations: List) = apply { + coEvery { + observeConversationsFromFolderUseCase.invoke(any()) + } returns flowOf(conversations) + } + + fun arrange() = this to GetConversationsFromSearchUseCase( + useCase, + getFavoriteFolderUseCase, + observeConversationsFromFolderUseCase, + wireSessionImageLoader, + userTypeMapper, + dispatcherProvider + ) } } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt index b1806e66a57..d9e1cdb253f 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModelTest.kt @@ -32,6 +32,7 @@ import com.wire.android.ui.home.conversations.usecase.GetConversationsFromSearch import com.wire.android.ui.home.conversationslist.model.ConversationsSource import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents +import com.wire.kalium.logic.data.conversation.ConversationFilter import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.UserId @@ -216,7 +217,7 @@ class ConversationListViewModelTest { } returns flowOf( PagingData.from(listOf(TestConversationItem.CONNECTION, TestConversationItem.PRIVATE, TestConversationItem.GROUP)) ) - coEvery { observeConversationListDetailsWithEventsUseCase.invoke(false) } returns flowOf( + coEvery { observeConversationListDetailsWithEventsUseCase.invoke(false, ConversationFilter.ALL) } returns flowOf( listOf( TestConversationDetails.CONNECTION, TestConversationDetails.CONVERSATION_ONE_ONE, diff --git a/kalium b/kalium index f69841cbc9f..d140d791a30 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit f69841cbc9faee3dc4fd110e4fc75a5404e3b21c +Subproject commit d140d791a308632e568b0ee3f8685644bad90424