diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt index 1427966dd85..d05fd478ccd 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/CallsModule.kt @@ -37,6 +37,7 @@ import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnable import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallWithSortedParticipantsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveInCallReactionsUseCase +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveLastActiveCallWithSortedParticipantsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveOutgoingCallUseCase @@ -83,6 +84,10 @@ class CallsModule { fun provideObserveOngoingCallsUseCase(callsScope: CallsScope): ObserveOngoingCallsUseCase = callsScope.observeOngoingCalls + @Provides + fun provideObserveJoinableCallsUseCase(callsScope: CallsScope): ObserveJoinableCallsUseCase = + callsScope.observeJoinableCalls + @Provides fun provideObserveEstablishedCallWithSortedParticipantsUseCase( callsScope: CallsScope diff --git a/app/src/main/kotlin/com/wire/android/mapper/ConversationMapper.kt b/app/src/main/kotlin/com/wire/android/mapper/ConversationMapper.kt index 067b649e6ab..cb94792be07 100644 --- a/app/src/main/kotlin/com/wire/android/mapper/ConversationMapper.kt +++ b/app/src/main/kotlin/com/wire/android/mapper/ConversationMapper.kt @@ -30,6 +30,7 @@ import com.wire.android.ui.home.conversationslist.model.ConversationItem.Group.C import com.wire.android.ui.home.conversationslist.model.ConversationItem.Group.Regular import com.wire.android.ui.home.conversationslist.model.ConversationItem.PrivateConversation import com.wire.android.util.ui.UiTextResolver +import com.wire.kalium.logic.data.call.Call import com.wire.kalium.logic.data.conversation.ConversationDetails import com.wire.kalium.logic.data.conversation.ConversationDetails.Connection import com.wire.kalium.logic.data.conversation.ConversationDetails.Group @@ -38,6 +39,7 @@ import com.wire.kalium.logic.data.conversation.ConversationDetails.Self import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.conversation.UnreadEventCount +import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.TeamId import com.wire.kalium.logic.data.message.UnreadEventType import com.wire.kalium.logic.data.user.ConnectionState @@ -47,9 +49,11 @@ import com.wire.kalium.logic.data.user.UserAvailabilityStatus fun ConversationDetailsWithEvents.toConversationItem( userTypeMapper: UserTypeMapper, uiTextResolver: UiTextResolver, - selfUserTeamId: TeamId? + selfUserTeamId: TeamId?, + joinableCallsByConversationId: Map = emptyMap() ): ConversationItem = when (val conversationDetails = this.conversationDetails) { is Group.Regular -> { + val hasJoinableCall = conversationDetails.hasJoinableCall(joinableCallsByConversationId) Regular( groupName = conversationDetails.conversation.name.orEmpty(), conversationId = conversationDetails.conversation.id, @@ -60,7 +64,7 @@ fun ConversationDetailsWithEvents.toConversationItem( mutedStatus = conversationDetails.conversation.mutedStatus, unreadEventCount = unreadEventCount ), - hasOnGoingCall = conversationDetails.hasOngoingCall && conversationDetails.isSelfUserMember, + hasOnGoingCall = hasJoinableCall, isFromTheSameTeam = conversationDetails.conversation.teamId == selfUserTeamId, isSelfUserMember = conversationDetails.isSelfUserMember, teamId = conversationDetails.conversation.teamId, @@ -68,13 +72,14 @@ fun ConversationDetailsWithEvents.toConversationItem( isArchived = conversationDetails.conversation.archived, mlsVerificationStatus = conversationDetails.conversation.mlsVerificationStatus, proteusVerificationStatus = conversationDetails.conversation.proteusVerificationStatus, - hasNewActivitiesToShow = hasNewActivitiesToShow, + hasNewActivitiesToShow = hasNewActivitiesToShow || hasJoinableCall, isFavorite = conversationDetails.isFavorite, folder = conversationDetails.folder ) } is Group.Channel -> { + val hasJoinableCall = conversationDetails.hasJoinableCall(joinableCallsByConversationId) Channel( groupName = conversationDetails.conversation.name.orEmpty(), conversationId = conversationDetails.conversation.id, @@ -85,7 +90,7 @@ fun ConversationDetailsWithEvents.toConversationItem( mutedStatus = conversationDetails.conversation.mutedStatus, unreadEventCount = unreadEventCount ), - hasOnGoingCall = conversationDetails.hasOngoingCall && conversationDetails.isSelfUserMember, + hasOnGoingCall = hasJoinableCall, isFromTheSameTeam = conversationDetails.conversation.teamId == selfUserTeamId, isSelfUserMember = conversationDetails.isSelfUserMember, teamId = conversationDetails.conversation.teamId, @@ -93,7 +98,7 @@ fun ConversationDetailsWithEvents.toConversationItem( isArchived = conversationDetails.conversation.archived, mlsVerificationStatus = conversationDetails.conversation.mlsVerificationStatus, proteusVerificationStatus = conversationDetails.conversation.proteusVerificationStatus, - hasNewActivitiesToShow = hasNewActivitiesToShow, + hasNewActivitiesToShow = hasNewActivitiesToShow || hasJoinableCall, isFavorite = conversationDetails.isFavorite, folder = conversationDetails.folder, isPrivate = conversationDetails.access == Group.Channel.ChannelAccess.PRIVATE @@ -170,6 +175,9 @@ fun ConversationDetailsWithEvents.toConversationItem( } } +private fun Group.hasJoinableCall(joinableCallsByConversationId: Map): Boolean = + joinableCallsByConversationId.containsKey(conversation.id) && isSelfUserMember + private fun parseConnectionEventType(connectionState: ConnectionState) = if (connectionState == ConnectionState.SENT) { BadgeEventType.SentConnectRequest diff --git a/app/src/main/kotlin/com/wire/android/ui/calling/CallingViewModelFactory.kt b/app/src/main/kotlin/com/wire/android/ui/calling/CallingViewModelFactory.kt index e65afc49129..09567bd9b73 100644 --- a/app/src/main/kotlin/com/wire/android/ui/calling/CallingViewModelFactory.kt +++ b/app/src/main/kotlin/com/wire/android/ui/calling/CallingViewModelFactory.kt @@ -57,8 +57,8 @@ import com.wire.kalium.logic.feature.call.usecase.ObserveCallQualityDataUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveInCallReactionsUseCase +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveLastActiveCallWithSortedParticipantsUseCase -import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveOutgoingCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveSpeakerUseCase import com.wire.kalium.logic.feature.call.usecase.RejectCallUseCase @@ -132,7 +132,7 @@ class CallingViewModelFactory @Inject constructor( private val flipToBackCamera: FlipToBackCameraUseCase, private val observeSpeaker: ObserveSpeakerUseCase, private val userTypeMapper: UserTypeMapper, - private val observeOngoingCalls: ObserveOngoingCallsUseCase, + private val observeJoinableCalls: ObserveJoinableCallsUseCase, private val observeParticipantsForConversation: ObserveParticipantsForConversationUseCase, private val observeSyncState: ObserveSyncStateUseCase, private val isConferenceCallingEnabled: IsEligibleToStartCallUseCase, @@ -219,7 +219,7 @@ class CallingViewModelFactory @Inject constructor( fun conversationCallViewModel(savedStateHandle: SavedStateHandle) = ConversationCallViewModel( savedStateHandle = savedStateHandle, currentAccount = currentAccount, - observeOngoingCalls = observeOngoingCalls, + observeJoinableCalls = observeJoinableCalls, observeEstablishedCalls = observeEstablishedCalls, observeParticipantsForConversation = observeParticipantsForConversation, answerCall = answerCall, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModelFactory.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModelFactory.kt index acd1810f619..8d3039a482f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModelFactory.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModelFactory.kt @@ -37,6 +37,7 @@ import com.wire.android.util.ui.UiTextResolver import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.channels.ObserveChannelsCreationPermissionUseCase +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase import com.wire.kalium.logic.feature.client.IsWireCellsEnabledUseCase import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase import com.wire.kalium.logic.feature.conversation.ObserveArchivedUnreadConversationsCountUseCase @@ -77,6 +78,7 @@ class HomeViewModelFactory @Inject constructor( private val disableAppLockUseCase: Lazy, private val getConversationsPaginated: GetConversationsFromSearchUseCase, private val observeConversationListDetailsWithEvents: ObserveConversationListDetailsWithEventsUseCase, + private val observeJoinableCalls: ObserveJoinableCallsUseCase, private val refreshUsersWithoutMetadata: RefreshUsersWithoutMetadataUseCase, private val refreshConversationsWithoutMetadata: RefreshConversationsWithoutMetadataUseCase, private val observeLegalHoldStateForSelfUser: ObserveLegalHoldStateForSelfUserUseCase, @@ -140,6 +142,7 @@ class HomeViewModelFactory @Inject constructor( userTypeMapper = userTypeMapper, getSelfTeamId = getSelfTeamId, uiTextResolver = uiTextResolver, + observeJoinableCalls = observeJoinableCalls, ) fun newConversationViewModel() = NewConversationViewModel( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt index 8b831ddb156..f657ddaa9a4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModel.kt @@ -36,7 +36,7 @@ import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.IsEligibleToStartCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase -import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCase +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase import com.wire.kalium.logic.feature.conversation.ObserveDegradedConversationNotifiedUseCase import com.wire.kalium.logic.feature.conversation.SetUserInformedAboutVerificationUseCase @@ -51,7 +51,7 @@ import kotlinx.coroutines.launch class ConversationCallViewModel( val savedStateHandle: SavedStateHandle, currentAccount: UserId, - private val observeOngoingCalls: ObserveOngoingCallsUseCase, + private val observeJoinableCalls: ObserveJoinableCallsUseCase, private val observeEstablishedCalls: ObserveEstablishedCallsUseCase, private val observeParticipantsForConversation: ObserveParticipantsForConversationUseCase, private val answerCall: AnswerCallUseCase, @@ -107,8 +107,8 @@ class ConversationCallViewModel( } private fun listenOngoingCall() = viewModelScope.launch { - combine(observeOngoingCalls(), observeConversationDetails(conversationId)) { calls, conversationDetailsResult -> - val hasOngoingCall = calls.any { call -> call.conversationId == conversationId } + combine(observeJoinableCalls(), observeConversationDetails(conversationId)) { callsByConversationId, conversationDetailsResult -> + val hasOngoingCall = callsByConversationId.containsKey(conversationId) // valid conversation is a conversation where the user is a member and it's not deleted val validConversation = when (conversationDetailsResult) { is ObserveConversationDetailsUseCase.Result.Success -> { 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 ca5b1d1c4cc..f0d1b950343 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 @@ -28,18 +28,28 @@ import com.wire.android.mapper.toConversationItem import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.UiTextResolver +import com.wire.kalium.logic.data.call.Call import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents import com.wire.kalium.logic.data.conversation.ConversationFilter import com.wire.kalium.logic.data.conversation.ConversationQueryConfig +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.id.TeamId +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase 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 com.wire.kalium.logic.feature.user.GetSelfTeamIdUseCase import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch class GetConversationsFromSearchUseCase @Inject constructor( private val useCase: GetPaginatedFlowOfConversationDetailsWithEventsBySearchQueryUseCase, @@ -49,6 +59,7 @@ class GetConversationsFromSearchUseCase @Inject constructor( private val dispatchers: DispatcherProvider, private val getSelfTeamId: GetSelfTeamIdUseCase, private val uiTextResolver: UiTextResolver, + private val observeJoinableCalls: ObserveJoinableCallsUseCase, ) { @Suppress("LongParameterList") suspend operator fun invoke( @@ -60,6 +71,8 @@ class GetConversationsFromSearchUseCase @Inject constructor( useStrictMlsFilter: Boolean, ): Flow> { val selfUserTeamId = getSelfTeamId() + val joinableCallsFlow = observeJoinableCalls() + val initialJoinableCallsByConversationId = joinableCallsFlow.first() val pagingConfig = PagingConfig( pageSize = PAGE_SIZE, prefetchDistance = PREFETCH_DISTANCE, @@ -69,19 +82,41 @@ class GetConversationsFromSearchUseCase @Inject constructor( return when (conversationFilter) { ConversationFilter.All, ConversationFilter.Groups, - ConversationFilter.Channels, - ConversationFilter.OneOnOne -> useCase( - queryConfig = ConversationQueryConfig( - searchQuery = searchQuery, - fromArchive = fromArchive, - newActivitiesOnTop = newActivitiesOnTop, - onlyInteractionEnabled = onlyInteractionEnabled, - conversationFilter = conversationFilter, - ), - pagingConfig = pagingConfig, - startingOffset = 0L, - strictMlsFilter = useStrictMlsFilter - ) + ConversationFilter.Channels -> + useCase( + queryConfig = ConversationQueryConfig( + searchQuery = searchQuery, + fromArchive = fromArchive, + newActivitiesOnTop = newActivitiesOnTop, + onlyInteractionEnabled = onlyInteractionEnabled, + conversationFilter = conversationFilter, + ), + pagingConfig = pagingConfig, + startingOffset = 0L, + strictMlsFilter = useStrictMlsFilter + ).mapToConversationItems( + selfUserTeamId = selfUserTeamId, + initialJoinableCallsByConversationId = initialJoinableCallsByConversationId, + joinableCallsFlow = joinableCallsFlow + ) + + ConversationFilter.OneOnOne -> + useCase( + queryConfig = ConversationQueryConfig( + searchQuery = searchQuery, + fromArchive = fromArchive, + newActivitiesOnTop = newActivitiesOnTop, + onlyInteractionEnabled = onlyInteractionEnabled, + conversationFilter = conversationFilter, + ), + pagingConfig = pagingConfig, + startingOffset = 0L, + strictMlsFilter = useStrictMlsFilter + ).mapToConversationItems( + selfUserTeamId = selfUserTeamId, + initialJoinableCallsByConversationId = emptyMap(), + joinableCallsFlow = flowOf(emptyMap()) + ) ConversationFilter.Favorites -> { when (val result = getFavoriteFolderUseCase.invoke()) { @@ -89,21 +124,70 @@ class GetConversationsFromSearchUseCase @Inject constructor( is GetFavoriteFolderUseCase.Result.Success -> observeConversationsFromFromFolder(result.folder.id) } - .map { staticPagingItems(it) } + .combine(joinableCallsFlow) { conversations, joinableCallsByConversationId -> + staticPagingItems( + conversations.toConversationItems( + selfUserTeamId = selfUserTeamId, + joinableCallsByConversationId = joinableCallsByConversationId + ) + ) + } } is ConversationFilter.Folder -> { observeConversationsFromFromFolder(conversationFilter.folderId) - .map { staticPagingItems(it) } + .combine(joinableCallsFlow) { conversations, joinableCallsByConversationId -> + staticPagingItems( + conversations.toConversationItems( + selfUserTeamId = selfUserTeamId, + joinableCallsByConversationId = joinableCallsByConversationId + ) + ) + } } - }.map { pagingData -> - pagingData.map { - it.toConversationItem(userTypeMapper, uiTextResolver, selfUserTeamId) - } - }.flowOn(dispatchers.io()) + }.flowOn(dispatchers.io()) + } + + private fun Flow>.mapToConversationItems( + selfUserTeamId: TeamId?, + initialJoinableCallsByConversationId: Map, + joinableCallsFlow: Flow> + ): Flow> = channelFlow { + val latestJoinableCallsByConversationId = MutableStateFlow(initialJoinableCallsByConversationId) + val callsJob = launch { + joinableCallsFlow.collect { latestJoinableCallsByConversationId.value = it } + } + try { + collect { pagingData -> + send( + pagingData.map { + it.toConversationItem( + userTypeMapper = userTypeMapper, + uiTextResolver = uiTextResolver, + selfUserTeamId = selfUserTeamId, + joinableCallsByConversationId = latestJoinableCallsByConversationId.value + ) + } + ) + } + } finally { + callsJob.cancel() + } + } + + private fun List.toConversationItems( + selfUserTeamId: TeamId?, + joinableCallsByConversationId: Map + ): List = map { + it.toConversationItem( + userTypeMapper = userTypeMapper, + uiTextResolver = uiTextResolver, + selfUserTeamId = selfUserTeamId, + joinableCallsByConversationId = joinableCallsByConversationId + ) } - private fun staticPagingItems(conversations: List): PagingData { + private fun staticPagingItems(conversations: List): PagingData { return PagingData.from( conversations, sourceLoadStates = LoadStates( 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 3b99bfd1943..43ae803d943 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 @@ -52,6 +52,7 @@ import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.conversation.ConversationFilter import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCase import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetailsWithEventsUseCase import com.wire.kalium.logic.feature.conversation.RefreshConversationsWithoutMetadataUseCase @@ -66,6 +67,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow @@ -111,6 +113,7 @@ class ConversationListViewModelImpl( private val userTypeMapper: UserTypeMapper, private val getSelfTeamId: GetSelfTeamIdUseCase, private val uiTextResolver: UiTextResolver, + private val observeJoinableCalls: ObserveJoinableCallsUseCase, ) : ConversationListViewModel, ViewModel() { private val _infoMessage = MutableSharedFlow() @@ -213,12 +216,13 @@ class ConversationListViewModelImpl( observeConversationListDetailsWithEvents( fromArchive = conversationsSource == ConversationsSource.ARCHIVE, conversationFilter = conversationsSource.toFilter() - ).map { conversations -> + ).combine(observeJoinableCalls()) { conversations, joinableCallsByConversationId -> conversations.map { conversationDetails -> conversationDetails.toConversationItem( userTypeMapper = userTypeMapper, uiTextResolver = uiTextResolver, - selfUserTeamId = selfTeamId + selfUserTeamId = selfTeamId, + joinableCallsByConversationId = joinableCallsByConversationId ) } to searchQuery } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt index b6a36b3fe35..beb4c274ffc 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/call/ConversationCallViewModelTest.kt @@ -40,7 +40,7 @@ import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.IsEligibleToStartCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase -import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCase +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase import com.wire.kalium.logic.feature.conversation.ObserveDegradedConversationNotifiedUseCase import com.wire.kalium.logic.feature.conversation.SetUserInformedAboutVerificationUseCase @@ -156,7 +156,7 @@ class ConversationCallViewModelTest { private lateinit var savedStateHandle: SavedStateHandle @MockK - private lateinit var observeOngoingCalls: ObserveOngoingCallsUseCase + private lateinit var observeJoinableCalls: ObserveJoinableCallsUseCase @MockK private lateinit var observeEstablishedCalls: ObserveEstablishedCallsUseCase @@ -198,7 +198,7 @@ class ConversationCallViewModelTest { MockKAnnotations.init(this, relaxUnitFun = true) every { savedStateHandle.navArgs() } returns ConversationNavArgs(conversationId = conversationId) coEvery { observeEstablishedCalls.invoke() } returns emptyFlow() - coEvery { observeOngoingCalls.invoke() } returns emptyFlow() + coEvery { observeJoinableCalls.invoke() } returns emptyFlow() coEvery { observeConversationDetails(any()) } returns flowOf() coEvery { observeParticipantsForConversation(any()) } returns flowOf() every { observeSyncState() } returns flowOf(SyncState.Live) @@ -220,7 +220,7 @@ class ConversationCallViewModelTest { } fun withOngoingCalls(calls: List) = apply { - coEvery { observeOngoingCalls() } returns flowOf(calls) + coEvery { observeJoinableCalls() } returns flowOf(calls.associateBy { it.conversationId }) } fun withConversationDetails(result: ObserveConversationDetailsUseCase.Result) = apply { @@ -229,7 +229,7 @@ class ConversationCallViewModelTest { fun arrange(): Pair = this to ConversationCallViewModel( savedStateHandle = savedStateHandle, - observeOngoingCalls = observeOngoingCalls, + observeJoinableCalls = observeJoinableCalls, observeEstablishedCalls = observeEstablishedCalls, answerCall = joinCall, endCall = endCall, diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModelArrangement.kt index 56374c912f2..bdb2166d84c 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModelArrangement.kt @@ -242,7 +242,6 @@ internal fun mockConversationDetailsGroup( ) = ConversationDetails.Group.Regular( conversation = TestConversation.GROUP() .copy(name = conversationName, id = mockedConversationId, teamId = teamId), - hasOngoingCall = false, isSelfUserMember = true, selfRole = Conversation.Member.Role.Member, wireCell = wireCell, diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupDetailsViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupDetailsViewModelTest.kt index 87af658115b..1bf7c4be696 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupDetailsViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupDetailsViewModelTest.kt @@ -699,7 +699,6 @@ class GroupDetailsViewModelTest { proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, legalHoldStatus = Conversation.LegalHoldStatus.ENABLED ), - hasOngoingCall = false, isSelfUserMember = true, selfRole = Conversation.Member.Role.Member, wireCell = null, @@ -729,7 +728,6 @@ class GroupDetailsViewModelTest { proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, legalHoldStatus = Conversation.LegalHoldStatus.ENABLED ), - hasOngoingCall = false, isSelfUserMember = true, selfRole = Conversation.Member.Role.Member, wireCell = null, diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/updateappsaccess/UpdateAppsAccessViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/updateappsaccess/UpdateAppsAccessViewModelTest.kt index ed5e0190b98..9370089857a 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/updateappsaccess/UpdateAppsAccessViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/updateappsaccess/UpdateAppsAccessViewModelTest.kt @@ -385,7 +385,6 @@ class UpdateAppsAccessViewModelTest { proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, legalHoldStatus = Conversation.LegalHoldStatus.ENABLED ), - hasOngoingCall = false, isSelfUserMember = true, selfRole = Conversation.Member.Role.Member, wireCell = null, 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 90e0c05d5f8..b7c505a6938 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 @@ -28,11 +28,16 @@ import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.util.ui.UiTextResolver import com.wire.android.util.ui.UIText +import com.wire.kalium.logic.data.call.Call +import com.wire.kalium.logic.data.call.CallStatus +import com.wire.kalium.logic.data.conversation.Conversation 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.data.id.ConversationId +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase 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 @@ -42,7 +47,9 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -186,6 +193,64 @@ class GetConversationsFromSearchUseCaseTest { assertEquals(false, conversation.isFromTheSameTeam) } + @Test + fun givenJoinableGroupCall_whenGettingPaginatedList_thenGroupItemHasOngoingCallMarker() = + runTest(dispatcherProvider.main()) { + // Given + val conversation = ConversationDetailsWithEvents(TestConversationDetails.GROUP) + val (arrangement, useCase) = Arrangement() + .withPaginatedResult(listOf(conversation)) + .withJoinableCalls(listOf(joinableCall(conversation.conversationDetails.conversation.id))) + .arrange() + + // When + val result = with(arrangement.queryConfig) { + useCase( + searchQuery = searchQuery, + fromArchive = fromArchive, + newActivitiesOnTop = newActivitiesOnTop, + onlyInteractionEnabled = onlyInteractionEnabled, + useStrictMlsFilter = true + ).asSnapshot() + } + + // Then + val group = assertInstanceOf(result.first()) + assertEquals(true, group.hasOnGoingCall) + assertEquals(true, group.hasNewActivitiesToShow) + } + + @Test + fun givenJoinableCallsChange_whenGettingPaginatedList_thenDoNotReEmitSamePagingData() = + runTest(dispatcherProvider.main()) { + // Given + val conversation = ConversationDetailsWithEvents(TestConversationDetails.GROUP) + val call = joinableCall(conversation.conversationDetails.conversation.id) + val (_, useCase) = Arrangement() + .withPaginatedResult(listOf(conversation)) + .withJoinableCallsFlow( + flowOf( + mapOf(call.conversationId to call), + emptyMap() + ) + ) + .arrange() + + // When + val result = with(ConversationQueryConfig("search")) { + useCase( + searchQuery = searchQuery, + fromArchive = fromArchive, + newActivitiesOnTop = newActivitiesOnTop, + onlyInteractionEnabled = onlyInteractionEnabled, + useStrictMlsFilter = true + ).toList() + } + + // Then + assertEquals(1, result.size) + } + inner class Arrangement { @MockK @@ -197,6 +262,9 @@ class GetConversationsFromSearchUseCaseTest { @MockK lateinit var observeConversationsFromFolderUseCase: ObserveConversationsFromFolderUseCase + @MockK + lateinit var observeJoinableCalls: ObserveJoinableCallsUseCase + @MockK lateinit var userTypeMapper: UserTypeMapper @@ -225,6 +293,7 @@ class GetConversationsFromSearchUseCaseTest { } } coEvery { uiTextResolver.localeTag() } returns "test-locale" + coEvery { observeJoinableCalls() } returns flowOf(emptyMap()) withSelfTeamId() withPaginatedResult(emptyList()) } @@ -245,6 +314,14 @@ class GetConversationsFromSearchUseCaseTest { } returns flowOf(conversations) } + fun withJoinableCalls(calls: List) = apply { + coEvery { observeJoinableCalls() } returns flowOf(calls.associateBy { it.conversationId }) + } + + fun withJoinableCallsFlow(result: Flow>) = apply { + coEvery { observeJoinableCalls() } returns result + } + fun withSelfTeamId() = apply { coEvery { getSelfTeamId() } returns TestUser.SELF_USER.teamId } @@ -256,7 +333,21 @@ class GetConversationsFromSearchUseCaseTest { userTypeMapper, dispatcherProvider, getSelfTeamId, - uiTextResolver + uiTextResolver, + observeJoinableCalls ) } + + private fun joinableCall(conversationId: ConversationId) = Call( + conversationId = conversationId, + status = CallStatus.STILL_ONGOING, + isMuted = false, + isCameraOn = false, + isCbrEnabled = false, + callerId = TestUser.SELF_USER_ID, + conversationName = null, + conversationType = Conversation.Type.Group.Regular, + callerName = null, + callerTeamName = null + ) } 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 0bf67c324fe..85aab5e6cd9 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 @@ -42,6 +42,7 @@ import com.wire.android.util.ui.UIText import com.wire.kalium.logic.data.conversation.ConversationDetailsWithEvents import com.wire.kalium.logic.data.conversation.ConversationFilter import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.feature.call.usecase.ObserveJoinableCallsUseCase import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetailsWithEventsUseCase import com.wire.kalium.logic.feature.conversation.RefreshConversationsWithoutMetadataUseCase import com.wire.kalium.logic.feature.legalhold.LegalHoldStateForSelfUser @@ -295,6 +296,9 @@ class ConversationListViewModelTest { private lateinit var observeConversationListDetailsWithEventsUseCase: ObserveConversationListDetailsWithEventsUseCase + @MockK + private lateinit var observeJoinableCalls: ObserveJoinableCallsUseCase + @MockK private lateinit var observeLegalHoldStateForSelfUserUseCase: ObserveLegalHoldStateForSelfUserUseCase @@ -323,6 +327,7 @@ class ConversationListViewModelTest { } ) every { audioMessagePlayer.playingAudioMessageFlow } returns flowOf(PlayingAudioMessage.None) + coEvery { observeJoinableCalls() } returns flowOf(emptyMap()) coEvery { getSelfTeamId() } returns TestUser.SELF_USER.teamId coEvery { uiTextResolver.resolve(any()) } answers { val text = firstArg() @@ -371,6 +376,7 @@ class ConversationListViewModelTest { refreshConversationsWithoutMetadata = refreshConversationsWithoutMetadata, currentAccount = TestUser.SELF_USER_ID, observeConversationListDetailsWithEvents = observeConversationListDetailsWithEventsUseCase, + observeJoinableCalls = observeJoinableCalls, observeLegalHoldStateForSelfUser = observeLegalHoldStateForSelfUserUseCase, userTypeMapper = UserTypeMapper(), getSelfTeamId = getSelfTeamId, diff --git a/kalium b/kalium index 22bf2fea615..48edf7f7b2b 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 22bf2fea6152bf87d5b25ceb2b5ae0acf54b4e89 +Subproject commit 48edf7f7b2b2283ef8e257fa42f8f1e1d2b401b2