From f3e420a9c87bb684336f869d38784fb8dcb0793e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Fri, 2 Feb 2024 15:19:37 +0100 Subject: [PATCH 01/12] feat: legal hold indication on self profile [WPB-4780] --- .../com/wire/android/di/CoreLogicModule.kt | 10 +++ .../com/wire/android/ui/WireActivity.kt | 3 + .../legalhold/banner/LegalHoldBaseBanner.kt | 64 ++++++++++++++ .../banner/LegalHoldPendingBanner.kt | 63 +++++++++++++ .../banner/LegalHoldSubjectBanner.kt | 33 +------ .../requested/LegalHoldRequestedViewModel.kt | 2 +- .../subject/LegalHoldSubjectBaseDialog.kt | 4 +- .../LegalHoldSubjectConnectionDialog.kt | 2 +- .../LegalHoldSubjectConversationDialog.kt | 2 +- .../subject/LegalHoldSubjectMessageDialog.kt | 2 +- .../subject/LegalHoldSubjectProfileDialog.kt | 17 +++- .../userprofile/self/SelfUserProfileScreen.kt | 88 ++++++++++++++----- .../userprofile/self/SelfUserProfileState.kt | 4 +- .../self/SelfUserProfileViewModel.kt | 25 ++++++ app/src/main/res/values/strings.xml | 2 + 15 files changed, 258 insertions(+), 63 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldPendingBanner.kt 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 1e881239543..ed1a0390714 100644 --- a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt @@ -440,4 +440,14 @@ class UseCaseModule { fun provideObserveIsAppLockEditableUseCase( @KaliumCoreLogic coreLogic: CoreLogic ): ObserveIsAppLockEditableUseCase = coreLogic.getGlobalScope().observeIsAppLockEditableUseCase + + @ViewModelScoped + @Provides + fun provideObserveLegalHoldRequestUseCase(@KaliumCoreLogic coreLogic: CoreLogic, @CurrentAccount currentAccount: UserId) = + coreLogic.getSessionScope(currentAccount).observeLegalHoldRequest + + @ViewModelScoped + @Provides + fun provideObserveLegalHoldForSelfUserUseCase(@KaliumCoreLogic coreLogic: CoreLogic, @CurrentAccount currentAccount: UserId) = + coreLogic.getSessionScope(currentAccount).observeLegalHoldForSelfUser } diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt index 21f7cb8bad9..7245801c5fb 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt @@ -159,6 +159,9 @@ class WireActivity : AppCompatActivity() { appLogger.i("$TAG persistent connection status") viewModel.observePersistentConnectionStatus() + appLogger.i("$TAG legal hold requested status") + legalHoldRequestedViewModel.observeLegalHoldRequest() + appLogger.i("$TAG start destination") val startDestination = when (viewModel.initialAppState) { InitialAppState.NOT_MIGRATED -> MigrationScreenDestination diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt new file mode 100644 index 00000000000..d96843f2ff0 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt @@ -0,0 +1,64 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.legalhold.banner + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp +import com.wire.android.ui.common.LegalHoldIndicator +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions + +@Composable +fun LegalHoldBaseBanner( + onClick: () -> Unit = {}, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimensions().spacing8x), + modifier = modifier + .clip(RoundedCornerShape(dimensions().spacing12x)) + .background(colorsScheme().surface) + .border( + width = dimensions().spacing1x, + shape = RoundedCornerShape(dimensions().spacing12x), + color = colorsScheme().error + ) + .clickable(onClick = onClick) + .heightIn(min = 26.dp) + .padding( + horizontal = dimensions().spacing12x, + vertical = dimensions().spacing4x + ) + ) { + LegalHoldIndicator() + content() + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldPendingBanner.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldPendingBanner.kt new file mode 100644 index 00000000000..50e51b1f609 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldPendingBanner.kt @@ -0,0 +1,63 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.legalhold.banner + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextDecoration +import com.wire.android.R +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.typography +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.ui.PreviewMultipleThemes + +@Composable +fun LegalHoldPendingBanner( + onClick: () -> Unit = {}, + modifier: Modifier = Modifier, +) { + LegalHoldBaseBanner(onClick = onClick, modifier = modifier) { + Row { + Text( + text = stringResource(id = R.string.legal_hold_is_pending_label), + style = typography().label01, + color = colorsScheme().onSurface, + ) + Text( + text = stringResource(id = R.string.legal_hold_accept), + style = typography().label02, + textDecoration = TextDecoration.Underline, + color = colorsScheme().onSurface, + modifier = Modifier.padding(start = dimensions().spacing2x), + ) + } + } +} + +@Composable +@PreviewMultipleThemes +fun PreviewLegalHoldPendingBanner() { + WireTheme { + LegalHoldPendingBanner() + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldSubjectBanner.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldSubjectBanner.kt index 959c402b554..afb65eb15aa 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldSubjectBanner.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldSubjectBanner.kt @@ -17,26 +17,13 @@ */ package com.wire.android.ui.legalhold.banner -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.unit.dp import com.wire.android.R -import com.wire.android.ui.common.LegalHoldIndicator import com.wire.android.ui.common.colorsScheme -import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.typography import com.wire.android.ui.theme.WireTheme import com.wire.android.util.ui.PreviewMultipleThemes @@ -47,25 +34,7 @@ fun LegalHoldSubjectBanner( onClick: () -> Unit = {}, modifier: Modifier = Modifier, ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(dimensions().spacing8x), - modifier = modifier - .clip(RoundedCornerShape(dimensions().spacing12x)) - .background(colorsScheme().surface) - .border( - width = dimensions().spacing1x, - shape = RoundedCornerShape(dimensions().spacing12x), - color = colorsScheme().error - ) - .clickable(onClick = onClick) - .heightIn(min = 26.dp) - .padding( - horizontal = dimensions().spacing12x, - vertical = dimensions().spacing4x - ) - ) { - LegalHoldIndicator() + LegalHoldBaseBanner(onClick = onClick, modifier = modifier) { val resources = LocalContext.current.resources Text( text = resources.stringWithStyledArgs( diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt index 27e09d8ff6a..b306bc7ed66 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModel.kt @@ -91,7 +91,7 @@ class LegalHoldRequestedViewModel @Inject constructor( } } - init { + fun observeLegalHoldRequest() { viewModelScope.launch { legalHoldRequestDataStateFlow.collectLatest { legalHoldRequestData -> state = when (legalHoldRequestData) { diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectBaseDialog.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectBaseDialog.kt index 5f25ced75e4..78359354b3b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectBaseDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectBaseDialog.kt @@ -33,7 +33,7 @@ import com.wire.android.util.ui.PreviewMultipleThemes @Composable fun LegalHoldSubjectBaseDialog( - name: String, + title: String, customInfo: String? = null, withDefaultInfo: Boolean, cancelText: String, @@ -45,7 +45,7 @@ fun LegalHoldSubjectBaseDialog( if (withDefaultInfo) stringResource(id = R.string.legal_hold_subject_dialog_description) else null ).joinToString("\n\n") WireDialog( - title = stringResource(id = R.string.legal_hold_subject_dialog_title, name), + title = title, text = text, onDismiss = dialogDismissed, buttonsHorizontalAlignment = false, diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConnectionDialog.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConnectionDialog.kt index 937c2ee430c..e2fcf91eb4d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConnectionDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectConnectionDialog.kt @@ -30,7 +30,7 @@ fun LegalHoldSubjectConnectionDialog( connectClicked: () -> Unit, ) { LegalHoldSubjectBaseDialog( - name = userName, + title = stringResource(id = R.string.legal_hold_subject_dialog_title, userName), withDefaultInfo = true, cancelText = stringResource(id = R.string.label_cancel), dialogDismissed = dialogDismissed, 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 57d5b9e3ba5..0ff5998d140 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 @@ -29,7 +29,7 @@ fun LegalHoldSubjectConversationDialog( dialogDismissed: () -> Unit, ) { LegalHoldSubjectBaseDialog( - name = conversationName, + title = stringResource(id = R.string.legal_hold_subject_dialog_title, conversationName), customInfo = stringResource(id = R.string.legal_hold_subject_dialog_description_group), withDefaultInfo = true, cancelText = stringResource(id = R.string.label_close), diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectMessageDialog.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectMessageDialog.kt index a57b7935190..b670d654c3c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectMessageDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectMessageDialog.kt @@ -30,7 +30,7 @@ fun LegalHoldSubjectMessageDialog( sendAnywayClicked: () -> Unit, ) { LegalHoldSubjectBaseDialog( - name = conversationName, + title = stringResource(id = R.string.legal_hold_subject_dialog_title, conversationName), customInfo = stringResource(id = R.string.legal_hold_subject_dialog_description_message), withDefaultInfo = false, cancelText = stringResource(id = R.string.label_cancel), diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectProfileDialog.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectProfileDialog.kt index 63de0ea519a..e2e8f707421 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectProfileDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/dialog/subject/LegalHoldSubjectProfileDialog.kt @@ -29,7 +29,15 @@ fun LegalHoldSubjectProfileDialog( dialogDismissed: () -> Unit, ) { LegalHoldSubjectBaseDialog( - name = userName, + title = stringResource(id = R.string.legal_hold_subject_dialog_title, userName), + withDefaultInfo = true, + cancelText = stringResource(id = R.string.label_close), + dialogDismissed = dialogDismissed) +} +@Composable +fun LegalHoldSubjectProfileSelfDialog(dialogDismissed: () -> Unit) { + LegalHoldSubjectBaseDialog( + title = stringResource(id = R.string.legal_hold_subject_self_dialog_title), withDefaultInfo = true, cancelText = stringResource(id = R.string.label_close), dialogDismissed = dialogDismissed) @@ -42,3 +50,10 @@ fun PreviewLegalHoldSubjectProfileDialog() { LegalHoldSubjectProfileDialog("username", {}) } } +@Composable +@PreviewMultipleThemes +fun PreviewLegalHoldSubjectProfileSelfDialog() { + WireTheme { + LegalHoldSubjectProfileSelfDialog {} + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt index a7b3c290e0b..ab033dc607d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties import androidx.hilt.navigation.compose.hiltViewModel @@ -65,6 +64,7 @@ import com.wire.android.ui.common.ArrowRightIcon import com.wire.android.ui.common.RowItemTemplate import com.wire.android.ui.common.UserProfileAvatar import com.wire.android.ui.common.UserStatusIndicator +import com.wire.android.ui.common.VisibilityState import com.wire.android.ui.common.WireDropDown import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.button.WirePrimaryButton @@ -72,6 +72,7 @@ import com.wire.android.ui.common.button.WireSecondaryButton import com.wire.android.ui.common.dialogs.ProgressDialog import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.scaffold.WireScaffold +import com.wire.android.ui.common.snackbar.LocalSnackbarHostState import com.wire.android.ui.common.topappbar.NavigationIconType import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar import com.wire.android.ui.common.visbility.rememberVisibilityState @@ -81,8 +82,14 @@ import com.wire.android.ui.destinations.WelcomeScreenDestination 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.common.FolderHeader +import com.wire.android.ui.legalhold.banner.LegalHoldPendingBanner +import com.wire.android.ui.legalhold.banner.LegalHoldSubjectBanner +import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedDialog +import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedState +import com.wire.android.ui.legalhold.dialog.requested.LegalHoldRequestedViewModel +import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectProfileSelfDialog import com.wire.android.ui.theme.WireTheme -import com.wire.android.ui.common.snackbar.LocalSnackbarHostState import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.userprofile.common.EditableState import com.wire.android.ui.userprofile.common.UserProfileInfo @@ -104,8 +111,11 @@ import com.wire.kalium.logic.data.user.UserId fun SelfUserProfileScreen( navigator: Navigator, viewModelSelf: SelfUserProfileViewModel = hiltViewModel(), + legalHoldRequestedViewModel: LegalHoldRequestedViewModel = hiltViewModel(), avatarPickerResultRecipient: ResultRecipient ) { + val legalHoldSubjectDialogState = rememberVisibilityState() + SelfUserProfileContent( state = viewModelSelf.userProfileState, onCloseClick = navigator::navigateBack, @@ -118,7 +128,8 @@ fun SelfUserProfileScreen( onStatusChange = viewModelSelf::changeStatus, onNotShowRationaleAgainChange = viewModelSelf::dialogCheckBoxStateChanged, onMessageShown = viewModelSelf::clearErrorMessage, - onMaxAccountReachedDialogDismissed = viewModelSelf::onMaxAccountReachedDialogDismissed, + onLegalHoldAcceptClick = legalHoldRequestedViewModel::show, + onLegalHoldLearnMoreClick = remember { { legalHoldSubjectDialogState.show(Unit) } }, onOtherAccountClick = { viewModelSelf.switchAccount(it, NavigationSwitchAccountActions(navigator::navigate)) }, isUserInCall = viewModelSelf::isUserInCall ) @@ -138,6 +149,18 @@ fun SelfUserProfileScreen( } } } + + if (legalHoldRequestedViewModel.state is LegalHoldRequestedState.Visible) { + LegalHoldRequestedDialog( + state = legalHoldRequestedViewModel.state as LegalHoldRequestedState.Visible, + passwordChanged = legalHoldRequestedViewModel::passwordChanged, + notNowClicked = legalHoldRequestedViewModel::notNowClicked, + acceptClicked = legalHoldRequestedViewModel::acceptClicked, + ) + } + VisibilityState(legalHoldSubjectDialogState) { + LegalHoldSubjectProfileSelfDialog(legalHoldSubjectDialogState::dismiss) + } } @OptIn(ExperimentalFoundationApi::class) @@ -154,7 +177,8 @@ private fun SelfUserProfileContent( onStatusChange: (UserAvailabilityStatus) -> Unit = {}, onNotShowRationaleAgainChange: (Boolean) -> Unit = {}, onMessageShown: () -> Unit = {}, - onMaxAccountReachedDialogDismissed: () -> Unit = {}, + onLegalHoldAcceptClick: () -> Unit = {}, + onLegalHoldLearnMoreClick: () -> Unit = {}, onOtherAccountClick: (UserId) -> Unit = {}, isUserInCall: () -> Boolean ) { @@ -209,6 +233,20 @@ private fun SelfUserProfileContent( else EditableState.IsEditable(onEditClick) ) } + if (state.legalHoldStatus != LegalHoldUIState.None) { + stickyHeader { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxWidth().padding(top = dimensions().spacing8x) + ) { + when (state.legalHoldStatus) { + LegalHoldUIState.Active -> LegalHoldSubjectBanner(onLegalHoldLearnMoreClick) + LegalHoldUIState.Pending -> LegalHoldPendingBanner(onLegalHoldAcceptClick) + LegalHoldUIState.None -> { /* no banner */ } + } + } + } + } if (!state.teamName.isNullOrBlank()) { stickyHeader { CurrentSelfUserStatus( @@ -416,32 +454,36 @@ private fun LoggingOutDialog(isLoggingOut: Boolean) { } } -@Preview(widthDp = 400, heightDp = 800) -@Preview(widthDp = 800) +@PreviewMultipleThemes @Composable fun PreviewSelfUserProfileScreen() { - SelfUserProfileContent( - SelfUserProfileState( - userId = UserId("value", "domain"), - status = UserAvailabilityStatus.BUSY, - fullName = "Tester Tost_long_long_long long long long long long long ", - userName = "userName_long_long_long_long_long_long_long_long_long_long", - teamName = "Best team ever long long long long long long long long long ", - otherAccounts = listOf( - OtherAccount(id = UserId("id1", "domain"), fullName = "Other Name", teamName = "team A"), - OtherAccount(id = UserId("id2", "domain"), fullName = "New Name") + WireTheme { + SelfUserProfileContent( + SelfUserProfileState( + userId = UserId("value", "domain"), + status = UserAvailabilityStatus.BUSY, + fullName = "Tester Tost_long_long_long long long long long long long ", + userName = "userName_long_long_long_long_long_long_long_long_long_long", + teamName = "Best team ever long long long long long long long long long ", + otherAccounts = listOf( + OtherAccount(id = UserId("id1", "domain"), fullName = "Other Name", teamName = "team A"), + OtherAccount(id = UserId("id2", "domain"), fullName = "New Name") + ), + statusDialogData = null, + legalHoldStatus = LegalHoldUIState.Active, ), - statusDialogData = null - ), - isUserInCall = { false } - ) + isUserInCall = { false } + ) + } + } -@Preview(widthDp = 800) -@Preview(widthDp = 400) +@PreviewMultipleThemes @Composable fun PreviewCurrentSelfUserStatus() { - CurrentSelfUserStatus(UserAvailabilityStatus.AVAILABLE, onStatusClicked = {}) + WireTheme { + CurrentSelfUserStatus(UserAvailabilityStatus.AVAILABLE, onStatusClicked = {}) + } } @Composable diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileState.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileState.kt index d6126d92361..58b9b51dd37 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileState.kt @@ -19,6 +19,7 @@ package com.wire.android.ui.userprofile.self import com.wire.android.model.ImageAsset.UserAvatarAsset +import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.ui.userprofile.self.SelfUserProfileViewModel.ErrorCodes import com.wire.android.ui.userprofile.self.dialog.StatusDialogData import com.wire.android.ui.userprofile.self.model.OtherAccount @@ -38,5 +39,6 @@ data class SelfUserProfileState constructor( val isAvatarLoading: Boolean = false, val maxAccountsReached: Boolean = false, // todo. cleanup unused code val isReadOnlyAccount: Boolean = true, - val isLoggingOut: Boolean = false + val isLoggingOut: Boolean = false, + val legalHoldStatus: LegalHoldUIState = LegalHoldUIState.None, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt index 049607eaa6f..98dd577a9b5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt @@ -37,6 +37,7 @@ import com.wire.android.mapper.OtherAccountMapper import com.wire.android.model.ImageAsset.UserAvatarAsset import com.wire.android.notification.NotificationChannelsManager import com.wire.android.notification.WireNotificationManager +import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.ui.userprofile.self.dialog.StatusDialogData import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.WireSessionImageLoader @@ -53,6 +54,9 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.LogoutUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldState +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldForSelfUserUseCase +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase @@ -63,6 +67,7 @@ import com.wire.kalium.logic.functional.getOrNull import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first @@ -85,6 +90,8 @@ class SelfUserProfileViewModel @Inject constructor( private val observeValidAccounts: ObserveValidAccountsUseCase, private val updateStatus: UpdateSelfAvailabilityStatusUseCase, private val logout: LogoutUseCase, + private val observeLegalHoldRequest: ObserveLegalHoldRequestUseCase, + private val observeLegalHoldForSelfUser: ObserveLegalHoldForSelfUserUseCase, private val dispatchers: DispatcherProvider, private val wireSessionImageLoader: WireSessionImageLoader, private val authServerConfigProvider: AuthServerConfigProvider, @@ -110,6 +117,7 @@ class SelfUserProfileViewModel @Inject constructor( fetchSelfUser() observeEstablishedCall() fetchIsReadOnlyAccount() + observeLegalHoldStatus() } } @@ -168,6 +176,23 @@ class SelfUserProfileViewModel @Inject constructor( } } + private fun observeLegalHoldStatus() { + viewModelScope.launch { + combine( + observeLegalHoldRequest(), + observeLegalHoldForSelfUser() + ) { legalHoldRequestStatus: ObserveLegalHoldRequestUseCase.Result, legalHoldStatus: LegalHoldState -> + when { + legalHoldRequestStatus is ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable -> LegalHoldUIState.Pending + legalHoldStatus is LegalHoldState.Enabled -> LegalHoldUIState.Active + else -> LegalHoldUIState.None + } + } + .distinctUntilChanged() + .collectLatest { userProfileState = userProfileState.copy(legalHoldStatus = it) } + } + } + private fun showErrorMessage() { userProfileState = userProfileState.copy(errorMessageCode = ErrorCodes.DownloadUserInfoError) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98ca13e53a6..4198d745f5b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1337,6 +1337,7 @@ Legal hold deactivated Future messages will not be recorded. %1$s 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. Do you still want to send your message? @@ -1347,6 +1348,7 @@ legal hold Legal hold is active Legal hold is pending + Accept You are now subject to legal hold. Legal hold activated for %1$s. You are no longer subject to legal hold. From c5bea0f5731083cec37ef90144c4d7de91e6217a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Fri, 2 Feb 2024 16:47:58 +0100 Subject: [PATCH 02/12] feat: legal hold indication on other profile [WPB-4780] --- .../com/wire/android/di/CoreLogicModule.kt | 5 + .../ui/userprofile/common/UserProfileInfo.kt | 13 --- .../other/OtherUserProfileScreen.kt | 110 ++++++++++++++---- .../other/OtherUserProfileScreenViewModel.kt | 12 ++ .../other/OtherUserProfileState.kt | 3 +- 5 files changed, 104 insertions(+), 39 deletions(-) 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 ed1a0390714..bea34f22067 100644 --- a/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/CoreLogicModule.kt @@ -450,4 +450,9 @@ class UseCaseModule { @Provides fun provideObserveLegalHoldForSelfUserUseCase(@KaliumCoreLogic coreLogic: CoreLogic, @CurrentAccount currentAccount: UserId) = coreLogic.getSessionScope(currentAccount).observeLegalHoldForSelfUser + + @ViewModelScoped + @Provides + fun provideObserveLegalHoldForUserUseCase(@KaliumCoreLogic coreLogic: CoreLogic, @CurrentAccount currentAccount: UserId) = + coreLogic.getSessionScope(currentAccount).observeLegalHoldStateForUser } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt index f15cb501bb2..d96abf4afcd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt @@ -61,8 +61,6 @@ import com.wire.android.ui.common.UserProfileAvatar import com.wire.android.ui.common.banner.SecurityClassificationBannerForUser import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.progress.WireCircularProgressIndicator -import com.wire.android.ui.common.spacers.VerticalSpace -import com.wire.android.ui.home.conversations.details.SearchAndMediaRow import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography @@ -91,9 +89,6 @@ fun UserProfileInfo( delayToShowPlaceholderIfNoAsset: Duration = 200.milliseconds, isProteusVerified: Boolean = false, isMLSVerified: Boolean = false, - onSearchConversationMessagesClick: () -> Unit = {}, - onConversationMediaClick: () -> Unit = {}, - shouldShowSearchButton: Boolean = false ) { Column( horizontalAlignment = CenterHorizontally, @@ -233,14 +228,6 @@ fun UserProfileInfo( modifier = Modifier.padding(top = dimensions().spacing8x) ) } - - if (shouldShowSearchButton) { - VerticalSpace.x24() - SearchAndMediaRow( - onSearchConversationMessagesClick = onSearchConversationMessagesClick, - onConversationMediaClick = onConversationMediaClick - ) - } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt index e7dd1482b01..a05f9898bf0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt @@ -48,10 +48,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -67,6 +67,7 @@ import com.wire.android.ui.authentication.devices.model.Device import com.wire.android.ui.common.CollapsingTopBarScaffold import com.wire.android.ui.common.MoreOptionIcon 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.WireModalSheetState @@ -80,6 +81,7 @@ import com.wire.android.ui.common.dialogs.UnblockUserDialogContent import com.wire.android.ui.common.dialogs.UnblockUserDialogState import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.snackbar.LocalSnackbarHostState +import com.wire.android.ui.common.spacers.VerticalSpace import com.wire.android.ui.common.topBarElevation import com.wire.android.ui.common.topappbar.NavigationIconType import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar @@ -89,9 +91,12 @@ import com.wire.android.ui.destinations.ConversationMediaScreenDestination import com.wire.android.ui.destinations.ConversationScreenDestination import com.wire.android.ui.destinations.DeviceDetailsScreenDestination import com.wire.android.ui.destinations.SearchConversationMessagesScreenDestination +import com.wire.android.ui.home.conversations.details.SearchAndMediaRow import com.wire.android.ui.home.conversations.details.dialog.ClearConversationContentDialog import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.Membership +import com.wire.android.ui.legalhold.banner.LegalHoldSubjectBanner +import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectProfileDialog import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions @@ -100,6 +105,7 @@ import com.wire.android.ui.userprofile.common.UserProfileInfo import com.wire.android.ui.userprofile.group.RemoveConversationMemberState import com.wire.android.ui.userprofile.other.bottomsheet.OtherUserBottomSheetState import com.wire.android.ui.userprofile.other.bottomsheet.OtherUserProfileBottomSheetContent +import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.ConnectionState import kotlinx.coroutines.CoroutineScope @@ -154,6 +160,8 @@ fun OtherUserProfileScreen( } } + val legalHoldSubjectDialogState = rememberVisibilityState() + OtherProfileScreenContent( scope = scope, state = viewModel.state, @@ -172,7 +180,8 @@ fun OtherUserProfileScreen( onSearchConversationMessagesClick = onSearchConversationMessagesClick, navigateBack = navigator::navigateBack, navigationIconType = NavigationIconType.Close, - onConversationMediaClick = onConversationMediaClick + onConversationMediaClick = onConversationMediaClick, + onLegalHoldLearnMoreClick = remember { { legalHoldSubjectDialogState.show(Unit) } }, ) LaunchedEffect(Unit) { @@ -185,6 +194,10 @@ fun OtherUserProfileScreen( sheetState.hide() } } + + VisibilityState(legalHoldSubjectDialogState) { + LegalHoldSubjectProfileDialog(viewModel.state.userName, legalHoldSubjectDialogState::dismiss) + } } @OptIn(ExperimentalFoundationApi::class) @@ -205,7 +218,8 @@ fun OtherProfileScreenContent( onOpenDeviceDetails: (Device) -> Unit = {}, onSearchConversationMessagesClick: () -> Unit, onConversationMediaClick: () -> Unit = {}, - navigateBack: () -> Unit = {} + navigateBack: () -> Unit = {}, + onLegalHoldLearnMoreClick: () -> Unit = {}, ) { val otherUserProfileScreenState = rememberOtherUserProfileScreenState() val blockUserDialogState = rememberVisibilityState() @@ -283,7 +297,8 @@ fun OtherProfileScreenContent( TopBarCollapsing( state = state, onSearchConversationMessagesClick = onSearchConversationMessagesClick, - onConversationMediaClick = onConversationMediaClick + onConversationMediaClick = onConversationMediaClick, + onLegalHoldLearnMoreClick = onLegalHoldLearnMoreClick, ) }, topBarFooter = { TopBarFooter(state, pagerState, tabBarElevationState, tabItems, currentTabState, scope) }, @@ -384,29 +399,44 @@ private fun TopBarHeader( private fun TopBarCollapsing( state: OtherUserProfileState, onSearchConversationMessagesClick: () -> Unit, - onConversationMediaClick: () -> Unit = {} + onConversationMediaClick: () -> Unit = {}, + onLegalHoldLearnMoreClick: () -> Unit = {}, ) { Crossfade( targetState = state, label = "OtherUserProfileScreenTopBarCollapsing" ) { targetState -> - UserProfileInfo( - userId = targetState.userId, - isLoading = targetState.isAvatarLoading, - avatarAsset = targetState.userAvatarAsset, - fullName = targetState.fullName, - userName = targetState.userName, - teamName = targetState.teamName, - membership = targetState.membership, - editableState = EditableState.NotEditable, + Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(bottom = dimensions().spacing16x), - connection = targetState.connectionState, - isProteusVerified = targetState.isProteusVerified, - isMLSVerified = targetState.isMLSVerified, - onSearchConversationMessagesClick = onSearchConversationMessagesClick, - shouldShowSearchButton = state.shouldShowSearchButton(), - onConversationMediaClick = onConversationMediaClick - ) + ) { + UserProfileInfo( + userId = targetState.userId, + isLoading = targetState.isAvatarLoading, + avatarAsset = targetState.userAvatarAsset, + fullName = targetState.fullName, + userName = targetState.userName, + teamName = targetState.teamName, + membership = targetState.membership, + editableState = EditableState.NotEditable, + connection = targetState.connectionState, + isProteusVerified = targetState.isProteusVerified, + isMLSVerified = targetState.isMLSVerified, + ) + if (state.isUnderLegalHold) { + LegalHoldSubjectBanner( + onClick = onLegalHoldLearnMoreClick, + modifier = Modifier.padding(top = dimensions().spacing8x) + ) + } + if (state.shouldShowSearchButton()) { + VerticalSpace.x24() + SearchAndMediaRow( + onSearchConversationMessagesClick = onSearchConversationMessagesClick, + onConversationMediaClick = onConversationMediaClick + ) + } + } } } @@ -549,12 +579,39 @@ enum class OtherUserProfileTabItem(@StringRes override val titleResId: Int) : Ta @OptIn(ExperimentalMaterial3Api::class) @Composable -@Preview(name = "Connected") +@PreviewMultipleThemes +fun PreviewOtherProfileScreenGroupMemberContent() { + WireTheme { + OtherProfileScreenContent( + scope = rememberCoroutineScope(), + state = OtherUserProfileState.PREVIEW.copy( + connectionState = ConnectionState.ACCEPTED, + isUnderLegalHold = true, + ), + navigationIconType = NavigationIconType.Back, + requestInProgress = false, + sheetState = rememberWireModalSheetState(), + openBottomSheet = {}, + closeBottomSheet = {}, + eventsHandler = OtherUserProfileEventsHandler.PREVIEW, + bottomSheetEventsHandler = OtherUserProfileBottomSheetEventsHandler.PREVIEW, + onSearchConversationMessagesClick = {} + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@PreviewMultipleThemes fun PreviewOtherProfileScreenContent() { WireTheme { OtherProfileScreenContent( scope = rememberCoroutineScope(), - state = OtherUserProfileState.PREVIEW.copy(connectionState = ConnectionState.ACCEPTED), + state = OtherUserProfileState.PREVIEW.copy( + connectionState = ConnectionState.ACCEPTED, + isUnderLegalHold = true, + groupState = null + ), navigationIconType = NavigationIconType.Back, requestInProgress = false, sheetState = rememberWireModalSheetState(), @@ -569,12 +626,15 @@ fun PreviewOtherProfileScreenContent() { @OptIn(ExperimentalMaterial3Api::class) @Composable -@Preview(name = "Not Connected") +@PreviewMultipleThemes fun PreviewOtherProfileScreenContentNotConnected() { WireTheme { OtherProfileScreenContent( scope = rememberCoroutineScope(), - state = OtherUserProfileState.PREVIEW.copy(connectionState = ConnectionState.CANCELLED), + state = OtherUserProfileState.PREVIEW.copy( + connectionState = ConnectionState.CANCELLED, + isUnderLegalHold = true, + ), navigationIconType = NavigationIconType.Back, requestInProgress = false, sheetState = rememberWireModalSheetState(), 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 654aa596a3b..9795b4b259d 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 @@ -72,12 +72,15 @@ import com.wire.kalium.logic.feature.e2ei.CertificateStatus import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusResult import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificatesUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldState +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldStateForUserUseCase import com.wire.kalium.logic.feature.user.GetUserInfoResult import com.wire.kalium.logic.feature.user.ObserveUserInfoUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn @@ -96,6 +99,7 @@ class OtherUserProfileScreenViewModel @Inject constructor( private val unblockUser: UnblockUserUseCase, private val observeOneToOneConversation: GetOneToOneConversationUseCase, private val observeUserInfo: ObserveUserInfoUseCase, + private val observeLegalHoldStateForUser: ObserveLegalHoldStateForUserUseCase, private val userTypeMapper: UserTypeMapper, private val wireSessionImageLoader: WireSessionImageLoader, private val observeConversationRoleForUser: ObserveConversationRoleForUserUseCase, @@ -134,6 +138,14 @@ class OtherUserProfileScreenViewModel @Inject constructor( observeUserInfoAndUpdateViewState() persistClients() getMLSVerificationStatus() + observeLegalHoldStatus() + } + + private fun observeLegalHoldStatus() { + viewModelScope.launch { + observeLegalHoldStateForUser(userId) + .collectLatest { state = state.copy(isUnderLegalHold = it is LegalHoldState.Enabled) } + } } private fun getMLSVerificationStatus() { diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileState.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileState.kt index a8b751828d3..022638ee4fe 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileState.kt @@ -49,7 +49,8 @@ data class OtherUserProfileState( val otherUserDevices: List = listOf(), val blockingState: BlockingState = BlockingState.CAN_NOT_BE_BLOCKED, val isProteusVerified: Boolean = false, - val isMLSVerified: Boolean = false + val isMLSVerified: Boolean = false, + val isUnderLegalHold: Boolean = false, ) { fun updateMuteStatus(status: MutedConversationStatus): OtherUserProfileState { return conversationSheetContent?.let { From 1adeee3d5c00d641ecfabdee677944a9f4aa75c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Fri, 2 Feb 2024 17:06:28 +0100 Subject: [PATCH 03/12] fix detekt --- .../wire/android/ui/userprofile/self/SelfUserProfileScreen.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt index ab033dc607d..cf508c968e4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt @@ -475,7 +475,6 @@ fun PreviewSelfUserProfileScreen() { isUserInCall = { false } ) } - } @PreviewMultipleThemes From d4cbedbd91bba9ed84e826ad6b9b47dd7e280aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Fri, 2 Feb 2024 18:07:24 +0100 Subject: [PATCH 04/12] tests --- .../OtherUserProfileScreenViewModelTest.kt | 23 +++ .../OtherUserProfileViewModelArrangement.kt | 11 ++ .../SelfUserProfileViewModelArrangement.kt | 141 ++++++++++++++++++ .../self/SelfUserProfileViewModelTest.kt | 68 +++++++++ 4 files changed, 243 insertions(+) create mode 100644 app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt create mode 100644 app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModelTest.kt index 78473500128..9250c3228ec 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreenViewModelTest.kt @@ -40,6 +40,7 @@ import com.wire.kalium.logic.data.user.type.UserType import com.wire.kalium.logic.feature.connection.BlockUserResult import com.wire.kalium.logic.feature.conversation.GetOneToOneConversationUseCase import com.wire.kalium.logic.feature.conversation.UpdateConversationMemberRoleResult +import com.wire.kalium.logic.feature.legalhold.LegalHoldState import com.wire.kalium.logic.feature.user.GetUserInfoResult import io.mockk.Called import io.mockk.coVerify @@ -203,6 +204,28 @@ class OtherUserProfileScreenViewModelTest { assertEquals(null, viewModel.state.conversationSheetContent) } + @Test + fun `given legal hold enabled, then isUnderLegalHold is true`() = runTest { + // given + val (_, viewModel) = OtherUserProfileViewModelArrangement() + .withUserInfo(GetUserInfoResult.Success(OTHER_USER.copy(connectionStatus = ConnectionState.NOT_CONNECTED), TEAM)) + .withLegalHoldState(LegalHoldState.Enabled) + .arrange() + // then + assertEquals(true, viewModel.state.isUnderLegalHold) + } + + @Test + fun `given legal hold disabled, then isUnderLegalHold is false`() = runTest { + // given + val (_, viewModel) = OtherUserProfileViewModelArrangement() + .withUserInfo(GetUserInfoResult.Success(OTHER_USER.copy(connectionStatus = ConnectionState.NOT_CONNECTED), TEAM)) + .withLegalHoldState(LegalHoldState.Disabled) + .arrange() + // then + assertEquals(false, viewModel.state.isUnderLegalHold) + } + companion object { val USER_ID = UserId("some_value", "some_domain") val CONVERSATION_ID = ConversationId("some_value", "some_domain") diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileViewModelArrangement.kt index 1729d4aa114..cf090fb8556 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileViewModelArrangement.kt @@ -47,6 +47,8 @@ import com.wire.kalium.logic.feature.e2ei.CertificateStatus import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusResult import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificateStatusUseCase import com.wire.kalium.logic.feature.e2ei.usecase.GetUserE2eiCertificatesUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldState +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldStateForUserUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.GetUserInfoResult import com.wire.kalium.logic.feature.user.ObserveUserInfoUseCase @@ -67,6 +69,9 @@ internal class OtherUserProfileViewModelArrangement { @MockK lateinit var observeUserInfo: ObserveUserInfoUseCase + @MockK + lateinit var observeLegalHoldStateForUser: ObserveLegalHoldStateForUserUseCase + @MockK lateinit var wireSessionImageLoader: WireSessionImageLoader @@ -120,6 +125,7 @@ internal class OtherUserProfileViewModelArrangement { unblockUser, getOneToOneConversation, observeUserInfo, + observeLegalHoldStateForUser, userTypeMapper, wireSessionImageLoader, observeConversationRoleForUserUseCase, @@ -161,6 +167,7 @@ internal class OtherUserProfileViewModelArrangement { ) coEvery { getUserE2eiCertificateStatus.invoke(any()) } returns GetUserE2eiCertificateStatusResult.Success(CertificateStatus.VALID) coEvery { getUserE2eiCertificates.invoke(any()) } returns mapOf() + coEvery { observeLegalHoldStateForUser.invoke(any()) } returns flowOf(LegalHoldState.Disabled) } suspend fun withBlockUserResult(result: BlockUserResult) = apply { @@ -186,5 +193,9 @@ internal class OtherUserProfileViewModelArrangement { coEvery { observeUserInfo(any()) } returns flowOf(result) } + fun withLegalHoldState(result: LegalHoldState) = apply { + coEvery { observeLegalHoldStateForUser.invoke(any()) } returns flowOf(result) + } + fun arrange() = this to viewModel } diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt new file mode 100644 index 00000000000..b9936d4e9d0 --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt @@ -0,0 +1,141 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.userprofile.self + +import com.wire.android.config.TestDispatcherProvider +import com.wire.android.config.mockUri +import com.wire.android.datastore.GlobalDataStore +import com.wire.android.datastore.UserDataStore +import com.wire.android.di.AuthServerConfigProvider +import com.wire.android.feature.AccountSwitchUseCase +import com.wire.android.framework.TestTeam +import com.wire.android.framework.TestUser +import com.wire.android.mapper.OtherAccountMapper +import com.wire.android.notification.NotificationChannelsManager +import com.wire.android.notification.WireNotificationManager +import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.android.util.ui.WireSessionImageLoader +import com.wire.kalium.logic.data.id.QualifiedIdMapper +import com.wire.kalium.logic.feature.auth.LogoutUseCase +import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase +import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldState +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldForSelfUserUseCase +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase +import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase +import com.wire.kalium.logic.feature.user.GetSelfUserUseCase +import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase +import com.wire.kalium.logic.feature.user.ObserveValidAccountsUseCase +import com.wire.kalium.logic.feature.user.SelfServerConfigUseCase +import com.wire.kalium.logic.feature.user.UpdateSelfAvailabilityStatusUseCase +import com.wire.kalium.logic.functional.Either +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.flowOf + +class SelfUserProfileViewModelArrangement { + @MockK + lateinit var userDataStore: UserDataStore + @MockK + lateinit var getSelf: GetSelfUserUseCase + @MockK + lateinit var getSelfTeam: GetUpdatedSelfTeamUseCase + @MockK + lateinit var observeValidAccounts: ObserveValidAccountsUseCase + @MockK + lateinit var updateStatus: UpdateSelfAvailabilityStatusUseCase + @MockK + lateinit var logout: LogoutUseCase + @MockK + lateinit var observeLegalHoldRequest: ObserveLegalHoldRequestUseCase + @MockK + lateinit var observeLegalHoldForSelfUser: ObserveLegalHoldForSelfUserUseCase + @MockK + lateinit var dispatchers: DispatcherProvider + @MockK + lateinit var wireSessionImageLoader: WireSessionImageLoader + @MockK + lateinit var authServerConfigProvider: AuthServerConfigProvider + @MockK + lateinit var selfServerLinks: SelfServerConfigUseCase + @MockK + lateinit var otherAccountMapper: OtherAccountMapper + @MockK + lateinit var observeEstablishedCalls: ObserveEstablishedCallsUseCase + @MockK + lateinit var accountSwitch: AccountSwitchUseCase + @MockK + lateinit var endCall: EndCallUseCase + @MockK + lateinit var isReadOnlyAccount: IsReadOnlyAccountUseCase + @MockK + lateinit var notificationChannelsManager: NotificationChannelsManager + @MockK + lateinit var notificationManager: WireNotificationManager + @MockK + lateinit var globalDataStore: GlobalDataStore + @MockK + lateinit var qualifiedIdMapper: QualifiedIdMapper + + private val viewModel by lazy { + SelfUserProfileViewModel( + selfUserId = TestUser.SELF_USER.id, + dataStore = userDataStore, + getSelf = getSelf, + getSelfTeam = getSelfTeam, + observeValidAccounts = observeValidAccounts, + updateStatus = updateStatus, + logout = logout, + observeLegalHoldRequest = observeLegalHoldRequest, + observeLegalHoldForSelfUser = observeLegalHoldForSelfUser, + dispatchers = TestDispatcherProvider(), + wireSessionImageLoader = wireSessionImageLoader, + authServerConfigProvider = authServerConfigProvider, + selfServerLinks = selfServerLinks, + otherAccountMapper = otherAccountMapper, + observeEstablishedCalls = observeEstablishedCalls, + accountSwitch = accountSwitch, + endCall = endCall, + isReadOnlyAccount = isReadOnlyAccount, + notificationChannelsManager = notificationChannelsManager, + notificationManager = notificationManager, + globalDataStore = globalDataStore, + qualifiedIdMapper = qualifiedIdMapper + ) + } + + init { + MockKAnnotations.init(this, relaxUnitFun = true) + mockUri() + + coEvery { getSelf.invoke() } returns flowOf(TestUser.SELF_USER) + coEvery { getSelfTeam.invoke() } returns Either.Right(TestTeam.TEAM) + coEvery { observeValidAccounts.invoke() } returns flowOf(listOf(TestUser.SELF_USER to TestTeam.TEAM)) + coEvery { isReadOnlyAccount.invoke() } returns false + coEvery { observeEstablishedCalls.invoke() } returns flowOf(emptyList()) + } + + fun withLegalHoldRequest(result: ObserveLegalHoldRequestUseCase.Result) = apply { + coEvery { observeLegalHoldRequest.invoke() } returns flowOf(result) + } + fun withLegalHold(result: LegalHoldState) = apply { + coEvery { observeLegalHoldForSelfUser.invoke() } returns flowOf(result) + } + fun arrange() = this to viewModel +} diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt new file mode 100644 index 00000000000..8b6c0095d6a --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt @@ -0,0 +1,68 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.userprofile.self + +import com.wire.android.config.CoroutineTestExtension +import com.wire.android.config.NavigationTestExtension +import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.kalium.logic.feature.legalhold.LegalHoldState +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.internal.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(CoroutineTestExtension::class) +@ExtendWith(NavigationTestExtension::class) +class SelfUserProfileViewModelTest { + + @Test + fun `given legal hold request available, then isUnderLegalHold is pending`() = runTest { + // given + val (_, viewModel) = SelfUserProfileViewModelArrangement() + .withLegalHold(LegalHoldState.Enabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable("fingerprint".toByteArray())) + .arrange() + // then + assertEquals(LegalHoldUIState.Pending, viewModel.userProfileState.legalHoldStatus) + } + + @Test + fun `given legal hold enabled, then isUnderLegalHold is active`() = runTest { + // given + val (_, viewModel) = SelfUserProfileViewModelArrangement() + .withLegalHold(LegalHoldState.Enabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .arrange() + // then + assertEquals(LegalHoldUIState.Active, viewModel.userProfileState.legalHoldStatus) + } + + @Test + fun `given legal hold disabled and no request available, then isUnderLegalHold is none`() = runTest { + // given + val (_, viewModel) = SelfUserProfileViewModelArrangement() + .withLegalHold(LegalHoldState.Disabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .arrange() + // then + assertEquals(LegalHoldUIState.None, viewModel.userProfileState.legalHoldStatus) + } +} From cdf76e3b7a0359116475d358ca26ac78c49df00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Fri, 2 Feb 2024 19:05:44 +0100 Subject: [PATCH 05/12] fix tests --- .../dialog/requested/LegalHoldRequestedViewModelTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/test/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModelTest.kt index 2da904421b9..3c6cc6fb916 100644 --- a/app/src/test/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/legalhold/dialog/requested/LegalHoldRequestedViewModelTest.kt @@ -272,7 +272,7 @@ class LegalHoldRequestedViewModelTest { coEvery { coreLogic.getSessionScope(any()).approveLegalHoldRequest(any()) } returns result } - fun arrange() = this to viewModel + fun arrange() = this to viewModel.apply { observeLegalHoldRequest() } companion object { val UNKNOWN_ERROR = CoreFailure.Unknown(RuntimeException("error")) From 6800b82e6c66e5739fc333f09b0d1fcbcb6e7bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Mon, 5 Feb 2024 16:37:21 +0100 Subject: [PATCH 06/12] changes after review --- .../android/ui/legalhold/banner/LegalHoldBaseBanner.kt | 3 +-- .../kotlin/com/wire/android/ui/theme/WireDimensions.kt | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt index d96843f2ff0..76d966bcc81 100644 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/banner/LegalHoldBaseBanner.kt @@ -29,7 +29,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.unit.dp import com.wire.android.ui.common.LegalHoldIndicator import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions @@ -52,7 +51,7 @@ fun LegalHoldBaseBanner( color = colorsScheme().error ) .clickable(onClick = onClick) - .heightIn(min = 26.dp) + .heightIn(min = dimensions().legalHoldBannerMinHeight) .padding( horizontal = dimensions().spacing12x, vertical = dimensions().spacing4x diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt b/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt index 75d3bc4002d..e777df7edb9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt @@ -192,7 +192,9 @@ data class WireDimensions( // Conversation options val conversationOptionsItemMinHeight: Dp, // Import media - val importedMediaAssetSize: Dp + val importedMediaAssetSize: Dp, + // legal hold banner + val legalHoldBannerMinHeight: Dp, ) private val DefaultPhonePortraitWireDimensions: WireDimensions = WireDimensions( @@ -334,7 +336,8 @@ private val DefaultPhonePortraitWireDimensions: WireDimensions = WireDimensions( ongoingCallLabelHeight = 28.dp, audioMessageHeight = 48.dp, importedMediaAssetSize = 120.dp, - typingIndicatorHeight = 24.dp + typingIndicatorHeight = 24.dp, + legalHoldBannerMinHeight = 26.dp, ) private val DefaultPhoneLandscapeWireDimensions: WireDimensions = DefaultPhonePortraitWireDimensions From dce051c113b793a3a30d9ddd0e6874b2834f124b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Tue, 6 Feb 2024 17:08:58 +0100 Subject: [PATCH 07/12] feat: legal hold - update banners to v2 [WPB-6464] --- .../com/wire/android/ui/WireActivity.kt | 1 - .../android/ui/common/UserProfileAvatar.kt | 66 +++++++-- .../ui/common/topappbar/CommonTopAppBar.kt | 52 ++----- .../common/topappbar/CommonTopAppBarState.kt | 3 - .../topappbar/CommonTopAppBarViewModel.kt | 37 +---- .../com/wire/android/ui/home/HomeScreen.kt | 1 + .../com/wire/android/ui/home/HomeState.kt | 3 +- .../com/wire/android/ui/home/HomeTopBar.kt | 14 +- .../com/wire/android/ui/home/HomeViewModel.kt | 18 ++- ...rveLegalHoldStatusForCurrentUserUseCase.kt | 61 +++++++++ .../wire/android/ui/theme/WireDimensions.kt | 2 + .../self/SelfUserProfileViewModel.kt | 20 +-- .../topappbar/CommonTopAppBarViewModelTest.kt | 53 -------- .../wire/android/ui/home/HomeViewModelTest.kt | 128 ++++++++++++++++++ ...egalHoldStatusForCurrentUserUseCaseTest.kt | 120 ++++++++++++++++ .../SelfUserProfileViewModelArrangement.kt | 20 +-- .../self/SelfUserProfileViewModelTest.kt | 15 +- 17 files changed, 423 insertions(+), 191 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt create mode 100644 app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt create mode 100644 app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt index 7245801c5fb..b997ade6e04 100644 --- a/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/WireActivity.kt @@ -216,7 +216,6 @@ class WireActivity : AppCompatActivity() { onReturnToCallClick = { establishedCall -> navigator.navigate(NavigationCommand(OngoingCallScreenDestination(establishedCall.conversationId))) }, - onPendingClicked = legalHoldRequestedViewModel::show, ) NavigationGraph( navigator = navigator, diff --git a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt index 952d2f93d28..094aeff6220 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt @@ -19,10 +19,8 @@ package com.wire.android.ui.common import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize @@ -42,26 +40,28 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.max import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.model.UserAvatarData import com.wire.android.ui.home.conversationslist.model.Membership -import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.UserAvailabilityStatus +import kotlin.math.sqrt @Composable fun UserProfileAvatar( avatarData: UserAvatarData = UserAvatarData(), size: Dp = MaterialTheme.wireDimensions.avatarDefaultSize, padding: Dp = MaterialTheme.wireDimensions.avatarClickablePadding, - statusBorderSize: PaddingValues = PaddingValues(all = dimensions().avatarStatusBorderSize), modifier: Modifier = Modifier, clickable: Clickable? = null, showPlaceholderIfNoAsset: Boolean = true, withCrossfadeAnimation: Boolean = false, - showStatusIndicator: Boolean = true + showStatusIndicator: Boolean = true, + withLegalHoldIndicator: Boolean = false, ) { Box( contentAlignment = Alignment.Center, @@ -79,18 +79,51 @@ fun UserProfileAvatar( painter = painter, contentDescription = stringResource(R.string.content_description_user_avatar), modifier = Modifier - .padding(statusBorderSize) - .background(MaterialTheme.wireColorScheme.divider, CircleShape) - .size(size) - .border(width = dimensions().spacing1x, shape = CircleShape, color = MaterialTheme.wireColorScheme.outline) + // we need to take borders into account + .size(size + (max(dimensions().avatarStatusBorderSize, dimensions().avatarLegalHoldIndicatorBorderSize) * 2)) + .let { + if (withLegalHoldIndicator) { + it + .border( + width = dimensions().avatarLegalHoldIndicatorBorderSize / 2, + shape = CircleShape, + color = colorsScheme().error.copy(alpha = 0.3f) + ) + .padding(dimensions().avatarLegalHoldIndicatorBorderSize / 2) + .border( + width = dimensions().avatarLegalHoldIndicatorBorderSize / 2, + shape = CircleShape, + color = colorsScheme().error.copy(alpha = 1.0f) + ) + .padding(dimensions().avatarLegalHoldIndicatorBorderSize / 2) + } else { + it + // this is to make the border of the avatar to be the same size as with the legal hold indicator + .padding(dimensions().avatarLegalHoldIndicatorBorderSize - dimensions().spacing1x) + .border( + width = dimensions().spacing1x, + shape = CircleShape, + color = colorsScheme().outline + ) + .padding(dimensions().spacing1x) + + } + } .clip(CircleShape) .testTag("User avatar"), contentScale = ContentScale.Crop ) if (showStatusIndicator) { + val avatarWithLegalHoldRadius = (size.value / 2f) + dimensions().avatarLegalHoldIndicatorBorderSize.value + val statusRadius = (dimensions().userAvatarStatusSize - dimensions().avatarStatusBorderSize).value / 2f + // calculated using the trigonometry so that the status is always in the right place according to the avatar + val paddingToAlignWithAvatar = ((sqrt(2f) - 1f) * avatarWithLegalHoldRadius + (1f - sqrt(2f)) * statusRadius) / sqrt(2f) UserStatusIndicator( status = avatarData.availabilityStatus, - modifier = Modifier.align(Alignment.BottomEnd) + modifier = Modifier + // on designs the status border extends beyond the avatar's perimeter so we need to subtract it's size from the padding + .padding(paddingToAlignWithAvatar.dp - dimensions().avatarStatusBorderSize) + .align(Alignment.BottomEnd) ) } } @@ -141,3 +174,16 @@ private fun getDefaultAvatarResourceId(membership: Membership): Int = fun PreviewUserProfileAvatar() { UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE)) } + +@Preview +@Composable +fun PreviewUserProfileAvatarWithLegalHold() { + UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), withLegalHoldIndicator = true) +} + +@Preview +@Composable +fun PreviewLargeUserProfileAvatarWithLegalHold() { + UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), size = 48.dp, withLegalHoldIndicator = true) +} + diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt index 3f20bb3e0cf..9dfc96331c0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt @@ -45,8 +45,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.IntSize import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.wire.android.R -import com.wire.android.ui.legalhold.banner.LegalHoldStatusBar -import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions @@ -58,17 +56,12 @@ import com.wire.kalium.logic.data.id.ConversationId fun CommonTopAppBar( commonTopAppBarState: CommonTopAppBarState, onReturnToCallClick: (ConnectivityUIState.EstablishedCall) -> Unit, - onPendingClicked: () -> Unit, ) { Column { ConnectivityStatusBar( connectivityInfo = commonTopAppBarState.connectivityState, onReturnToCallClick = onReturnToCallClick ) - LegalHoldStatusBar( - legalHoldState = commonTopAppBarState.legalHoldState, - onPendingClicked = onPendingClicked - ) } } @@ -197,52 +190,23 @@ private fun clearStatusBarColor() { } @Composable -private fun PreviewCommonTopAppBar(connectivityUIState: ConnectivityUIState, legalHoldUIState: LegalHoldUIState) { +private fun PreviewCommonTopAppBar(connectivityUIState: ConnectivityUIState) { WireTheme { - CommonTopAppBar(CommonTopAppBarState(connectivityUIState, legalHoldUIState), {}, {}) + CommonTopAppBar(CommonTopAppBarState(connectivityUIState), {}) } } @PreviewMultipleThemes @Composable -fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldNone() = - PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.None) - -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldPending() = - PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.Pending) - -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityCallNotMuted_LegalHoldActive() = - PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false), LegalHoldUIState.Active) - -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityConnecting_LegalHoldNone() = - PreviewCommonTopAppBar(ConnectivityUIState.Connecting, LegalHoldUIState.None) - -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityConnecting_LegalHoldPending() = - PreviewCommonTopAppBar(ConnectivityUIState.Connecting, LegalHoldUIState.Pending) -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityConnecting_LegalHoldActive() = - PreviewCommonTopAppBar(ConnectivityUIState.Connecting, LegalHoldUIState.Active) - -@PreviewMultipleThemes -@Composable -fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldNone() = - PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.None) +fun PreviewCommonTopAppBar_ConnectivityCallNotMuted() = + PreviewCommonTopAppBar(ConnectivityUIState.EstablishedCall(ConversationId("what", "ever"), false)) @PreviewMultipleThemes @Composable -fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldPending() = - PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.Pending) +fun PreviewCommonTopAppBar_ConnectivityConnecting() = + PreviewCommonTopAppBar(ConnectivityUIState.Connecting) @PreviewMultipleThemes @Composable -fun PreviewCommonTopAppBar_ConnectivityNone_LegalHoldActive() = - PreviewCommonTopAppBar(ConnectivityUIState.None, LegalHoldUIState.Active) +fun PreviewCommonTopAppBar_ConnectivityNone() = + PreviewCommonTopAppBar(ConnectivityUIState.None) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt index 4730d19b58f..64177c20502 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt @@ -17,9 +17,6 @@ */ package com.wire.android.ui.common.topappbar -import com.wire.android.ui.legalhold.banner.LegalHoldUIState - data class CommonTopAppBarState( val connectivityState: ConnectivityUIState = ConnectivityUIState.None, - val legalHoldState: LegalHoldUIState = LegalHoldUIState.None, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt index f16430be061..1747775931b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt @@ -24,15 +24,12 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.wire.android.di.KaliumCoreLogic -import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.call.Call -import com.wire.kalium.logic.data.user.LegalHoldStatus import com.wire.kalium.logic.data.sync.SyncState import com.wire.kalium.logic.data.user.UserId -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -76,23 +73,13 @@ class CommonTopAppBarViewModel @Inject constructor( } } - private fun legalHoldStatusFlow(userId: UserId) = coreLogic.sessionScope(userId) { - observeLegalHoldRequest() // TODO combine with legal hold status - .map { legalHoldRequestResult -> - when (legalHoldRequestResult) { - is ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable -> LegalHoldStatus.PENDING - else -> LegalHoldStatus.DISABLED - } - } - } - init { viewModelScope.launch { coreLogic.globalScope { session.currentSessionFlow().flatMapLatest { when (it) { is CurrentSessionResult.Failure.Generic, - is CurrentSessionResult.Failure.SessionNotFound -> flowOf(ConnectivityUIState.None to LegalHoldUIState.None) + is CurrentSessionResult.Failure.SessionNotFound -> flowOf(ConnectivityUIState.None) is CurrentSessionResult.Success -> { val userId = it.accountInfo.userId @@ -100,15 +87,12 @@ class CommonTopAppBarViewModel @Inject constructor( activeCallFlow(userId), currentScreenFlow(), connectivityFlow(userId), - legalHoldStatusFlow(userId), - ) { activeCall, currentScreen, connectivity, legalHoldStatus -> - mapToConnectivityUIState(currentScreen, connectivity, activeCall) to - mapToLegalHoldUIState(currentScreen, legalHoldStatus) + ) { activeCall, currentScreen, connectivity -> + mapToConnectivityUIState(currentScreen, connectivity, activeCall) } } } - }.collectLatest { (connectivityUIState, legalHoldUIState) -> - state = state.copy(legalHoldState = legalHoldUIState) + }.collectLatest { connectivityUIState -> /** * Adding some delay here to avoid some bad UX : ongoing call banner displayed and * hided in a short time when the user hangs up the call @@ -149,19 +133,6 @@ class CommonTopAppBarViewModel @Inject constructor( } } - private fun mapToLegalHoldUIState( - currentScreen: CurrentScreen, - legalHoldStatus: LegalHoldStatus - ): LegalHoldUIState = when (legalHoldStatus) { - LegalHoldStatus.ENABLED -> LegalHoldUIState.Active - LegalHoldStatus.PENDING -> LegalHoldUIState.Pending - LegalHoldStatus.DISABLED, - LegalHoldStatus.NO_CONSENT -> LegalHoldUIState.None - }.let { legalHoldUIState -> - if (currentScreen is CurrentScreen.AuthRelated || currentScreen is CurrentScreen.CallScreen) LegalHoldUIState.None - else legalHoldUIState - } - private companion object { const val WAITING_TIME_TO_SHOW_ONGOING_CALL_BANNER = 600L } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt index 9330e43cab0..85862cf9d61 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeScreen.kt @@ -245,6 +245,7 @@ fun HomeContent( status = homeState.status, title = stringResource(currentNavigationItem.title), elevation = elevation, + withLegalHoldIndicator = homeState.shouldDisplayLegalHoldIndicator, onHamburgerMenuClick = ::openDrawer, onNavigateToSelfUserProfile = onSelfUserClick ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeState.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeState.kt index cf07b167aa1..0d6759974d6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeState.kt @@ -30,7 +30,8 @@ import com.wire.kalium.logic.data.user.UserId data class HomeState( val avatarAsset: ImageAsset.UserAvatarAsset? = null, val status: UserAvailabilityStatus = UserAvailabilityStatus.NONE, - val shouldDisplayWelcomeMessage: Boolean = false + val shouldDisplayWelcomeMessage: Boolean = false, + val shouldDisplayLegalHoldIndicator: Boolean = false, ) sealed class HomeRequirement { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeTopBar.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeTopBar.kt index df09883317e..29e748409c5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeTopBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeTopBar.kt @@ -38,6 +38,7 @@ fun HomeTopBar( status: UserAvailabilityStatus, title: String, elevation: Dp, + withLegalHoldIndicator: Boolean, onHamburgerMenuClick: () -> Unit, onNavigateToSelfUserProfile: () -> Unit ) { @@ -48,7 +49,8 @@ fun HomeTopBar( actions = { UserProfileAvatar( avatarData = UserAvatarData(avatarAsset, status), - clickable = remember { Clickable(enabled = true) { onNavigateToSelfUserProfile() } } + clickable = remember { Clickable(enabled = true) { onNavigateToSelfUserProfile() } }, + withLegalHoldIndicator = withLegalHoldIndicator, ) }, elevation = elevation, @@ -59,6 +61,14 @@ fun HomeTopBar( @Composable fun PreviewTopBar() { WireTheme { - HomeTopBar(null, UserAvailabilityStatus.AVAILABLE, "Title", 0.dp, {}, {}) + HomeTopBar(null, UserAvailabilityStatus.AVAILABLE, "Title", 0.dp, false, {}, {}) + } +} + +@PreviewMultipleThemes +@Composable +fun PreviewTopBarWithLegalHold() { + WireTheme { + HomeTopBar(null, UserAvailabilityStatus.AVAILABLE, "Title", 0.dp, true, {}, {}) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt index ffcaa4ed585..5538ddce376 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt @@ -27,10 +27,13 @@ import com.wire.android.datastore.GlobalDataStore import com.wire.android.migration.userDatabase.ShouldTriggerMigrationForUserUserCase import com.wire.android.model.ImageAsset.UserAvatarAsset import com.wire.android.navigation.SavedStateViewModel +import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase +import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @@ -42,6 +45,7 @@ class HomeViewModel @Inject constructor( private val globalDataStore: GlobalDataStore, private val getSelf: GetSelfUserUseCase, private val needsToRegisterClient: NeedsToRegisterClientUseCase, + private val observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase, private val wireSessionImageLoader: WireSessionImageLoader, private val shouldTriggerMigrationForUser: ShouldTriggerMigrationForUserUserCase ) : SavedStateViewModel(savedStateHandle) { @@ -51,6 +55,14 @@ class HomeViewModel @Inject constructor( init { loadUserAvatar() + observeLegalHoldStatus() + } + + private fun observeLegalHoldStatus() { + viewModelScope.launch { + observeLegalHoldStatusForCurrentUser() + .collectLatest { homeState = homeState.copy(shouldDisplayLegalHoldIndicator = it != LegalHoldUIState.None) } + } } fun checkRequirements(onRequirement: (HomeRequirement) -> Unit) { @@ -76,9 +88,9 @@ class HomeViewModel @Inject constructor( private fun loadUserAvatar() { viewModelScope.launch { getSelf().collect { selfUser -> - homeState = HomeState( - selfUser.previewPicture?.let { UserAvatarAsset(wireSessionImageLoader, it) }, - selfUser.availabilityStatus + homeState = homeState.copy( + avatarAsset = selfUser.previewPicture?.let { UserAvatarAsset(wireSessionImageLoader, it) }, + status = selfUser.availabilityStatus ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt new file mode 100644 index 00000000000..f34ff2cb6c8 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt @@ -0,0 +1,61 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.legalhold + +import com.wire.android.di.KaliumCoreLogic +import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.feature.legalhold.LegalHoldState +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable +import com.wire.kalium.logic.feature.session.CurrentSessionResult +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.withContext +import javax.inject.Inject + +class ObserveLegalHoldStatusForCurrentUserUseCase @Inject constructor( + @KaliumCoreLogic private val coreLogic: CoreLogic, + private val dispatchers: DispatcherProvider, +) { + suspend operator fun invoke(): Flow = withContext(dispatchers.io()) { + coreLogic.getGlobalScope().session.currentSessionFlow() + .flatMapLatest { + when (it) { + is CurrentSessionResult.Success -> coreLogic.sessionScope(it.accountInfo.userId) { + combine( + observeLegalHoldRequest(), + observeLegalHoldForSelfUser() + ) { legalHoldRequestStatus: ObserveLegalHoldRequestUseCase.Result, legalHoldStatus: LegalHoldState -> + when { + legalHoldRequestStatus is LegalHoldRequestAvailable -> LegalHoldUIState.Pending + legalHoldStatus is LegalHoldState.Enabled -> LegalHoldUIState.Active + else -> LegalHoldUIState.None + } + } + } + else -> flowOf(LegalHoldUIState.None) + } + } + .distinctUntilChanged() + } +} diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt b/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt index e777df7edb9..a4883ec6252 100644 --- a/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt @@ -41,6 +41,7 @@ data class WireDimensions( val userAvatarBusyVerticalPadding: Dp, val userAvatarBusyHorizontalPadding: Dp, val avatarStatusBorderSize: Dp, + val avatarLegalHoldIndicatorBorderSize: Dp, val groupAvatarCornerRadius: Dp, val avatarConversationTopBarSize: Dp, val groupAvatarConversationTopBarCornerRadius: Dp, @@ -208,6 +209,7 @@ private val DefaultPhonePortraitWireDimensions: WireDimensions = WireDimensions( avatarClickablePadding = 6.dp, userAvatarStatusSize = 16.dp, avatarStatusBorderSize = 2.dp, + avatarLegalHoldIndicatorBorderSize = 4.dp, userAvatarBusyVerticalPadding = 5.dp, userAvatarBusyHorizontalPadding = 3.dp, groupAvatarCornerRadius = 10.dp, diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt index 98dd577a9b5..fe209307f7b 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt @@ -37,7 +37,7 @@ import com.wire.android.mapper.OtherAccountMapper import com.wire.android.model.ImageAsset.UserAvatarAsset import com.wire.android.notification.NotificationChannelsManager import com.wire.android.notification.WireNotificationManager -import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase import com.wire.android.ui.userprofile.self.dialog.StatusDialogData import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.WireSessionImageLoader @@ -54,9 +54,6 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.LogoutUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase -import com.wire.kalium.logic.feature.legalhold.LegalHoldState -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldForSelfUserUseCase -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase @@ -90,8 +87,7 @@ class SelfUserProfileViewModel @Inject constructor( private val observeValidAccounts: ObserveValidAccountsUseCase, private val updateStatus: UpdateSelfAvailabilityStatusUseCase, private val logout: LogoutUseCase, - private val observeLegalHoldRequest: ObserveLegalHoldRequestUseCase, - private val observeLegalHoldForSelfUser: ObserveLegalHoldForSelfUserUseCase, + private val observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase, private val dispatchers: DispatcherProvider, private val wireSessionImageLoader: WireSessionImageLoader, private val authServerConfigProvider: AuthServerConfigProvider, @@ -178,17 +174,7 @@ class SelfUserProfileViewModel @Inject constructor( private fun observeLegalHoldStatus() { viewModelScope.launch { - combine( - observeLegalHoldRequest(), - observeLegalHoldForSelfUser() - ) { legalHoldRequestStatus: ObserveLegalHoldRequestUseCase.Result, legalHoldStatus: LegalHoldState -> - when { - legalHoldRequestStatus is ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable -> LegalHoldUIState.Pending - legalHoldStatus is LegalHoldState.Enabled -> LegalHoldUIState.Active - else -> LegalHoldUIState.None - } - } - .distinctUntilChanged() + observeLegalHoldStatusForCurrentUser() .collectLatest { userProfileState = userProfileState.copy(legalHoldStatus = it) } } } diff --git a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt index 807af5d24b3..7ad54890d65 100644 --- a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt @@ -19,7 +19,6 @@ package com.wire.android.ui.common.topappbar import com.wire.android.config.CoroutineTestExtension -import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.util.CurrentScreen import com.wire.android.util.CurrentScreenManager import com.wire.kalium.logic.CoreLogic @@ -30,7 +29,6 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.sync.SyncState import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult import com.wire.kalium.logic.sync.ObserveSyncStateUseCase import io.mockk.MockKAnnotations @@ -185,54 +183,8 @@ class CommonTopAppBarViewModelTest { val state = commonTopAppBarViewModel.state state.connectivityState shouldBeInstanceOf ConnectivityUIState.None::class - state.legalHoldState shouldBeInstanceOf LegalHoldUIState.None::class } - private fun testLegalHoldRequestInfo( - currentScreen: CurrentScreen, - result: ObserveLegalHoldRequestUseCase.Result, - expectedState: LegalHoldUIState, - ) = runTest { - val (_, commonTopAppBarViewModel) = Arrangement() - .withCurrentSessionExist() - .withCurrentScreen(currentScreen) - .withLegalHoldRequestResult(result) - .arrange() - - advanceUntilIdle() - val state = commonTopAppBarViewModel.state - - state.legalHoldState shouldBeInstanceOf expectedState::class - } - - @Test - fun givenNoLegalHoldRequest_whenGettingState_thenShouldNotHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo( - currentScreen = CurrentScreen.Home, - result = ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest, - expectedState = LegalHoldUIState.None - ) - - @Test - fun givenLegalHoldRequestAndHomeScreen_whenGettingState_thenShouldHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo( - currentScreen = CurrentScreen.Home, - result = ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable(byteArrayOf()), - expectedState = LegalHoldUIState.Pending - ) - - @Test - fun givenLegalHoldRequestAndCallScreen_whenGettingState_thenShouldNotHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo( - currentScreen = CurrentScreen.OngoingCallScreen(mockk()), - result = ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable(byteArrayOf()), - expectedState = LegalHoldUIState.None - ) - - @Test - fun givenLegalHoldRequestAndAuthRelatedScreen_whenGettingState_thenShouldNotHaveLegalHoldRequestInfo() = testLegalHoldRequestInfo( - currentScreen = CurrentScreen.AuthRelated, - result = ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable(byteArrayOf()), - expectedState = LegalHoldUIState.None - ) - private class Arrangement { val activeCall: Call = mockk() @@ -280,7 +232,6 @@ class CommonTopAppBarViewModelTest { withSyncState(SyncState.Live) withoutActiveCall() - withLegalHoldRequestResult(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) } private val commonTopAppBarViewModel by lazy { @@ -325,10 +276,6 @@ class CommonTopAppBarViewModelTest { coEvery { currentScreenManager.observeCurrentScreen(any()) } returns MutableStateFlow(currentScreen) } - fun withLegalHoldRequestResult(result: ObserveLegalHoldRequestUseCase.Result) = apply { - every { coreLogic.getSessionScope(any()).observeLegalHoldRequest() } returns flowOf(result) - } - fun arrange() = this to commonTopAppBarViewModel } } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt new file mode 100644 index 00000000000..9bee2913413 --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt @@ -0,0 +1,128 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.home + +import androidx.lifecycle.SavedStateHandle +import com.wire.android.config.CoroutineTestExtension +import com.wire.android.datastore.GlobalDataStore +import com.wire.android.framework.TestUser +import com.wire.android.migration.userDatabase.ShouldTriggerMigrationForUserUserCase +import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase +import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.android.util.ui.WireSessionImageLoader +import com.wire.kalium.logic.data.user.SelfUser +import com.wire.kalium.logic.data.user.UserAvailabilityStatus +import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase +import com.wire.kalium.logic.feature.user.GetSelfUserUseCase +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.internal.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(CoroutineTestExtension::class) +class HomeViewModelTest { + @Test + fun `given legal hold request pending, then shouldDisplayLegalHoldIndicator is true`() = runTest { + // given + val (_, viewModel) = Arrangement() + .withLegalHoldStatus(flowOf(LegalHoldUIState.Pending)) + .arrange() + // then + assertEquals(true, viewModel.homeState.shouldDisplayLegalHoldIndicator) + } + @Test + fun `given legal hold active, then shouldDisplayLegalHoldIndicator is true`() = runTest { + // given + val (_, viewModel) = Arrangement() + .withLegalHoldStatus(flowOf(LegalHoldUIState.Active)) + .arrange() + // then + assertEquals(true, viewModel.homeState.shouldDisplayLegalHoldIndicator) + } + @Test + fun `given legal hold disabled and no request available, then shouldDisplayLegalHoldIndicator is false`() = runTest { + // given + val (_, viewModel) = Arrangement() + .withLegalHoldStatus(flowOf(LegalHoldUIState.None)) + .arrange() + // then + assertEquals(false, viewModel.homeState.shouldDisplayLegalHoldIndicator) + } + @Test + fun `given legal hold active, when user status changes, then shouldDisplayLegalHoldIndicator should keep the same`() = runTest { + // given + val selfFlow = MutableStateFlow(TestUser.SELF_USER.copy(availabilityStatus = UserAvailabilityStatus.AVAILABLE)) + val (_, viewModel) = Arrangement() + .withLegalHoldStatus(flowOf(LegalHoldUIState.Active)) + .withGetSelf(selfFlow) + .arrange() + // when + selfFlow.emit(TestUser.SELF_USER.copy(availabilityStatus = UserAvailabilityStatus.AWAY)) + // then + assertEquals(true, viewModel.homeState.shouldDisplayLegalHoldIndicator) + } + + internal class Arrangement { + + @MockK + lateinit var savedStateHandle: SavedStateHandle + @MockK + lateinit var globalDataStore: GlobalDataStore + @MockK + lateinit var getSelf: GetSelfUserUseCase + @MockK + lateinit var needsToRegisterClient: NeedsToRegisterClientUseCase + @MockK + lateinit var observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase + @MockK + lateinit var wireSessionImageLoader: WireSessionImageLoader + @MockK + lateinit var shouldTriggerMigrationForUser: ShouldTriggerMigrationForUserUserCase + + private val viewModel by lazy { + HomeViewModel( + savedStateHandle = savedStateHandle, + globalDataStore = globalDataStore, + getSelf = getSelf, + needsToRegisterClient = needsToRegisterClient, + observeLegalHoldStatusForCurrentUser = observeLegalHoldStatusForCurrentUser, + wireSessionImageLoader = wireSessionImageLoader, + shouldTriggerMigrationForUser = shouldTriggerMigrationForUser + ) + } + init { + MockKAnnotations.init(this, relaxUnitFun = true) + withGetSelf(flowOf(TestUser.SELF_USER)) + } + fun withGetSelf(result: Flow) = apply { + coEvery { getSelf.invoke() } returns result + } + fun withLegalHoldStatus(result: Flow) = apply { + coEvery { observeLegalHoldStatusForCurrentUser.invoke() } returns result + } + fun arrange() = this to viewModel + } +} diff --git a/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt new file mode 100644 index 00000000000..149ebbbaed2 --- /dev/null +++ b/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt @@ -0,0 +1,120 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.legalhold + +import com.wire.android.config.CoroutineTestExtension +import com.wire.android.config.TestDispatcherProvider +import com.wire.android.framework.TestUser +import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.kalium.logic.CoreFailure +import com.wire.kalium.logic.CoreLogic +import com.wire.kalium.logic.data.auth.AccountInfo +import com.wire.kalium.logic.feature.legalhold.LegalHoldState +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase +import com.wire.kalium.logic.feature.session.CurrentSessionResult +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.internal.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@OptIn(ExperimentalCoroutinesApi::class) +@ExtendWith(CoroutineTestExtension::class) +class ObserveLegalHoldStatusForCurrentUserUseCaseTest { + + @Test + fun `given legal hold request available, then isUnderLegalHold is pending`() = runTest { + // given + val (_, useCase) = Arrangement() + .withLegalHold(LegalHoldState.Enabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable("fingerprint".toByteArray())) + .arrange() + // then + assertEquals(LegalHoldUIState.Pending, useCase.invoke().first()) + } + @Test + fun `given legal hold enabled, then isUnderLegalHold is active`() = runTest { + // given + val (_, useCase) = Arrangement() + .withLegalHold(LegalHoldState.Enabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .arrange() + // then + assertEquals(LegalHoldUIState.Active, useCase.invoke().first()) + } + @Test + fun `given legal hold disabled and no request available, then isUnderLegalHold is none`() = runTest { + // given + val (_, useCase) = Arrangement() + .withLegalHold(LegalHoldState.Disabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .arrange() + // then + assertEquals(LegalHoldUIState.None, useCase.invoke().first()) + } + @Test + fun `given no session, then isUnderLegalHold is none`() = runTest { + // given + val (_, useCase) = Arrangement() + .withCurrentSession(CurrentSessionResult.Failure.SessionNotFound) + .withLegalHold(LegalHoldState.Disabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .arrange() + // then + assertEquals(LegalHoldUIState.None, useCase.invoke().first()) + } + @Test + fun `given current session failure, then isUnderLegalHold is none`() = runTest { + // given + val (_, useCase) = Arrangement() + .withCurrentSession(CurrentSessionResult.Failure.Generic(CoreFailure.Unknown(null))) + .withLegalHold(LegalHoldState.Disabled) + .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .arrange() + // then + assertEquals(LegalHoldUIState.None, useCase.invoke().first()) + } + + internal class Arrangement() { + + @MockK + private lateinit var coreLogic: CoreLogic + + private val useCase by lazy { ObserveLegalHoldStatusForCurrentUserUseCase(coreLogic, TestDispatcherProvider()) } + + init { + MockKAnnotations.init(this, relaxUnitFun = true) + withCurrentSession(CurrentSessionResult.Success(AccountInfo.Valid(TestUser.USER_ID))) + } + fun arrange() = this to useCase + fun withCurrentSession(result: CurrentSessionResult) = apply { + coEvery { coreLogic.getGlobalScope().session.currentSessionFlow.invoke() } returns flowOf(result) + } + fun withLegalHoldRequest(result: ObserveLegalHoldRequestUseCase.Result) = apply { + coEvery { coreLogic.getSessionScope(any()).observeLegalHoldRequest.invoke() } returns flowOf(result) + } + fun withLegalHold(result: LegalHoldState) = apply { + coEvery { coreLogic.getSessionScope(any()).observeLegalHoldForSelfUser.invoke() } returns flowOf(result) + } + } +} diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt index b9936d4e9d0..6f6bbce24e7 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt @@ -28,15 +28,14 @@ import com.wire.android.framework.TestUser import com.wire.android.mapper.OtherAccountMapper import com.wire.android.notification.NotificationChannelsManager import com.wire.android.notification.WireNotificationManager +import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase +import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.feature.auth.LogoutUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase -import com.wire.kalium.logic.feature.legalhold.LegalHoldState -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldForSelfUserUseCase -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase @@ -63,9 +62,7 @@ class SelfUserProfileViewModelArrangement { @MockK lateinit var logout: LogoutUseCase @MockK - lateinit var observeLegalHoldRequest: ObserveLegalHoldRequestUseCase - @MockK - lateinit var observeLegalHoldForSelfUser: ObserveLegalHoldForSelfUserUseCase + lateinit var observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase @MockK lateinit var dispatchers: DispatcherProvider @MockK @@ -102,8 +99,7 @@ class SelfUserProfileViewModelArrangement { observeValidAccounts = observeValidAccounts, updateStatus = updateStatus, logout = logout, - observeLegalHoldRequest = observeLegalHoldRequest, - observeLegalHoldForSelfUser = observeLegalHoldForSelfUser, + observeLegalHoldStatusForCurrentUser = observeLegalHoldStatusForCurrentUser, dispatchers = TestDispatcherProvider(), wireSessionImageLoader = wireSessionImageLoader, authServerConfigProvider = authServerConfigProvider, @@ -130,12 +126,8 @@ class SelfUserProfileViewModelArrangement { coEvery { isReadOnlyAccount.invoke() } returns false coEvery { observeEstablishedCalls.invoke() } returns flowOf(emptyList()) } - - fun withLegalHoldRequest(result: ObserveLegalHoldRequestUseCase.Result) = apply { - coEvery { observeLegalHoldRequest.invoke() } returns flowOf(result) - } - fun withLegalHold(result: LegalHoldState) = apply { - coEvery { observeLegalHoldForSelfUser.invoke() } returns flowOf(result) + fun withLegalHoldStatus(result: LegalHoldUIState) = apply { + coEvery { observeLegalHoldStatusForCurrentUser.invoke() } returns flowOf(result) } fun arrange() = this to viewModel } diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt index 8b6c0095d6a..4c8dd2621e4 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt @@ -20,8 +20,6 @@ package com.wire.android.ui.userprofile.self import com.wire.android.config.CoroutineTestExtension import com.wire.android.config.NavigationTestExtension import com.wire.android.ui.legalhold.banner.LegalHoldUIState -import com.wire.kalium.logic.feature.legalhold.LegalHoldState -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.amshove.kluent.internal.assertEquals @@ -34,22 +32,20 @@ import org.junit.jupiter.api.extension.ExtendWith class SelfUserProfileViewModelTest { @Test - fun `given legal hold request available, then isUnderLegalHold is pending`() = runTest { + fun `given legal hold request pending, then isUnderLegalHold is pending`() = runTest { // given val (_, viewModel) = SelfUserProfileViewModelArrangement() - .withLegalHold(LegalHoldState.Enabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable("fingerprint".toByteArray())) + .withLegalHoldStatus(LegalHoldUIState.Pending) .arrange() // then assertEquals(LegalHoldUIState.Pending, viewModel.userProfileState.legalHoldStatus) } @Test - fun `given legal hold enabled, then isUnderLegalHold is active`() = runTest { + fun `given legal hold active, then isUnderLegalHold is active`() = runTest { // given val (_, viewModel) = SelfUserProfileViewModelArrangement() - .withLegalHold(LegalHoldState.Enabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .withLegalHoldStatus(LegalHoldUIState.Active) .arrange() // then assertEquals(LegalHoldUIState.Active, viewModel.userProfileState.legalHoldStatus) @@ -59,8 +55,7 @@ class SelfUserProfileViewModelTest { fun `given legal hold disabled and no request available, then isUnderLegalHold is none`() = runTest { // given val (_, viewModel) = SelfUserProfileViewModelArrangement() - .withLegalHold(LegalHoldState.Disabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) + .withLegalHoldStatus(LegalHoldUIState.None) .arrange() // then assertEquals(LegalHoldUIState.None, viewModel.userProfileState.legalHoldStatus) From 1eb51520620aff4580a4127b7881108bd0cb04ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Tue, 6 Feb 2024 17:26:25 +0100 Subject: [PATCH 08/12] update previews for avatars --- .../com/wire/android/ui/common/UserProfileAvatar.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt index 094aeff6220..8f813a54050 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt @@ -38,7 +38,6 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.max @@ -47,6 +46,7 @@ import com.wire.android.model.Clickable import com.wire.android.model.UserAvatarData import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.theme.wireDimensions +import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.user.ConnectionState import com.wire.kalium.logic.data.user.UserAvailabilityStatus import kotlin.math.sqrt @@ -169,19 +169,19 @@ private fun getDefaultAvatarResourceId(membership: Membership): Int = R.drawable.ic_default_user_avatar } -@Preview +@PreviewMultipleThemes @Composable fun PreviewUserProfileAvatar() { UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE)) } -@Preview +@PreviewMultipleThemes @Composable fun PreviewUserProfileAvatarWithLegalHold() { UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), withLegalHoldIndicator = true) } -@Preview +@PreviewMultipleThemes @Composable fun PreviewLargeUserProfileAvatarWithLegalHold() { UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), size = 48.dp, withLegalHoldIndicator = true) From 0790b7025061ab25f1e5c60d8318940692d6961d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Tue, 6 Feb 2024 17:36:13 +0100 Subject: [PATCH 09/12] fix detekt issues --- .../kotlin/com/wire/android/ui/common/UserProfileAvatar.kt | 2 -- .../ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt index 8f813a54050..55c41b25eaa 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt @@ -106,7 +106,6 @@ fun UserProfileAvatar( color = colorsScheme().outline ) .padding(dimensions().spacing1x) - } } .clip(CircleShape) @@ -186,4 +185,3 @@ fun PreviewUserProfileAvatarWithLegalHold() { fun PreviewLargeUserProfileAvatarWithLegalHold() { UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), size = 48.dp, withLegalHoldIndicator = true) } - diff --git a/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt index 149ebbbaed2..56eb4a61d13 100644 --- a/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt @@ -95,7 +95,7 @@ class ObserveLegalHoldStatusForCurrentUserUseCaseTest { assertEquals(LegalHoldUIState.None, useCase.invoke().first()) } - internal class Arrangement() { + internal class Arrangement { @MockK private lateinit var coreLogic: CoreLogic @@ -108,7 +108,7 @@ class ObserveLegalHoldStatusForCurrentUserUseCaseTest { } fun arrange() = this to useCase fun withCurrentSession(result: CurrentSessionResult) = apply { - coEvery { coreLogic.getGlobalScope().session.currentSessionFlow.invoke() } returns flowOf(result) + coEvery { coreLogic.getGlobalScope().session.currentSessionFlow.invoke() } returns flowOf(result) } fun withLegalHoldRequest(result: ObserveLegalHoldRequestUseCase.Result) = apply { coEvery { coreLogic.getSessionScope(any()).observeLegalHoldRequest.invoke() } returns flowOf(result) From 5e9c3c7a2c94daeeefe196a53c47962a95959c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Tue, 6 Feb 2024 18:16:38 +0100 Subject: [PATCH 10/12] update previews for avatars --- .../com/wire/android/ui/common/UserProfileAvatar.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt index 55c41b25eaa..3563433810c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/UserProfileAvatar.kt @@ -45,6 +45,7 @@ import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.model.UserAvatarData import com.wire.android.ui.home.conversationslist.model.Membership +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireDimensions import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.user.ConnectionState @@ -171,17 +172,23 @@ private fun getDefaultAvatarResourceId(membership: Membership): Int = @PreviewMultipleThemes @Composable fun PreviewUserProfileAvatar() { - UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE)) + WireTheme { + UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE)) + } } @PreviewMultipleThemes @Composable fun PreviewUserProfileAvatarWithLegalHold() { - UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), withLegalHoldIndicator = true) + WireTheme { + UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), withLegalHoldIndicator = true) + } } @PreviewMultipleThemes @Composable fun PreviewLargeUserProfileAvatarWithLegalHold() { - UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), size = 48.dp, withLegalHoldIndicator = true) + WireTheme { + UserProfileAvatar(UserAvatarData(availabilityStatus = UserAvailabilityStatus.AVAILABLE), 48.dp, withLegalHoldIndicator = true) + } } From 645f8431c729a86915b401b95ff8668ca41cc822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Thu, 8 Feb 2024 14:31:30 +0100 Subject: [PATCH 11/12] use updated use case from kalium --- .../com/wire/android/ui/home/HomeViewModel.kt | 10 +- ...rveLegalHoldStatusForCurrentUserUseCase.kt | 61 --------- .../self/SelfUserProfileViewModel.kt | 16 ++- .../wire/android/ui/home/HomeViewModelTest.kt | 24 ++-- ...egalHoldStatusForCurrentUserUseCaseTest.kt | 120 ------------------ .../SelfUserProfileViewModelArrangement.kt | 12 +- .../self/SelfUserProfileViewModelTest.kt | 9 +- kalium | 2 +- 8 files changed, 42 insertions(+), 212 deletions(-) delete mode 100644 app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt delete mode 100644 app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt index 5538ddce376..3eaa4cb90ab 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/HomeViewModel.kt @@ -27,10 +27,10 @@ import com.wire.android.datastore.GlobalDataStore import com.wire.android.migration.userDatabase.ShouldTriggerMigrationForUserUserCase import com.wire.android.model.ImageAsset.UserAvatarAsset import com.wire.android.navigation.SavedStateViewModel -import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase -import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldStateForSelfUser +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldStateForSelfUserUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collectLatest @@ -45,7 +45,7 @@ class HomeViewModel @Inject constructor( private val globalDataStore: GlobalDataStore, private val getSelf: GetSelfUserUseCase, private val needsToRegisterClient: NeedsToRegisterClientUseCase, - private val observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase, + private val observeLegalHoldStatusForSelfUser: ObserveLegalHoldStateForSelfUserUseCase, private val wireSessionImageLoader: WireSessionImageLoader, private val shouldTriggerMigrationForUser: ShouldTriggerMigrationForUserUserCase ) : SavedStateViewModel(savedStateHandle) { @@ -60,8 +60,8 @@ class HomeViewModel @Inject constructor( private fun observeLegalHoldStatus() { viewModelScope.launch { - observeLegalHoldStatusForCurrentUser() - .collectLatest { homeState = homeState.copy(shouldDisplayLegalHoldIndicator = it != LegalHoldUIState.None) } + observeLegalHoldStatusForSelfUser() + .collectLatest { homeState = homeState.copy(shouldDisplayLegalHoldIndicator = it != LegalHoldStateForSelfUser.Disabled) } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt b/app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt deleted file mode 100644 index f34ff2cb6c8..00000000000 --- a/app/src/main/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCase.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.android.ui.legalhold - -import com.wire.android.di.KaliumCoreLogic -import com.wire.android.ui.legalhold.banner.LegalHoldUIState -import com.wire.android.util.dispatchers.DispatcherProvider -import com.wire.kalium.logic.CoreLogic -import com.wire.kalium.logic.feature.legalhold.LegalHoldState -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable -import com.wire.kalium.logic.feature.session.CurrentSessionResult -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.withContext -import javax.inject.Inject - -class ObserveLegalHoldStatusForCurrentUserUseCase @Inject constructor( - @KaliumCoreLogic private val coreLogic: CoreLogic, - private val dispatchers: DispatcherProvider, -) { - suspend operator fun invoke(): Flow = withContext(dispatchers.io()) { - coreLogic.getGlobalScope().session.currentSessionFlow() - .flatMapLatest { - when (it) { - is CurrentSessionResult.Success -> coreLogic.sessionScope(it.accountInfo.userId) { - combine( - observeLegalHoldRequest(), - observeLegalHoldForSelfUser() - ) { legalHoldRequestStatus: ObserveLegalHoldRequestUseCase.Result, legalHoldStatus: LegalHoldState -> - when { - legalHoldRequestStatus is LegalHoldRequestAvailable -> LegalHoldUIState.Pending - legalHoldStatus is LegalHoldState.Enabled -> LegalHoldUIState.Active - else -> LegalHoldUIState.None - } - } - } - else -> flowOf(LegalHoldUIState.None) - } - } - .distinctUntilChanged() - } -} diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt index fe209307f7b..1ade65075c6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModel.kt @@ -37,7 +37,7 @@ import com.wire.android.mapper.OtherAccountMapper import com.wire.android.model.ImageAsset.UserAvatarAsset import com.wire.android.notification.NotificationChannelsManager import com.wire.android.notification.WireNotificationManager -import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase +import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.ui.userprofile.self.dialog.StatusDialogData import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.WireSessionImageLoader @@ -54,6 +54,8 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.auth.LogoutUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldStateForSelfUser +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldStateForSelfUserUseCase import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase @@ -69,6 +71,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -87,7 +90,7 @@ class SelfUserProfileViewModel @Inject constructor( private val observeValidAccounts: ObserveValidAccountsUseCase, private val updateStatus: UpdateSelfAvailabilityStatusUseCase, private val logout: LogoutUseCase, - private val observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase, + private val observeLegalHoldStatusForSelfUser: ObserveLegalHoldStateForSelfUserUseCase, private val dispatchers: DispatcherProvider, private val wireSessionImageLoader: WireSessionImageLoader, private val authServerConfigProvider: AuthServerConfigProvider, @@ -174,7 +177,14 @@ class SelfUserProfileViewModel @Inject constructor( private fun observeLegalHoldStatus() { viewModelScope.launch { - observeLegalHoldStatusForCurrentUser() + observeLegalHoldStatusForSelfUser() + .map { legalHoldState -> + when (legalHoldState) { + is LegalHoldStateForSelfUser.Enabled -> LegalHoldUIState.Active + is LegalHoldStateForSelfUser.PendingRequest -> LegalHoldUIState.Pending + is LegalHoldStateForSelfUser.Disabled -> LegalHoldUIState.None + } + } .collectLatest { userProfileState = userProfileState.copy(legalHoldStatus = it) } } } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt index 9bee2913413..dcb849f3407 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/HomeViewModelTest.kt @@ -22,12 +22,12 @@ import com.wire.android.config.CoroutineTestExtension import com.wire.android.datastore.GlobalDataStore import com.wire.android.framework.TestUser import com.wire.android.migration.userDatabase.ShouldTriggerMigrationForUserUserCase -import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase -import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.user.SelfUser import com.wire.kalium.logic.data.user.UserAvailabilityStatus import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldStateForSelfUser +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldStateForSelfUserUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -48,16 +48,16 @@ class HomeViewModelTest { fun `given legal hold request pending, then shouldDisplayLegalHoldIndicator is true`() = runTest { // given val (_, viewModel) = Arrangement() - .withLegalHoldStatus(flowOf(LegalHoldUIState.Pending)) + .withLegalHoldStatus(flowOf(LegalHoldStateForSelfUser.PendingRequest)) .arrange() // then assertEquals(true, viewModel.homeState.shouldDisplayLegalHoldIndicator) } @Test - fun `given legal hold active, then shouldDisplayLegalHoldIndicator is true`() = runTest { + fun `given legal hold enabled, then shouldDisplayLegalHoldIndicator is true`() = runTest { // given val (_, viewModel) = Arrangement() - .withLegalHoldStatus(flowOf(LegalHoldUIState.Active)) + .withLegalHoldStatus(flowOf(LegalHoldStateForSelfUser.Enabled)) .arrange() // then assertEquals(true, viewModel.homeState.shouldDisplayLegalHoldIndicator) @@ -66,17 +66,17 @@ class HomeViewModelTest { fun `given legal hold disabled and no request available, then shouldDisplayLegalHoldIndicator is false`() = runTest { // given val (_, viewModel) = Arrangement() - .withLegalHoldStatus(flowOf(LegalHoldUIState.None)) + .withLegalHoldStatus(flowOf(LegalHoldStateForSelfUser.Disabled)) .arrange() // then assertEquals(false, viewModel.homeState.shouldDisplayLegalHoldIndicator) } @Test - fun `given legal hold active, when user status changes, then shouldDisplayLegalHoldIndicator should keep the same`() = runTest { + fun `given legal hold enabled, when user status changes, then shouldDisplayLegalHoldIndicator should keep the same`() = runTest { // given val selfFlow = MutableStateFlow(TestUser.SELF_USER.copy(availabilityStatus = UserAvailabilityStatus.AVAILABLE)) val (_, viewModel) = Arrangement() - .withLegalHoldStatus(flowOf(LegalHoldUIState.Active)) + .withLegalHoldStatus(flowOf(LegalHoldStateForSelfUser.Enabled)) .withGetSelf(selfFlow) .arrange() // when @@ -96,7 +96,7 @@ class HomeViewModelTest { @MockK lateinit var needsToRegisterClient: NeedsToRegisterClientUseCase @MockK - lateinit var observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase + lateinit var observeLegalHoldStatusForSelfUser: ObserveLegalHoldStateForSelfUserUseCase @MockK lateinit var wireSessionImageLoader: WireSessionImageLoader @MockK @@ -108,7 +108,7 @@ class HomeViewModelTest { globalDataStore = globalDataStore, getSelf = getSelf, needsToRegisterClient = needsToRegisterClient, - observeLegalHoldStatusForCurrentUser = observeLegalHoldStatusForCurrentUser, + observeLegalHoldStatusForSelfUser = observeLegalHoldStatusForSelfUser, wireSessionImageLoader = wireSessionImageLoader, shouldTriggerMigrationForUser = shouldTriggerMigrationForUser ) @@ -120,8 +120,8 @@ class HomeViewModelTest { fun withGetSelf(result: Flow) = apply { coEvery { getSelf.invoke() } returns result } - fun withLegalHoldStatus(result: Flow) = apply { - coEvery { observeLegalHoldStatusForCurrentUser.invoke() } returns result + fun withLegalHoldStatus(result: Flow) = apply { + coEvery { observeLegalHoldStatusForSelfUser.invoke() } returns result } fun arrange() = this to viewModel } diff --git a/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt b/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt deleted file mode 100644 index 56eb4a61d13..00000000000 --- a/app/src/test/kotlin/com/wire/android/ui/legalhold/ObserveLegalHoldStatusForCurrentUserUseCaseTest.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Wire - * Copyright (C) 2024 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ -package com.wire.android.ui.legalhold - -import com.wire.android.config.CoroutineTestExtension -import com.wire.android.config.TestDispatcherProvider -import com.wire.android.framework.TestUser -import com.wire.android.ui.legalhold.banner.LegalHoldUIState -import com.wire.kalium.logic.CoreFailure -import com.wire.kalium.logic.CoreLogic -import com.wire.kalium.logic.data.auth.AccountInfo -import com.wire.kalium.logic.feature.legalhold.LegalHoldState -import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldRequestUseCase -import com.wire.kalium.logic.feature.session.CurrentSessionResult -import io.mockk.MockKAnnotations -import io.mockk.coEvery -import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.amshove.kluent.internal.assertEquals -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith - -@OptIn(ExperimentalCoroutinesApi::class) -@ExtendWith(CoroutineTestExtension::class) -class ObserveLegalHoldStatusForCurrentUserUseCaseTest { - - @Test - fun `given legal hold request available, then isUnderLegalHold is pending`() = runTest { - // given - val (_, useCase) = Arrangement() - .withLegalHold(LegalHoldState.Enabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.LegalHoldRequestAvailable("fingerprint".toByteArray())) - .arrange() - // then - assertEquals(LegalHoldUIState.Pending, useCase.invoke().first()) - } - @Test - fun `given legal hold enabled, then isUnderLegalHold is active`() = runTest { - // given - val (_, useCase) = Arrangement() - .withLegalHold(LegalHoldState.Enabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) - .arrange() - // then - assertEquals(LegalHoldUIState.Active, useCase.invoke().first()) - } - @Test - fun `given legal hold disabled and no request available, then isUnderLegalHold is none`() = runTest { - // given - val (_, useCase) = Arrangement() - .withLegalHold(LegalHoldState.Disabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) - .arrange() - // then - assertEquals(LegalHoldUIState.None, useCase.invoke().first()) - } - @Test - fun `given no session, then isUnderLegalHold is none`() = runTest { - // given - val (_, useCase) = Arrangement() - .withCurrentSession(CurrentSessionResult.Failure.SessionNotFound) - .withLegalHold(LegalHoldState.Disabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) - .arrange() - // then - assertEquals(LegalHoldUIState.None, useCase.invoke().first()) - } - @Test - fun `given current session failure, then isUnderLegalHold is none`() = runTest { - // given - val (_, useCase) = Arrangement() - .withCurrentSession(CurrentSessionResult.Failure.Generic(CoreFailure.Unknown(null))) - .withLegalHold(LegalHoldState.Disabled) - .withLegalHoldRequest(ObserveLegalHoldRequestUseCase.Result.NoLegalHoldRequest) - .arrange() - // then - assertEquals(LegalHoldUIState.None, useCase.invoke().first()) - } - - internal class Arrangement { - - @MockK - private lateinit var coreLogic: CoreLogic - - private val useCase by lazy { ObserveLegalHoldStatusForCurrentUserUseCase(coreLogic, TestDispatcherProvider()) } - - init { - MockKAnnotations.init(this, relaxUnitFun = true) - withCurrentSession(CurrentSessionResult.Success(AccountInfo.Valid(TestUser.USER_ID))) - } - fun arrange() = this to useCase - fun withCurrentSession(result: CurrentSessionResult) = apply { - coEvery { coreLogic.getGlobalScope().session.currentSessionFlow.invoke() } returns flowOf(result) - } - fun withLegalHoldRequest(result: ObserveLegalHoldRequestUseCase.Result) = apply { - coEvery { coreLogic.getSessionScope(any()).observeLegalHoldRequest.invoke() } returns flowOf(result) - } - fun withLegalHold(result: LegalHoldState) = apply { - coEvery { coreLogic.getSessionScope(any()).observeLegalHoldForSelfUser.invoke() } returns flowOf(result) - } - } -} diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt index 6f6bbce24e7..87ddf9e9999 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelArrangement.kt @@ -28,14 +28,14 @@ import com.wire.android.framework.TestUser import com.wire.android.mapper.OtherAccountMapper import com.wire.android.notification.NotificationChannelsManager import com.wire.android.notification.WireNotificationManager -import com.wire.android.ui.legalhold.ObserveLegalHoldStatusForCurrentUserUseCase -import com.wire.android.ui.legalhold.banner.LegalHoldUIState import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.id.QualifiedIdMapper import com.wire.kalium.logic.feature.auth.LogoutUseCase import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase +import com.wire.kalium.logic.feature.legalhold.LegalHoldStateForSelfUser +import com.wire.kalium.logic.feature.legalhold.ObserveLegalHoldStateForSelfUserUseCase import com.wire.kalium.logic.feature.team.GetUpdatedSelfTeamUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase @@ -62,7 +62,7 @@ class SelfUserProfileViewModelArrangement { @MockK lateinit var logout: LogoutUseCase @MockK - lateinit var observeLegalHoldStatusForCurrentUser: ObserveLegalHoldStatusForCurrentUserUseCase + lateinit var observeLegalHoldStatusForSelfUser: ObserveLegalHoldStateForSelfUserUseCase @MockK lateinit var dispatchers: DispatcherProvider @MockK @@ -99,7 +99,7 @@ class SelfUserProfileViewModelArrangement { observeValidAccounts = observeValidAccounts, updateStatus = updateStatus, logout = logout, - observeLegalHoldStatusForCurrentUser = observeLegalHoldStatusForCurrentUser, + observeLegalHoldStatusForSelfUser = observeLegalHoldStatusForSelfUser, dispatchers = TestDispatcherProvider(), wireSessionImageLoader = wireSessionImageLoader, authServerConfigProvider = authServerConfigProvider, @@ -126,8 +126,8 @@ class SelfUserProfileViewModelArrangement { coEvery { isReadOnlyAccount.invoke() } returns false coEvery { observeEstablishedCalls.invoke() } returns flowOf(emptyList()) } - fun withLegalHoldStatus(result: LegalHoldUIState) = apply { - coEvery { observeLegalHoldStatusForCurrentUser.invoke() } returns flowOf(result) + fun withLegalHoldStatus(result: LegalHoldStateForSelfUser) = apply { + coEvery { observeLegalHoldStatusForSelfUser.invoke() } returns flowOf(result) } fun arrange() = this to viewModel } diff --git a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt index 4c8dd2621e4..95b4fd93974 100644 --- a/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileViewModelTest.kt @@ -20,6 +20,7 @@ package com.wire.android.ui.userprofile.self import com.wire.android.config.CoroutineTestExtension import com.wire.android.config.NavigationTestExtension import com.wire.android.ui.legalhold.banner.LegalHoldUIState +import com.wire.kalium.logic.feature.legalhold.LegalHoldStateForSelfUser import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.amshove.kluent.internal.assertEquals @@ -35,17 +36,17 @@ class SelfUserProfileViewModelTest { fun `given legal hold request pending, then isUnderLegalHold is pending`() = runTest { // given val (_, viewModel) = SelfUserProfileViewModelArrangement() - .withLegalHoldStatus(LegalHoldUIState.Pending) + .withLegalHoldStatus(LegalHoldStateForSelfUser.PendingRequest) .arrange() // then assertEquals(LegalHoldUIState.Pending, viewModel.userProfileState.legalHoldStatus) } @Test - fun `given legal hold active, then isUnderLegalHold is active`() = runTest { + fun `given legal hold enabled, then isUnderLegalHold is active`() = runTest { // given val (_, viewModel) = SelfUserProfileViewModelArrangement() - .withLegalHoldStatus(LegalHoldUIState.Active) + .withLegalHoldStatus(LegalHoldStateForSelfUser.Enabled) .arrange() // then assertEquals(LegalHoldUIState.Active, viewModel.userProfileState.legalHoldStatus) @@ -55,7 +56,7 @@ class SelfUserProfileViewModelTest { fun `given legal hold disabled and no request available, then isUnderLegalHold is none`() = runTest { // given val (_, viewModel) = SelfUserProfileViewModelArrangement() - .withLegalHoldStatus(LegalHoldUIState.None) + .withLegalHoldStatus(LegalHoldStateForSelfUser.Disabled) .arrange() // then assertEquals(LegalHoldUIState.None, viewModel.userProfileState.legalHoldStatus) diff --git a/kalium b/kalium index d6c73552a4f..68b10a26470 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit d6c73552a4f2327b72bb255ebc79adddfd0e519d +Subproject commit 68b10a264700a9a9d1d97d21ac485f9544cc242e From 91ef87005b28593947fe356629dff39b15314c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Thu, 8 Feb 2024 15:39:16 +0100 Subject: [PATCH 12/12] update kalium ref --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 68b10a26470..65f9293521b 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 68b10a264700a9a9d1d97d21ac485f9544cc242e +Subproject commit 65f9293521b612f83a27353e6b04456c877f7aad