Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class CallsModule {
fun provideObserveOngoingCallsUseCase(callsScope: CallsScope) =
callsScope.observeOngoingCalls

@ViewModelScoped
@Provides
fun provideObserveActiveCallConversationIdsUseCase(callsScope: CallsScope) =
callsScope.observeActiveCallConversationIds

@ViewModelScoped
@Provides
fun provideObserveEstablishedCallWithSortedParticipantsUseCase(callsScope: CallsScope) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
mutedStatus = conversationDetails.conversation.mutedStatus,
unreadEventCount = unreadEventCount
),
hasOnGoingCall = conversationDetails.hasOngoingCall && conversationDetails.isSelfUserMember,
hasOnGoingCall = false,
isFromTheSameTeam = conversationDetails.conversation.teamId == selfUserTeamId,
isSelfUserMember = conversationDetails.isSelfUserMember,
teamId = conversationDetails.conversation.teamId,
Expand All @@ -89,7 +89,7 @@
mutedStatus = conversationDetails.conversation.mutedStatus,
unreadEventCount = unreadEventCount
),
hasOnGoingCall = conversationDetails.hasOngoingCall && conversationDetails.isSelfUserMember,
hasOnGoingCall = false,

Check warning on line 92 in app/src/main/kotlin/com/wire/android/mapper/ConversationMapper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/mapper/ConversationMapper.kt#L92

Added line #L92 was not covered by tests
isFromTheSameTeam = conversationDetails.conversation.teamId == selfUserTeamId,
isSelfUserMember = conversationDetails.isSelfUserMember,
teamId = conversationDetails.conversation.teamId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ class GetConversationsFromSearchUseCase @Inject constructor(
playingAudioMessage = playingAudioMessage
)
}
}.flowOn(dispatchers.io())
}
.flowOn(dispatchers.io())
}

private fun staticPagingItems(conversations: List<ConversationDetailsWithEvents>): PagingData<ConversationDetailsWithEvents> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,26 @@

import androidx.compose.runtime.Stable
import androidx.paging.PagingData
import com.wire.android.ui.home.conversationslist.model.ConversationItem
import com.wire.android.ui.home.conversationslist.model.ConversationSection
import com.wire.android.ui.home.conversationslist.model.ConversationItemType
import com.wire.android.ui.home.conversationslist.model.ConversationItem
import com.wire.kalium.logic.data.id.ConversationId
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

@Stable
sealed interface ConversationListState {
data class Paginated(
val conversations: Flow<PagingData<ConversationItemType>>,
val activeCallConversationIds: Flow<Set<ConversationId>> = flowOf(emptySet()),
val domain: String = "",
) : ConversationListState
data class NotPaginated(
val isLoading: Boolean = true,
val conversations: ImmutableMap<ConversationSection, List<ConversationItem>> = persistentMapOf(),
val activeCallConversationIds: Flow<Set<ConversationId>> = flowOf(emptySet()),

Check warning on line 42 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListState.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListState.kt#L42

Added line #L42 was not covered by tests
val domain: String = "",
) : ConversationListState
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@
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.id.ConversationId
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.call.usecase.ObserveActiveCallConversationIdsUseCase
import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetailsWithEventsUseCase
import com.wire.kalium.logic.feature.conversation.RefreshConversationsWithoutMetadataUseCase
Expand All @@ -70,7 +72,9 @@
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
Expand Down Expand Up @@ -104,6 +108,7 @@
private val observeConversationListDetailsWithEvents: ObserveConversationListDetailsWithEventsUseCase,
private val refreshUsersWithoutMetadata: RefreshUsersWithoutMetadataUseCase,
private val refreshConversationsWithoutMetadata: RefreshConversationsWithoutMetadataUseCase,
private val observeActiveCallConversationIds: ObserveActiveCallConversationIdsUseCase,
private val observeLegalHoldStateForSelfUser: ObserveLegalHoldStateForSelfUserUseCase,
private val audioMessagePlayer: ConversationAudioMessagePlayer,
@CurrentAccount val currentAccount: UserId,
Expand All @@ -128,6 +133,11 @@

private val searchQueryFlow: MutableStateFlow<String> = MutableStateFlow("")
private val isSelfUserUnderLegalHoldFlow = MutableSharedFlow<Boolean>(replay = 1)
private val activeCallConversationIdsFlow: Flow<Set<ConversationId>> = flow {
emitAll(observeActiveCallConversationIds())
}
.onStart { emit(emptySet()) }
.flowOn(dispatcher.io())

private val containsNewActivitiesSection = when (conversationsSource) {
ConversationsSource.MAIN,
Expand All @@ -148,7 +158,11 @@
.combine(audioMessagePlayer.playingAudioMessageFlow) { (searchQuery, isSelfUserUnderLegalHold), playingAudioMessage ->
Triple(searchQuery, isSelfUserUnderLegalHold, playingAudioMessage)
}
.flatMapLatest { (searchQuery, isSelfUserUnderLegalHold, playingAudioMessage) ->
.combine(activeCallConversationIdsFlow) { conversationListConfig, activeCallConversationIds ->
conversationListConfig to activeCallConversationIds
}
.flatMapLatest { (conversationListConfig, activeCallConversationIds) ->
val (searchQuery, isSelfUserUnderLegalHold, playingAudioMessage) = conversationListConfig
getConversationsPaginated(
searchQuery = searchQuery,
fromArchive = conversationsSource == ConversationsSource.ARCHIVE,
Expand All @@ -165,15 +179,18 @@
// do not add separators if the list shouldn't show conversations grouped into different folders
!containsNewActivitiesSection -> null

before == null && after != null && after.hasNewActivitiesToShow ->
before == null && after != null && after.hasNewActivitiesToShow(activeCallConversationIds) ->
// list starts with items with "new activities"
ConversationSection.Predefined.NewActivities

before == null && after != null && !after.hasNewActivitiesToShow ->
before == null && after != null && !after.hasNewActivitiesToShow(activeCallConversationIds) ->
// list doesn't contain any items with "new activities"
ConversationSection.Predefined.Conversations

before != null && before.hasNewActivitiesToShow && after != null && !after.hasNewActivitiesToShow ->
before != null &&
before.hasNewActivitiesToShow(activeCallConversationIds) &&
after != null &&
!after.hasNewActivitiesToShow(activeCallConversationIds) ->
// end of "new activities" section and beginning of "conversations" section
ConversationSection.Predefined.Conversations

Expand All @@ -187,8 +204,13 @@

override var conversationListState by mutableStateOf(
when (usePagination) {
true -> ConversationListState.Paginated(conversations = conversationsPaginatedFlow, domain = currentAccount.domain)
false -> ConversationListState.NotPaginated()
true -> ConversationListState.Paginated(
conversations = conversationsPaginatedFlow,
activeCallConversationIds = activeCallConversationIdsFlow,
domain = currentAccount.domain
)

false -> ConversationListState.NotPaginated(activeCallConversationIds = activeCallConversationIdsFlow)

Check warning on line 213 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L213

Added line #L213 was not covered by tests
}
)
private set
Expand Down Expand Up @@ -222,34 +244,44 @@
conversationFilter = conversationsSource.toFilter()
),
isSelfUserUnderLegalHoldFlow,
audioMessagePlayer.playingAudioMessageFlow
) { conversations, isSelfUserUnderLegalHold, playingAudioMessage ->
audioMessagePlayer.playingAudioMessageFlow,
activeCallConversationIdsFlow
) { conversations, isSelfUserUnderLegalHold, playingAudioMessage, activeCallConversationIds ->
conversations.map { conversationDetails ->
conversationDetails.toConversationItem(
userTypeMapper = userTypeMapper,
uiTextResolver = uiTextResolver,
searchQuery = searchQuery,
selfUserTeamId = getSelfUser()?.teamId,
playingAudioMessage = playingAudioMessage
).hideIndicatorForSelfUserUnderLegalHold(isSelfUserUnderLegalHold)
} to searchQuery
)
.hideIndicatorForSelfUserUnderLegalHold(isSelfUserUnderLegalHold)
} to (searchQuery to activeCallConversationIds)

Check warning on line 259 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L258-L259

Added lines #L258 - L259 were not covered by tests
}
}
.map { (conversationItems, searchQuery) ->
.map { (conversationItems, searchQueryAndActiveCallConversationIds) ->

Check warning on line 262 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L262

Added line #L262 was not covered by tests
val (searchQuery, activeCallConversationIds) = searchQueryAndActiveCallConversationIds
if (searchQuery.isEmpty()) {
conversationItems.withSections(source = conversationsSource).toImmutableMap()
conversationItems.withSections(
source = conversationsSource,
activeCallConversationIds = activeCallConversationIds
).toImmutableMap()
} else {
searchConversation(
conversationDetails = conversationItems,
searchQuery = searchQuery
).withSections(source = conversationsSource).toImmutableMap()
).withSections(
source = conversationsSource,
activeCallConversationIds = activeCallConversationIds
).toImmutableMap()
}
}
.flowOn(dispatcher.io())
.collect {
conversationListState = ConversationListState.NotPaginated(
isLoading = false,
conversations = it,
activeCallConversationIds = activeCallConversationIdsFlow,

Check warning on line 284 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L284

Added line #L284 was not covered by tests
domain = currentAccount.domain
)
}
Expand Down Expand Up @@ -329,7 +361,10 @@
}

@Suppress("ComplexMethod")
private fun List<ConversationItem>.withSections(source: ConversationsSource): Map<ConversationSection, List<ConversationItem>> {
private fun List<ConversationItem>.withSections(

Check failure on line 364 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-android&issues=AZ5Bvu6IpmbvQSOQtiB-&open=AZ5Bvu6IpmbvQSOQtiB-&pullRequest=4840
source: ConversationsSource,
activeCallConversationIds: Set<ConversationId> = emptySet()

Check warning on line 366 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L366

Added line #L366 was not covered by tests
): Map<ConversationSection, List<ConversationItem>> {
return when (source) {
ConversationsSource.ARCHIVE -> {
buildMap {
Expand All @@ -345,7 +380,7 @@
ConversationsSource.ONE_ON_ONE,
is ConversationsSource.FOLDER,
ConversationsSource.MAIN -> {
val (unreadConversations, remainingConversations) = unreadToReadConversationsItems()
val (unreadConversations, remainingConversations) = unreadToReadConversationsItems(activeCallConversationIds)

Check warning on line 383 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L383

Added line #L383 was not covered by tests
buildMap {
if (unreadConversations.isNotEmpty()) {
put(ConversationSection.Predefined.NewActivities, unreadConversations)
Expand All @@ -357,7 +392,7 @@
}

is ConversationsSource.CHANNELS -> {
val (unreadConversations, remainingConversations) = unreadToReadConversationsItems()
val (unreadConversations, remainingConversations) = unreadToReadConversationsItems(activeCallConversationIds)

Check warning on line 395 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L395

Added line #L395 was not covered by tests
buildMap {
put(ConversationSection.Predefined.BrowseChannels, emptyList())
if (unreadConversations.isNotEmpty()) {
Expand All @@ -372,7 +407,9 @@
}

@Suppress("CyclomaticComplexMethod")
private fun List<ConversationItem>.unreadToReadConversationsItems(): Pair<List<ConversationItem>, List<ConversationItem>> {
private fun List<ConversationItem>.unreadToReadConversationsItems(
activeCallConversationIds: Set<ConversationId>
): Pair<List<ConversationItem>, List<ConversationItem>> {
val unreadConversations = filter {
when (it.mutedStatus) {
MutedConversationStatus.AllAllowed -> when (it.badgeEventType) {
Expand All @@ -397,13 +434,22 @@
}

MutedConversationStatus.AllMuted -> false
} || (it is ConversationItem.Group && it.hasOnGoingCall)
}
} || it.isActiveGroupCall(activeCallConversationIds)
}.sortedByDescending { it.isActiveGroupCall(activeCallConversationIds) }

Check warning on line 438 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt#L437-L438

Added lines #L437 - L438 were not covered by tests

val remainingConversations = this - unreadConversations.toSet()
return unreadConversations to remainingConversations
}

private fun ConversationItemType.hasNewActivitiesToShow(activeCallConversationIds: Set<ConversationId>): Boolean =
this is ConversationItem && hasNewActivitiesToShow(activeCallConversationIds)

private fun ConversationItem.hasNewActivitiesToShow(activeCallConversationIds: Set<ConversationId>): Boolean =
hasNewActivitiesToShow || isActiveGroupCall(activeCallConversationIds)

private fun ConversationItem.isActiveGroupCall(activeCallConversationIds: Set<ConversationId>): Boolean =
this is ConversationItem.Group && isSelfUserMember && conversationId in activeCallConversationIds

private fun searchConversation(conversationDetails: List<ConversationItem>, searchQuery: String): List<ConversationItem> =
conversationDetails.filter { details ->
when (details) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import com.ramcosta.composedestinations.generated.app.destinations.BrowseChannelsScreenDestination
Expand Down Expand Up @@ -167,6 +168,7 @@ fun ConversationsScreenContent(
when (val state = conversationListViewModel.conversationListState) {
is ConversationListState.Paginated -> {
val lazyPagingItems = state.conversations.collectAsLazyPagingItemsWithLifecycle()
val activeCallConversationIds by state.activeCallConversationIds.collectAsStateWithLifecycle(emptySet())
searchBarState.searchVisibleChanged(lazyPagingItems.itemCount > 0 || searchBarState.isSearchActive)
when {
// when conversation list is not yet fetched, show loading indicator
Expand All @@ -179,6 +181,7 @@ fun ConversationsScreenContent(
onEditConversation = onEditConversationItem,
onOpenUserProfile = onOpenUserProfile,
onJoinCall = onJoinCall,
activeCallConversationIds = activeCallConversationIds,
onAudioPermissionPermanentlyDenied = {
permissionPermanentlyDeniedDialogState.show(
PermissionPermanentlyDeniedDialogState.Visible(
Expand All @@ -200,6 +203,7 @@ fun ConversationsScreenContent(
}

is ConversationListState.NotPaginated -> {
val activeCallConversationIds by state.activeCallConversationIds.collectAsStateWithLifecycle(emptySet())
val hasConversations = state.conversations.isNotEmpty() && state.conversations.any { it.value.isNotEmpty() }
searchBarState.searchVisibleChanged(isSearchVisible = hasConversations || searchBarState.isSearchActive)
when {
Expand All @@ -213,6 +217,7 @@ fun ConversationsScreenContent(
onEditConversation = onEditConversationItem,
onOpenUserProfile = onOpenUserProfile,
onJoinCall = onJoinCall,
activeCallConversationIds = activeCallConversationIds,
onAudioPermissionPermanentlyDenied = {
permissionPermanentlyDeniedDialogState.show(
PermissionPermanentlyDeniedDialogState.Visible(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.android.ui.home.conversationslist.common

import com.wire.android.ui.home.conversationslist.model.ConversationItem
import com.wire.kalium.logic.data.id.ConversationId

internal fun ConversationItem.withActiveCallStatus(activeCallConversationIds: Set<ConversationId>): ConversationItem =
when (this) {
is ConversationItem.Group.Regular -> {
val hasActiveCall = conversationId in activeCallConversationIds && isSelfUserMember
copy(
hasOnGoingCall = hasActiveCall,
hasNewActivitiesToShow = hasNewActivitiesToShow || hasActiveCall
)
}

is ConversationItem.Group.Channel -> {
val hasActiveCall = conversationId in activeCallConversationIds && isSelfUserMember
copy(
hasOnGoingCall = hasActiveCall,
hasNewActivitiesToShow = hasNewActivitiesToShow || hasActiveCall
)
}

Check warning on line 40 in app/src/main/kotlin/com/wire/android/ui/home/conversationslist/common/ConversationItemActiveCallStatus.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This branch's code block is the same as the block for the branch on line 26.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-android&issues=AZ5BuIuW_5KGQq1TkQgd&open=AZ5BuIuW_5KGQq1TkQgd&pullRequest=4840

is ConversationItem.ConnectionConversation,
is ConversationItem.PrivateConversation -> this
}
Loading
Loading