diff --git a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt index eebcb0dd95d..b772a7ba015 100644 --- a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt @@ -460,4 +460,9 @@ class UseCaseModule { @Provides fun provideObserveLegalHoldForUserUseCase(@KaliumCoreLogic coreLogic: CoreLogic, @CurrentAccount currentAccount: UserId) = coreLogic.getSessionScope(currentAccount).observeLegalHoldStateForUser + + @ViewModelScoped + @Provides + fun provideMembersHavingLegalHoldClientUseCase(@KaliumCoreLogic coreLogic: CoreLogic, @CurrentAccount currentAccount: UserId) = + coreLogic.getSessionScope(currentAccount).membersHavingLegalHoldClient } diff --git a/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt b/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt index cc63ba62613..ae99a2256ee 100644 --- a/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt +++ b/app/src/main/kotlin/com/wire/android/mapper/UIParticipantMapper.kt @@ -36,7 +36,11 @@ class UIParticipantMapper @Inject constructor( private val userTypeMapper: UserTypeMapper, private val wireSessionImageLoader: WireSessionImageLoader ) { - fun toUIParticipant(user: User, mlsCertificateStatus: CertificateStatus? = null): UIParticipant = with(user) { + fun toUIParticipant( + user: User, + mlsCertificateStatus: CertificateStatus? = null, + isUnderLegalHold: Boolean = false, + ): UIParticipant = with(user) { val (userType, connectionState, unavailable) = when (this) { is OtherUser -> Triple(this.userType, this.connectionStatus, this.isUnavailableUser) // TODO(refactor): does self user need a type ? to false @@ -57,7 +61,8 @@ class UIParticipantMapper @Inject constructor( isDefederated = (user is OtherUser && user.defederated), isProteusVerified = (user is OtherUser && user.isProteusVerified), isMLSVerified = mlsCertificateStatus == CertificateStatus.VALID, - supportedProtocolList = supportedProtocols.orEmpty().toList() + supportedProtocolList = supportedProtocols.orEmpty().toList(), + isUnderLegalHold = isUnderLegalHold, ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt index 02445d535f5..02279736e91 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt @@ -126,7 +126,8 @@ data class ConversationSheetContent( val isArchived: Boolean, val protocol: Conversation.ProtocolInfo, val mlsVerificationStatus: Conversation.VerificationStatus, - val proteusVerificationStatus: Conversation.VerificationStatus + val proteusVerificationStatus: Conversation.VerificationStatus, + val isUnderLegalHold: Boolean, ) { private val isSelfUserMember: Boolean get() = selfRole != null diff --git a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetState.kt b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetState.kt index 46297d1ff5a..0e48a9ac9a7 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetState.kt @@ -77,7 +77,8 @@ fun rememberConversationSheetState( isArchived = conversationItem.isArchived, protocol = Conversation.ProtocolInfo.Proteus, mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED, - proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED + proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED, + isUnderLegalHold = isLegalHold ) } } @@ -100,7 +101,8 @@ fun rememberConversationSheetState( isArchived = conversationItem.isArchived, protocol = Conversation.ProtocolInfo.Proteus, mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED, - proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED + proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED, + isUnderLegalHold = isLegalHold ) } } @@ -119,7 +121,8 @@ fun rememberConversationSheetState( isArchived = conversationItem.isArchived, protocol = Conversation.ProtocolInfo.Proteus, mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED, - proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED + proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED, + isUnderLegalHold = isLegalHold ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt index fa189cea151..83085f5d298 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt @@ -18,6 +18,7 @@ package com.wire.android.ui.home.conversations.details +import SwipeableSnackbar import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollConfiguration @@ -34,6 +35,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -69,6 +71,7 @@ import com.wire.android.ui.common.MLSVerifiedIcon import com.wire.android.ui.common.MoreOptionIcon import com.wire.android.ui.common.ProteusVerifiedIcon import com.wire.android.ui.common.TabItem +import com.wire.android.ui.common.VisibilityState import com.wire.android.ui.common.WireTabRow import com.wire.android.ui.common.bottomsheet.WireModalSheetLayout import com.wire.android.ui.common.bottomsheet.conversation.ConversationSheetContent @@ -104,6 +107,7 @@ import com.wire.android.ui.home.conversations.details.participants.GroupConversa import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.GroupDialogState +import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectConversationDialog import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions @@ -111,8 +115,6 @@ import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.UIText import com.wire.kalium.logic.data.conversation.Conversation import kotlinx.coroutines.launch -import SwipeableSnackbar -import androidx.compose.material3.SnackbarHost @RootNavGraph @Destination( @@ -304,6 +306,7 @@ private fun GroupConversationDetailsContent( val leaveGroupDialogState = rememberVisibilityState() val clearConversationDialogState = rememberVisibilityState() val archiveConversationDialogState = rememberVisibilityState() + val legalHoldSubjectDialogState = rememberVisibilityState() LaunchedEffect(conversationSheetState.conversationSheetContent) { // on each closing BottomSheet we revert BSContent to Home. @@ -319,6 +322,7 @@ private fun GroupConversationDetailsContent( leaveGroupDialogState.dismiss() clearConversationDialogState.dismiss() archiveConversationDialogState.dismiss() + legalHoldSubjectDialogState.dismiss() } Scaffold( @@ -344,7 +348,9 @@ private fun GroupConversationDetailsContent( totalParticipants = groupParticipantsState.data.allCount, isLoading = isLoading, onSearchConversationMessagesClick = onSearchConversationMessagesClick, - onConversationMediaClick = onConversationMediaClick + onConversationMediaClick = onConversationMediaClick, + isUnderLegalHold = it.isUnderLegalHold, + onLegalHoldLearnMoreClick = remember { { legalHoldSubjectDialogState.show(Unit) } } ) } WireTabRow( @@ -473,6 +479,10 @@ private fun GroupConversationDetailsContent( bottomSheetEventsHandler.updateConversationArchiveStatus(dialogState = it, onMessage = closeBottomSheetAndShowSnackbarMessage) } ) + + VisibilityState(legalHoldSubjectDialogState) { + LegalHoldSubjectConversationDialog(legalHoldSubjectDialogState::dismiss) + } } @Composable diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt index d6f098ce04d..47e008094af 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsTopBarCollapsing.kt @@ -21,7 +21,6 @@ import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight @@ -40,8 +39,11 @@ import com.wire.android.ui.common.conversationColor import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.spacers.VerticalSpace import com.wire.android.ui.home.conversationslist.common.GroupConversationAvatar +import com.wire.android.ui.legalhold.banner.LegalHoldSubjectBanner +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography +import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.android.util.ui.UIText import com.wire.kalium.logic.data.id.ConversationId @@ -51,8 +53,10 @@ fun GroupConversationDetailsTopBarCollapsing( conversationId: ConversationId, totalParticipants: Int, isLoading: Boolean, + isUnderLegalHold: Boolean, onSearchConversationMessagesClick: () -> Unit, onConversationMediaClick: () -> Unit, + onLegalHoldLearnMoreClick: () -> Unit, modifier: Modifier = Modifier ) { Column( @@ -88,37 +92,35 @@ fun GroupConversationDetailsTopBarCollapsing( end.linkTo(parent.end) } ) { - Row( - verticalAlignment = Alignment.CenterVertically, + Text( + text = title.ifBlank { + if (isLoading) "" + else UIText.StringResource(R.string.group_unavailable_label).asString() + }, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = MaterialTheme.wireTypography.body02, + color = MaterialTheme.colorScheme.onBackground, modifier = Modifier .padding( horizontal = dimensions().spacing16x, vertical = dimensions().spacing4x ) - ) { - Text( - text = title.ifBlank { - if (isLoading) "" - else UIText.StringResource(R.string.group_unavailable_label).asString() - }, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - style = MaterialTheme.wireTypography.body02, - color = MaterialTheme.colorScheme.onBackground - ) - } - Row( - verticalAlignment = Alignment.CenterVertically, + ) + Text( + text = stringResource( + id = R.string.conversation_details_participants_count, + totalParticipants + ), + style = MaterialTheme.wireTypography.subline01, + color = MaterialTheme.wireColorScheme.secondaryText, modifier = Modifier .padding(horizontal = dimensions().spacing64x) - ) { - Text( - text = stringResource( - id = R.string.conversation_details_participants_count, - totalParticipants - ), - style = MaterialTheme.wireTypography.subline01, - color = MaterialTheme.wireColorScheme.secondaryText + ) + if (isUnderLegalHold) { + LegalHoldSubjectBanner( + onClick = onLegalHoldLearnMoreClick, + modifier = Modifier.padding(vertical = dimensions().spacing8x) ) } } @@ -131,3 +133,20 @@ fun GroupConversationDetailsTopBarCollapsing( ) } } + +@PreviewMultipleThemes +@Composable +fun PreviewGroupConversationDetailsTopBarCollapsing() { + WireTheme { + GroupConversationDetailsTopBarCollapsing( + title = "Conversation Title", + conversationId = ConversationId("conversationId", "domain"), + totalParticipants = 10, + isUnderLegalHold = true, + isLoading = false, + onSearchConversationMessagesClick = {}, + onConversationMediaClick = {}, + onLegalHoldLearnMoreClick = {}, + ) + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt index c12b96c9907..1a9b4e65964 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt @@ -33,6 +33,7 @@ import com.wire.android.ui.home.conversations.details.participants.GroupConversa import com.wire.android.ui.home.conversations.details.participants.usecase.ObserveParticipantsForConversationUseCase import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.GroupDialogState +import com.wire.android.ui.home.conversationslist.showLegalHoldIndicator import com.wire.android.ui.navArgs import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.UIText @@ -153,7 +154,8 @@ class GroupConversationDetailsViewModel @Inject constructor( isArchived = groupDetails.conversation.archived, protocol = groupDetails.conversation.protocol, mlsVerificationStatus = groupDetails.conversation.mlsVerificationStatus, - proteusVerificationStatus = groupDetails.conversation.proteusVerificationStatus + proteusVerificationStatus = groupDetails.conversation.proteusVerificationStatus, + isUnderLegalHold = groupDetails.conversation.legalHoldStatus.showLegalHoldIndicator(), ) val isGuestAllowed = groupDetails.conversation.isGuestAllowed() || groupDetails.conversation.isNonTeamMemberAllowed() val isUpdatingReadReceiptAllowed = if (selfTeam == null) { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt index 441b3fbd049..db0e4bc4e75 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/ConversationParticipantItem.kt @@ -28,12 +28,12 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import com.wire.android.BuildConfig import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.model.UserAvatarData import com.wire.android.ui.common.ArrowRightIcon +import com.wire.android.ui.common.LegalHoldIndicator import com.wire.android.ui.common.MLSVerifiedIcon import com.wire.android.ui.common.ProteusVerifiedIcon import com.wire.android.ui.common.ProtocolLabel @@ -45,10 +45,12 @@ import com.wire.android.ui.home.conversations.details.participants.model.UIParti import com.wire.android.ui.home.conversations.search.HighlightName import com.wire.android.ui.home.conversations.search.HighlightSubtitle import com.wire.android.ui.home.conversationslist.model.Membership +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography import com.wire.android.util.EMPTY +import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.android.util.uiReadReceiptDateTime import com.wire.kalium.logic.data.user.SupportedProtocol import com.wire.kalium.logic.data.user.UserId @@ -106,6 +108,9 @@ fun ConversationParticipantItem( ) } } + if (uiParticipant.isUnderLegalHold) { + LegalHoldIndicator(modifier = Modifier.padding(start = dimensions().spacing6x)) + } } }, subtitle = { @@ -134,21 +139,25 @@ fun ConversationParticipantItem( ) } -@Preview +@PreviewMultipleThemes @Composable fun PreviewGroupConversationParticipantItem() { - ConversationParticipantItem( - UIParticipant( - UserId("0", ""), - "name", - "handle", - false, - false, - UserAvatarData(), - Membership.Guest, - isMLSVerified = true, - isProteusVerified = true, - supportedProtocolList = listOf(SupportedProtocol.PROTEUS, SupportedProtocol.MLS)), - clickable = Clickable(enabled = true) {} - ) + WireTheme { + ConversationParticipantItem( + UIParticipant( + UserId("0", ""), + "name", + "handle", + false, + false, + UserAvatarData(), + Membership.Guest, + isMLSVerified = true, + isProteusVerified = true, + isUnderLegalHold = true, + supportedProtocolList = listOf(SupportedProtocol.PROTEUS, SupportedProtocol.MLS) + ), + clickable = Clickable(enabled = true) {} + ) + } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/UIParticipant.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/UIParticipant.kt index 6c137705e2b..b68c5aa926e 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/UIParticipant.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/model/UIParticipant.kt @@ -42,5 +42,6 @@ data class UIParticipant( val isDefederated: Boolean = false, val isProteusVerified: Boolean = false, val isMLSVerified: Boolean = false, - val supportedProtocolList: List = listOf() + val supportedProtocolList: List = listOf(), + val isUnderLegalHold: Boolean = false, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt index ad0121a9681..8b99ad3a2fd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCase.kt @@ -24,12 +24,15 @@ import com.wire.android.ui.home.conversations.name import com.wire.android.ui.home.conversations.userId import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.kalium.logic.data.conversation.Conversation.Member +import com.wire.kalium.logic.data.conversation.MemberDetails import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.OtherUser import com.wire.kalium.logic.data.user.SelfUser import com.wire.kalium.logic.data.user.type.UserType import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCase +import com.wire.kalium.logic.feature.legalhold.MembersHavingLegalHoldClientUseCase +import com.wire.kalium.logic.functional.getOrElse import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -38,6 +41,7 @@ import javax.inject.Inject class ObserveParticipantsForConversationUseCase @Inject constructor( private val observeConversationMembers: ObserveConversationMembersUseCase, private val getMembersE2EICertificateStatuses: GetMembersE2EICertificateStatusesUseCase, + private val membersHavingLegalHoldClientUseCase: MembersHavingLegalHoldClientUseCase, private val uiParticipantMapper: UIParticipantMapper, private val dispatchers: DispatcherProvider ) { @@ -60,11 +64,15 @@ class ObserveParticipantsForConversationUseCase @Inject constructor( .plus(visibleAdminsWithoutServices.map { it.userId }) val mlsVerificationMap = getMembersE2EICertificateStatuses(conversationId, visibleUserIds) + val legalHoldList = membersHavingLegalHoldClientUseCase(conversationId).getOrElse(emptyList()) + + fun List.toUIParticipants() = this.map { + uiParticipantMapper.toUIParticipant(it.user, mlsVerificationMap[it.userId], legalHoldList.contains(it.userId)) + } + ConversationParticipantsData( - admins = visibleAdminsWithoutServices - .map { uiParticipantMapper.toUIParticipant(it.user, mlsVerificationMap[it.user.id]) }, - participants = visibleParticipants - .map { uiParticipantMapper.toUIParticipant(it.user, mlsVerificationMap[it.user.id]) }, + admins = visibleAdminsWithoutServices.toUIParticipants(), + participants = visibleParticipants.toUIParticipants(), allAdminsCount = allAdminsWithoutServices.size, allParticipantsCount = allParticipants.size, isSelfAnAdmin = allAdminsWithoutServices.any { it.user is SelfUser } diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConversationDialog.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConversationDialog.kt index 0ff5998d140..13c2245398f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConversationDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConversationDialog.kt @@ -25,11 +25,10 @@ import com.wire.android.util.ui.PreviewMultipleThemes @Composable fun LegalHoldSubjectConversationDialog( - conversationName: String, dialogDismissed: () -> Unit, ) { LegalHoldSubjectBaseDialog( - title = stringResource(id = R.string.legal_hold_subject_dialog_title, conversationName), + title = stringResource(id = R.string.legal_hold_subject_conversation_dialog_title), customInfo = stringResource(id = R.string.legal_hold_subject_dialog_description_group), withDefaultInfo = true, cancelText = stringResource(id = R.string.label_close), @@ -41,6 +40,6 @@ fun LegalHoldSubjectConversationDialog( @PreviewMultipleThemes fun PreviewLegalHoldSubjectConversationDialog() { WireTheme { - LegalHoldSubjectConversationDialog("conversation name", {}) + LegalHoldSubjectConversationDialog {} } } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModel.kt index 9795b4b259d..4c137c93c67 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModel.kt @@ -35,6 +35,7 @@ import com.wire.android.ui.common.dialogs.BlockUserDialogState import com.wire.android.ui.home.conversations.details.participants.usecase.ObserveConversationRoleForUserUseCase import com.wire.android.ui.home.conversationslist.model.BlockState import com.wire.android.ui.home.conversationslist.model.DialogState +import com.wire.android.ui.home.conversationslist.showLegalHoldIndicator import com.wire.android.ui.navArgs import com.wire.android.ui.userprofile.common.UsernameMapper.mapUserLabel import com.wire.android.ui.userprofile.group.RemoveConversationMemberState @@ -415,7 +416,8 @@ class OtherUserProfileScreenViewModel @Inject constructor( isArchived = conversation.archived, protocol = conversation.protocol, mlsVerificationStatus = conversation.mlsVerificationStatus, - proteusVerificationStatus = conversation.proteusVerificationStatus + proteusVerificationStatus = conversation.proteusVerificationStatus, + isUnderLegalHold = conversation.legalHoldStatus.showLegalHoldIndicator(), ) } ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9b0be6c0d85..651f28d9661 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1336,6 +1336,7 @@ Legal hold deactivated Future messages will not be recorded. %1$s is subject to legal hold + Conversation is subject to legal hold You are subject to legal hold All messages, pictures, and documents will be preserved for future access. It includes deleted, edited, and self-deleting messages. At least one person in this conversation is subject to legal hold. diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt index 8a1ce62f2b0..4e239c67838 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModelTest.kt @@ -420,7 +420,8 @@ class GroupConversationDetailsViewModelTest { isArchived = false, protocol = Conversation.ProtocolInfo.Proteus, mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, - proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED + proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, + isUnderLegalHold = true, ) // When - Then assertEquals(expected, viewModel.conversationSheetContent) @@ -592,7 +593,7 @@ class GroupConversationDetailsViewModelTest { archivedDateTime = null, mlsVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, proteusVerificationStatus = Conversation.VerificationStatus.NOT_VERIFIED, - legalHoldStatus = Conversation.LegalHoldStatus.DISABLED + legalHoldStatus = Conversation.LegalHoldStatus.ENABLED ), hasOngoingCall = false, lastMessage = null, diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCaseTest.kt index 000ef758f72..a96c36cbeae 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCaseTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/details/participants/usecase/ObserveParticipantsForConversationUseCaseTest.kt @@ -27,9 +27,12 @@ import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.conversation.Conversation.Member import com.wire.kalium.logic.data.conversation.MemberDetails import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.data.user.type.UserType import com.wire.kalium.logic.feature.conversation.ObserveConversationMembersUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetMembersE2EICertificateStatusesUseCase +import com.wire.kalium.logic.feature.legalhold.MembersHavingLegalHoldClientUseCase +import com.wire.kalium.logic.functional.Either import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.impl.annotations.MockK @@ -82,6 +85,25 @@ class ObserveParticipantsForConversationUseCaseTest { assert(data.allParticipantsCount == members.size) } } + + @Test + fun givenGroupMembersUnderLegalHold_whenSolvingTheParticipantsList_thenPassCorrectLegalHoldValues() = runTest { + // Given + val memberUnderLegalHold = MemberDetails(testOtherUser(0).copy(userType = UserType.INTERNAL), Member.Role.Member) + val memberNotUnderLegalHold = MemberDetails(testOtherUser(1).copy(userType = UserType.INTERNAL), Member.Role.Member) + val (_, useCase) = ObserveParticipantsForConversationUseCaseArrangement() + .withConversationParticipantsUpdate(listOf(memberUnderLegalHold, memberNotUnderLegalHold)) + .withMembersHavingLegalHoldClient(listOf(memberUnderLegalHold.user.id)) + .arrange() + // When - Then + useCase(ConversationId("", "")).test { + val data = awaitItem() + for (participant in data.participants) { + val expected = participant.id == memberUnderLegalHold.user.id + assertEquals(expected, participant.isUnderLegalHold) + } + } + } } internal class ObserveParticipantsForConversationUseCaseArrangement { @@ -92,6 +114,9 @@ internal class ObserveParticipantsForConversationUseCaseArrangement { @MockK lateinit var getMembersE2EICertificateStatuses: GetMembersE2EICertificateStatusesUseCase + @MockK + lateinit var membersHavingLegalHoldClientUseCase: MembersHavingLegalHoldClientUseCase + @MockK private lateinit var wireSessionImageLoader: WireSessionImageLoader private val uIParticipantMapper by lazy { UIParticipantMapper(UserTypeMapper(), wireSessionImageLoader) } @@ -100,6 +125,7 @@ internal class ObserveParticipantsForConversationUseCaseArrangement { ObserveParticipantsForConversationUseCase( observeConversationMembersUseCase, getMembersE2EICertificateStatuses, + membersHavingLegalHoldClientUseCase, uIParticipantMapper, dispatchers = TestDispatcherProvider() ) @@ -111,6 +137,7 @@ internal class ObserveParticipantsForConversationUseCaseArrangement { // Default empty values coEvery { observeConversationMembersUseCase(any()) } returns flowOf() coEvery { getMembersE2EICertificateStatuses(any(), any()) } returns mapOf() + coEvery { membersHavingLegalHoldClientUseCase(any()) } returns Either.Right(emptyList()) } suspend fun withConversationParticipantsUpdate(members: List): ObserveParticipantsForConversationUseCaseArrangement { @@ -119,5 +146,9 @@ internal class ObserveParticipantsForConversationUseCaseArrangement { return this } + suspend fun withMembersHavingLegalHoldClient(members: List) = apply { + coEvery { membersHavingLegalHoldClientUseCase(any()) } returns Either.Right(members) + } + fun arrange() = this to useCase }