From 21bbb68f4ad6e69060715d8260a2708d05649192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= <30429749+saleniuk@users.noreply.github.com> Date: Thu, 28 Dec 2023 15:38:16 +0100 Subject: [PATCH] feat: discover legal hold when sending message [WPB-5999] (#2558) --- .../android/di/accountScoped/ClientModule.kt | 10 +-- .../conversations/MessageComposerViewModel.kt | 37 ++++++++-- .../conversations/MessageComposerViewState.kt | 10 ++- .../other/OtherUserProfileScreenViewModel.kt | 6 +- .../MessageComposerViewModelArrangement.kt | 14 ++++ .../MessageComposerViewModelTest.kt | 69 +++++++++++++++++-- .../OtherUserProfileViewModelArrangement.kt | 6 +- kalium | 2 +- 8 files changed, 129 insertions(+), 25 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/ClientModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/ClientModule.kt index 8ad1250e413..7b9ce36b36d 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/ClientModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/ClientModule.kt @@ -25,12 +25,12 @@ import com.wire.kalium.logic.feature.client.ClientFingerprintUseCase import com.wire.kalium.logic.feature.client.ClientScope import com.wire.kalium.logic.feature.client.DeleteClientUseCase import com.wire.kalium.logic.feature.client.FetchSelfClientsFromRemoteUseCase +import com.wire.kalium.logic.feature.client.FetchUsersClientsFromRemoteUseCase import com.wire.kalium.logic.feature.client.GetOrRegisterClientUseCase import com.wire.kalium.logic.feature.client.NeedsToRegisterClientUseCase import com.wire.kalium.logic.feature.client.ObserveClientDetailsUseCase import com.wire.kalium.logic.feature.client.ObserveClientsByUserIdUseCase import com.wire.kalium.logic.feature.client.ObserveCurrentClientIdUseCase -import com.wire.kalium.logic.feature.client.PersistOtherUserClientsUseCase import com.wire.kalium.logic.feature.client.UpdateClientVerificationStatusUseCase import com.wire.kalium.logic.feature.keypackage.MLSKeyPackageCountUseCase import com.wire.kalium.logic.sync.slow.RestartSlowSyncProcessForRecoveryUseCase @@ -72,8 +72,8 @@ class ClientModule { @ViewModelScoped @Provides - fun providePersistOtherUsersClients(clientScope: ClientScope): PersistOtherUserClientsUseCase = - clientScope.persistOtherUserClients + fun provideFetchUsersClientsFromRemoteUseCase(clientScope: ClientScope): FetchUsersClientsFromRemoteUseCase = + clientScope.fetchUsersClients @ViewModelScoped @Provides @@ -82,8 +82,8 @@ class ClientModule { @ViewModelScoped @Provides - fun provideSelfClientsUseCase(clientScope: ClientScope): FetchSelfClientsFromRemoteUseCase = - clientScope.selfClients + fun provideFetchSelfClientsFromRemoteUseCase(clientScope: ClientScope): FetchSelfClientsFromRemoteUseCase = + clientScope.fetchSelfClients @ViewModelScoped @Provides diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt index 07020243f00..3d1a6303337 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModel.kt @@ -47,6 +47,7 @@ import com.wire.android.util.FileManager import com.wire.android.util.ImageUtil import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.android.util.getAudioLengthInMs +import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.configuration.FileSharingStatus import com.wire.kalium.logic.data.asset.AttachmentType import com.wire.kalium.logic.data.asset.KaliumFileSystem @@ -54,7 +55,9 @@ import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.message.SelfDeletionTimer import com.wire.kalium.logic.data.user.OtherUser +import com.wire.kalium.logic.failure.LegalHoldEnabledForConversationFailure import com.wire.kalium.logic.feature.asset.GetAssetSizeLimitUseCase +import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageResult import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageUseCase import com.wire.kalium.logic.feature.conversation.InteractionAvailability import com.wire.kalium.logic.feature.conversation.IsInteractionAvailableResult @@ -75,6 +78,7 @@ import com.wire.kalium.logic.feature.message.ephemeral.EnqueueMessageSelfDeletio import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveSelfDeletionTimerSettingsForConversationUseCase import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCase import com.wire.kalium.logic.feature.user.IsFileSharingEnabledUseCase +import com.wire.kalium.logic.functional.Either import com.wire.kalium.logic.functional.onFailure import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow @@ -216,7 +220,8 @@ class MessageComposerViewModel @Inject constructor( shouldInformAboutDegradedBeforeSendingMessage() -> sureAboutMessagingDialogState = SureAboutMessagingDialogState.Visible.ConversationVerificationDegraded(messageBundle) shouldInformAboutUnderLegalHoldBeforeSendingMessage() -> - sureAboutMessagingDialogState = SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold(messageBundle) + sureAboutMessagingDialogState = + SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.BeforeSending(messageBundle) else -> sendMessage(messageBundle) } } @@ -231,7 +236,7 @@ class MessageComposerViewModel @Inject constructor( originalMessageId = originalMessageId, text = newContent, mentions = newMentions.map { it.intoMessageMention() }, - ) + ).handleLegalHoldFailureAfterSendingMessage() } sendTypingEvent(conversationId, TypingIndicatorMode.STOPPED) } @@ -256,7 +261,7 @@ class MessageComposerViewModel @Inject constructor( text = message, mentions = mentions.map { it.intoMessageMention() }, quotedMessageId = quotedMessageId - ) + ).handleLegalHoldFailureAfterSendingMessage() } sendTypingEvent(conversationId, TypingIndicatorMode.STOPPED) } @@ -335,7 +340,7 @@ class MessageComposerViewModel @Inject constructor( assetDataSize = dataSize, assetMimeType = mimeType, audioLengthInMs = 0L - ) + ).handleLegalHoldFailureAfterSendingMessage() } AttachmentType.VIDEO, @@ -354,7 +359,7 @@ class MessageComposerViewModel @Inject constructor( dataPath = dataPath, mimeType = mimeType ) - ) + ).handleLegalHoldFailureAfterSendingMessage() } catch (e: OutOfMemoryError) { appLogger.e("There was an OutOfMemory error while uploading the asset") onSnackbarMessage(ConversationSnackbarMessages.ErrorSendingAsset) @@ -366,6 +371,19 @@ class MessageComposerViewModel @Inject constructor( } } + private fun CoreFailure.handleLegalHoldFailureAfterSendingMessage() = also { + if (this is LegalHoldEnabledForConversationFailure) { + sureAboutMessagingDialogState = SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.AfterSending(this.messageId) + } + } + private fun Either.handleLegalHoldFailureAfterSendingMessage() = + onFailure { it.handleLegalHoldFailureAfterSendingMessage() } + private fun ScheduleNewAssetMessageResult.handleLegalHoldFailureAfterSendingMessage() = also { + if (it is ScheduleNewAssetMessageResult.Failure) { + it.coreFailure.handleLegalHoldFailureAfterSendingMessage() + } + } + fun retrySendingMessage(messageId: String) { viewModelScope.launch { retryFailedMessage(messageId = messageId, conversationId = conversationId) @@ -481,7 +499,14 @@ class MessageComposerViewModel @Inject constructor( (sureAboutMessagingDialogState as? SureAboutMessagingDialogState.Visible)?.let { viewModelScope.launch { it.markAsNotified() - trySendMessage(it.messageBundleToSend) + when (it) { + is SureAboutMessagingDialogState.Visible.ConversationVerificationDegraded -> + trySendMessage(it.messageBundleToSend) + is SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.BeforeSending -> + trySendMessage(it.messageBundleToSend) + is SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.AfterSending -> + retrySendingMessage(it.messageId) + } } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt index 11cb2ebe1e7..57044fd66da 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewState.kt @@ -23,6 +23,7 @@ package com.wire.android.ui.home.conversations import com.wire.android.ui.home.messagecomposer.state.MessageBundle import com.wire.android.ui.home.newconversation.model.Contact import com.wire.kalium.logic.data.asset.AttachmentType +import com.wire.kalium.logic.data.id.MessageId import com.wire.kalium.logic.data.message.SelfDeletionTimer import com.wire.kalium.logic.feature.conversation.InteractionAvailability import kotlin.time.Duration.Companion.ZERO @@ -51,8 +52,11 @@ sealed class InvalidLinkDialogState { sealed class SureAboutMessagingDialogState { data object Hidden : SureAboutMessagingDialogState() - sealed class Visible(open val messageBundleToSend: MessageBundle) : SureAboutMessagingDialogState() { - data class ConversationVerificationDegraded(override val messageBundleToSend: MessageBundle) : Visible(messageBundleToSend) - data class ConversationUnderLegalHold(override val messageBundleToSend: MessageBundle) : Visible(messageBundleToSend) + sealed class Visible : SureAboutMessagingDialogState() { + data class ConversationVerificationDegraded(val messageBundleToSend: MessageBundle) : Visible() + sealed class ConversationUnderLegalHold : Visible() { + data class BeforeSending(val messageBundleToSend: MessageBundle) : ConversationUnderLegalHold() + data class AfterSending(val messageId: MessageId) : ConversationUnderLegalHold() + } } } 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 74dcf018d6d..c891c20c117 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 @@ -55,8 +55,8 @@ import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.QualifiedID import com.wire.kalium.logic.data.user.UserId +import com.wire.kalium.logic.feature.client.FetchUsersClientsFromRemoteUseCase import com.wire.kalium.logic.feature.client.ObserveClientsByUserIdUseCase -import com.wire.kalium.logic.feature.client.PersistOtherUserClientsUseCase import com.wire.kalium.logic.feature.connection.BlockUserResult import com.wire.kalium.logic.feature.connection.BlockUserUseCase import com.wire.kalium.logic.feature.connection.UnblockUserResult @@ -100,7 +100,7 @@ class OtherUserProfileScreenViewModel @Inject constructor( private val removeMemberFromConversation: RemoveMemberFromConversationUseCase, private val updateMemberRole: UpdateConversationMemberRoleUseCase, private val observeClientList: ObserveClientsByUserIdUseCase, - private val persistOtherUserClients: PersistOtherUserClientsUseCase, + private val fetchUsersClients: FetchUsersClientsFromRemoteUseCase, private val clearConversationContentUseCase: ClearConversationContentUseCase, private val updateConversationArchivedStatus: UpdateConversationArchivedStatusUseCase, savedStateHandle: SavedStateHandle @@ -152,7 +152,7 @@ class OtherUserProfileScreenViewModel @Inject constructor( private fun persistClients() { viewModelScope.launch(dispatchers.io()) { - persistOtherUserClients(userId) + fetchUsersClients(listOf(userId)) } } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt index eabe7690898..b5795f26527 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelArrangement.kt @@ -278,6 +278,16 @@ internal class MessageComposerViewModelArrangement { ) } returns Either.Right(Unit) } + fun withFailedSendTextMessage(failure: CoreFailure) = apply { + coEvery { + sendTextMessage( + any(), + any(), + any(), + any() + ) + } returns Either.Left(failure) + } fun withSuccessfulSendEditTextMessage() = apply { coEvery { @@ -334,6 +344,10 @@ internal class MessageComposerViewModelArrangement { coEvery { observeConversationUnderLegalHoldNotified(any()) } returns flowOf(flag) } + fun withSuccessfulRetryFailedMessage() = apply { + coEvery { retryFailedMessageUseCase(any(), any()) } returns Either.Right(Unit) + } + fun arrange() = this to viewModel } diff --git a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt index 362a36dbd4b..64b518fa4a5 100644 --- a/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/home/conversations/MessageComposerViewModelTest.kt @@ -33,6 +33,7 @@ import com.wire.android.ui.home.messagecomposer.state.Ping import com.wire.kalium.logic.data.asset.AttachmentType import com.wire.kalium.logic.data.conversation.Conversation import com.wire.kalium.logic.data.message.SelfDeletionTimer +import com.wire.kalium.logic.failure.LegalHoldEnabledForConversationFailure import com.wire.kalium.logic.feature.asset.GetAssetSizeLimitUseCaseImpl.Companion.ASSET_SIZE_DEFAULT_LIMIT_BYTES import io.mockk.coVerify import io.mockk.verify @@ -680,7 +681,7 @@ class MessageComposerViewModelTest { } @Test - fun `given that user needs to be informed about enabled legal hold, when invoked sending, then message is not sent and dialog shown`() = + fun `given that user needs to be informed about enabled legal hold when sending, then message is not sent and dialog shown`() = runTest { // given val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList()) @@ -693,13 +694,13 @@ class MessageComposerViewModelTest { // then coVerify(exactly = 0) { arrangement.sendTextMessage.invoke(any(), any(), any(), any()) } assertEquals( - SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold(messageBundle), + SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.BeforeSending(messageBundle), viewModel.sureAboutMessagingDialogState ) } @Test - fun `given that user chose to dismiss when enabled legal hold, when invoked sending, then message is not sent and dialog hidden`() = + fun `given that user chose to dismiss when enabled legal hold before sending, then message is not sent and dialog hidden`() = runTest { // given val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList()) @@ -718,7 +719,7 @@ class MessageComposerViewModelTest { } @Test - fun `given that user chose to send anyway when enabled legal hold, when invoked sending, then message is sent and dialog hidden`() = + fun `given that user chose to send anyway when enabled legal hold before sending, then message is sent and dialog hidden`() = runTest { // given val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList()) @@ -736,4 +737,64 @@ class MessageComposerViewModelTest { coVerify(exactly = 1) { arrangement.sendTextMessage.invoke(any(), any(), any(), any()) } assertEquals(SureAboutMessagingDialogState.Hidden, viewModel.sureAboutMessagingDialogState) } + + @Test + fun `given that user needs to be informed about enabled legal hold when sending fails, then message is not resent and dialog shown`() = + runTest { + // given + val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList()) + val messageId = "messageId" + val (arrangement, viewModel) = MessageComposerViewModelArrangement() + .withSuccessfulViewModelInit() + .withFailedSendTextMessage(LegalHoldEnabledForConversationFailure(messageId)) + .arrange() + // when + viewModel.trySendMessage(messageBundle) + // then + coVerify(exactly = 0) { arrangement.retryFailedMessageUseCase.invoke(eq(messageId), any()) } + assertEquals( + SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold.AfterSending(messageId), + viewModel.sureAboutMessagingDialogState + ) + } + + @Test + fun `given that user chose to dismiss when enabled legal hold when sending fails, then message is not resent and dialog hidden`() = + runTest { + // given + val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList()) + val messageId = "messageId" + val (arrangement, viewModel) = MessageComposerViewModelArrangement() + .withSuccessfulViewModelInit() + .withObserveConversationUnderLegalHoldNotified(true) + .withFailedSendTextMessage(LegalHoldEnabledForConversationFailure(messageId)) + .arrange() + viewModel.trySendMessage(messageBundle) + // when + viewModel.dismissSureAboutSendingMessage() + advanceUntilIdle() + // then + coVerify(exactly = 0) { arrangement.retryFailedMessageUseCase.invoke(any(), any()) } + assertEquals(SureAboutMessagingDialogState.Hidden, viewModel.sureAboutMessagingDialogState) + } + + @Test + fun `given that user chose to send anyway when enabled legal hold when sending fails, then message is resent and dialog hidden`() = + runTest { + // given + val messageBundle = ComposableMessageBundle.SendTextMessageBundle("mocked-text-message", emptyList()) + val messageId = "messageId" + val (arrangement, viewModel) = MessageComposerViewModelArrangement() + .withSuccessfulViewModelInit() + .withFailedSendTextMessage(LegalHoldEnabledForConversationFailure(messageId)) + .withSuccessfulRetryFailedMessage() + .arrange() + viewModel.trySendMessage(messageBundle) + // when + viewModel.acceptSureAboutSendingMessage() + advanceUntilIdle() + // then + coVerify(exactly = 1) { arrangement.retryFailedMessageUseCase.invoke(eq(messageId), any()) } + assertEquals(SureAboutMessagingDialogState.Hidden, viewModel.sureAboutMessagingDialogState) + } } 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 6bf5877728c..7af66d23acc 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 @@ -32,8 +32,8 @@ import com.wire.android.ui.userprofile.other.OtherUserProfileScreenViewModelTest import com.wire.android.ui.userprofile.other.OtherUserProfileScreenViewModelTest.Companion.USER_ID import com.wire.android.util.ui.WireSessionImageLoader import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.feature.client.FetchUsersClientsFromRemoteUseCase import com.wire.kalium.logic.feature.client.ObserveClientsByUserIdUseCase -import com.wire.kalium.logic.feature.client.PersistOtherUserClientsUseCase import com.wire.kalium.logic.feature.connection.BlockUserResult import com.wire.kalium.logic.feature.connection.BlockUserUseCase import com.wire.kalium.logic.feature.connection.UnblockUserUseCase @@ -96,7 +96,7 @@ internal class OtherUserProfileViewModelArrangement { lateinit var observeClientList: ObserveClientsByUserIdUseCase @MockK - lateinit var persistOtherUserClientsUseCase: PersistOtherUserClientsUseCase + lateinit var fetchUsersClientsFromRemote: FetchUsersClientsFromRemoteUseCase @MockK lateinit var clearConversationContent: ClearConversationContentUseCase @@ -118,7 +118,7 @@ internal class OtherUserProfileViewModelArrangement { removeMemberFromConversationUseCase, updateConversationMemberRoleUseCase, observeClientList, - persistOtherUserClientsUseCase, + fetchUsersClientsFromRemote, clearConversationContent, updateConversationArchivedStatus, savedStateHandle diff --git a/kalium b/kalium index 10e2eec72c6..8b74269d61e 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 10e2eec72c639bd680db417f2c778266dd4520ba +Subproject commit 8b74269d61e94c518f6dcbc974cc69668dcecd28