From 398cd9af2beaaf37e2ed46ce6e77cb9da50fa081 Mon Sep 17 00:00:00 2001 From: Serdar Ozturk Date: Thu, 30 Oct 2025 13:15:47 +0000 Subject: [PATCH] Add Block Sender confirmation dialog - If the sender is a contact, then the title changes to Block this contact - Implemented dialog state and reducer - Added view actions for the dialog ET-5145 --- .../model/BlockSenderDialogState.kt | 34 +++++++ .../model/ConversationDetailOperation.kt | 13 ++- .../model/ConversationDetailState.kt | 6 +- .../ConversationDetailsPreviewData.kt | 22 +++-- .../ConversationBlockSenderDialogReducer.kt | 37 ++++++++ .../reducer/ConversationDetailReducer.kt | 15 +++- .../ui/ConversationDetailScreen.kt | 14 ++- .../ui/dialog/BlockSenderDialog.kt | 81 +++++++++++++++++ .../viewmodel/ConversationDetailViewModel.kt | 25 +++--- ...onversationBlockSenderDialogReducerTest.kt | 90 +++++++++++++++++++ .../reducer/ConversationDetailReducerTest.kt | 18 ++++ ...versationDetailViewModelIntegrationTest.kt | 2 + .../ConversationDetailViewModelTest.kt | 19 +++- .../model/ContactActionUiModel.kt | 3 +- .../ContactActionsBottomSheetReducer.kt | 17 ++-- .../ContactActionsBottomSheetContent.kt | 13 ++- .../src/main/res/values/strings.xml | 5 +- .../ContactActionsBottomSheetReducerTest.kt | 78 ++++++++++++++-- .../contact/ContactActionsGroupsSample.kt | 2 +- 19 files changed, 449 insertions(+), 45 deletions(-) create mode 100644 mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/BlockSenderDialogState.kt create mode 100644 mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducer.kt create mode 100644 mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/dialog/BlockSenderDialog.kt create mode 100644 mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducerTest.kt diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/BlockSenderDialogState.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/BlockSenderDialogState.kt new file mode 100644 index 0000000000..4e582da105 --- /dev/null +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/BlockSenderDialogState.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail 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. + * + * Proton Mail 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 Proton Mail. If not, see . + */ + +package ch.protonmail.android.maildetail.presentation.model + +import ch.protonmail.android.mailcontact.domain.model.ContactId + +sealed interface BlockSenderDialogState { + + object Hidden : BlockSenderDialogState + + sealed interface Shown : BlockSenderDialogState { + data class ShowConfirmation( + val messageId: MessageIdUiModel?, + val email: String, + val contactId: ContactId? + ) : Shown + } +} diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailOperation.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailOperation.kt index 8cd62e3f22..b6f77d0567 100644 --- a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailOperation.kt +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailOperation.kt @@ -27,6 +27,7 @@ import ch.protonmail.android.mailattachments.domain.model.OpenAttachmentIntentVa import ch.protonmail.android.mailcommon.presentation.model.AvatarUiModel import ch.protonmail.android.mailcommon.presentation.model.BottomBarEvent import ch.protonmail.android.mailcommon.presentation.model.BottomSheetOperation +import ch.protonmail.android.mailcontact.domain.model.ContactId import ch.protonmail.android.mailconversation.domain.entity.HiddenMessagesBanner import ch.protonmail.android.maildetail.domain.model.OpenProtonCalendarIntentValues import ch.protonmail.android.maildetail.presentation.R @@ -39,6 +40,7 @@ import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOpe import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOperation.AffectingMessageBar import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOperation.AffectingMessages import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOperation.AffectingReportPhishingDialog +import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOperation.AffectingBlockSenderDialog import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOperation.AffectingHiddenMessagesBanner import ch.protonmail.android.maillabel.domain.model.LabelId import ch.protonmail.android.maillabel.domain.model.SystemLabelId @@ -63,6 +65,8 @@ sealed interface ConversationDetailOperation { sealed interface AffectingHiddenMessagesBanner sealed interface AffectingMarkAsLegitimateDialog sealed interface AffectingEditScheduleMessageDialog + sealed interface AffectingBlockSenderDialog + } sealed interface ConversationDetailEvent : ConversationDetailOperation { @@ -350,8 +354,13 @@ sealed interface ConversationDetailViewAction : ConversationDetailOperation { data class UnblockSender(val messageId: MessageIdUiModel?, val email: String) : ConversationDetailViewAction, AffectingBottomSheet - data class BlockSender(val messageId: MessageIdUiModel?, val email: String) : - ConversationDetailViewAction, AffectingBottomSheet + data class BlockSender(val messageId: MessageIdUiModel?, val email: String, val contactId: ContactId?) : + ConversationDetailViewAction, AffectingBottomSheet, AffectingBlockSenderDialog + object BlockSenderDismissed : ConversationDetailViewAction, AffectingBlockSenderDialog + data class BlockSenderConfirmed( + val messageId: MessageIdUiModel?, + val email: String + ) : ConversationDetailViewAction, AffectingBlockSenderDialog data object EditScheduleSendMessageDismissed : ConversationDetailViewAction, AffectingEditScheduleMessageDialog diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailState.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailState.kt index aabb3f1ae4..3cf8279151 100644 --- a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailState.kt +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/model/ConversationDetailState.kt @@ -46,7 +46,8 @@ data class ConversationDetailState( val reportPhishingDialogState: ReportPhishingDialogState, val hiddenMessagesBannerState: HiddenMessagesBannerState, val markAsLegitimateDialogState: MarkAsLegitimateDialogState, - val editScheduledMessageDialogState: EditScheduledMessageDialogState + val editScheduledMessageDialogState: EditScheduledMessageDialogState, + val blockSenderDialogState: BlockSenderDialogState ) { companion object { @@ -71,7 +72,8 @@ data class ConversationDetailState( reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) } } diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/previewdata/ConversationDetailsPreviewData.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/previewdata/ConversationDetailsPreviewData.kt index 57a3d8835f..b1a707e015 100644 --- a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/previewdata/ConversationDetailsPreviewData.kt +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/previewdata/ConversationDetailsPreviewData.kt @@ -30,6 +30,7 @@ import ch.protonmail.android.mailcommon.presentation.model.contentDescription import ch.protonmail.android.mailcommon.presentation.model.description import ch.protonmail.android.mailcommon.presentation.model.iconDrawable import ch.protonmail.android.mailcommon.presentation.sample.TextMessageSample +import ch.protonmail.android.maildetail.presentation.model.BlockSenderDialogState import ch.protonmail.android.maildetail.presentation.model.ConversationDeleteState import ch.protonmail.android.maildetail.presentation.model.ConversationDetailMetadataState import ch.protonmail.android.maildetail.presentation.model.ConversationDetailState @@ -97,7 +98,8 @@ object ConversationDetailsPreviewData { reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) val SuccessWithRandomMessageIds = ConversationDetailState( @@ -154,7 +156,8 @@ object ConversationDetailsPreviewData { reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) val FailedLoadingConversation = ConversationDetailState( @@ -177,7 +180,8 @@ object ConversationDetailsPreviewData { reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) val FailedLoadingMessages = ConversationDetailState( @@ -200,7 +204,8 @@ object ConversationDetailsPreviewData { reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) val FailedLoadingBottomBar = ConversationDetailState( @@ -223,7 +228,8 @@ object ConversationDetailsPreviewData { reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) val Loading = ConversationDetailState( @@ -246,7 +252,8 @@ object ConversationDetailsPreviewData { reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) val NotLoggedIn = ConversationDetailState( @@ -269,7 +276,8 @@ object ConversationDetailsPreviewData { reportPhishingDialogState = ReportPhishingDialogState.Hidden, hiddenMessagesBannerState = HiddenMessagesBannerState.Hidden, markAsLegitimateDialogState = MarkAsLegitimateDialogState.Hidden, - editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden + editScheduledMessageDialogState = EditScheduledMessageDialogState.Hidden, + blockSenderDialogState = BlockSenderDialogState.Hidden ) } diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducer.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducer.kt new file mode 100644 index 0000000000..f165620538 --- /dev/null +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducer.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail 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. + * + * Proton Mail 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 Proton Mail. If not, see . + */ + +package ch.protonmail.android.maildetail.presentation.reducer + +import ch.protonmail.android.maildetail.presentation.model.BlockSenderDialogState +import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOperation +import ch.protonmail.android.maildetail.presentation.model.ConversationDetailViewAction +import javax.inject.Inject + +class ConversationBlockSenderDialogReducer @Inject constructor() { + + internal fun newStateFrom(operation: ConversationDetailOperation.AffectingBlockSenderDialog) = when (operation) { + is ConversationDetailViewAction.BlockSenderDismissed, + is ConversationDetailViewAction.BlockSenderConfirmed -> BlockSenderDialogState.Hidden + is ConversationDetailViewAction.BlockSender -> BlockSenderDialogState.Shown.ShowConfirmation( + messageId = operation.messageId, + email = operation.email, + contactId = operation.contactId + ) + } +} diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducer.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducer.kt index d53f4d3724..a377bf3bd7 100644 --- a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducer.kt +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducer.kt @@ -27,6 +27,7 @@ import ch.protonmail.android.mailcommon.presentation.reducer.BottomBarReducer import ch.protonmail.android.maildetail.domain.model.OpenProtonCalendarIntentValues import ch.protonmail.android.maildetail.presentation.R import ch.protonmail.android.maildetail.presentation.mapper.ActionResultMapper +import ch.protonmail.android.maildetail.presentation.model.BlockSenderDialogState import ch.protonmail.android.maildetail.presentation.model.ConversationDeleteState import ch.protonmail.android.maildetail.presentation.model.ConversationDetailEvent.ConversationBottomBarEvent import ch.protonmail.android.maildetail.presentation.model.ConversationDetailEvent.ConversationBottomSheetEvent @@ -85,6 +86,7 @@ class ConversationDetailReducer @Inject constructor( private val bottomSheetReducer: BottomSheetReducer, private val deleteDialogReducer: ConversationDeleteDialogReducer, private val reportPhishingDialogReducer: ConversationReportPhishingDialogReducer, + private val blockSenderDialogReducer: ConversationBlockSenderDialogReducer, private val hiddenMessagesBannerReducer: HiddenMessagesBannerReducer, private val markAsLegitimateDialogReducer: MarkAsLegitimateDialogReducer, private val editScheduledMessageDialogReducer: EditScheduledMessageDialogReducer, @@ -114,7 +116,8 @@ class ConversationDetailReducer @Inject constructor( reportPhishingDialogState = currentState.toNewReportPhishingDialogState(operation), hiddenMessagesBannerState = currentState.toNewHiddenMessagesBannerState(operation), markAsLegitimateDialogState = currentState.toNewMarkAsLegitimateDialogState(operation), - editScheduledMessageDialogState = currentState.toNewEditScheduleMessageDialogState(operation) + editScheduledMessageDialogState = currentState.toNewEditScheduleMessageDialogState(operation), + blockSenderDialogState = currentState.toNewBlockSenderDialogState(operation) ) } @@ -380,6 +383,16 @@ class ConversationDetailReducer @Inject constructor( } } + private fun ConversationDetailState.toNewBlockSenderDialogState( + operation: ConversationDetailOperation + ): BlockSenderDialogState { + return if (operation is ConversationDetailOperation.AffectingBlockSenderDialog) { + blockSenderDialogReducer.newStateFrom(operation) + } else { + blockSenderDialogState + } + } + private fun ConversationDetailState.toNewHiddenMessagesBannerState( operation: ConversationDetailOperation ): HiddenMessagesBannerState { diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/ConversationDetailScreen.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/ConversationDetailScreen.kt index a065e86d9d..2c8972f7fa 100644 --- a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/ConversationDetailScreen.kt +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/ConversationDetailScreen.kt @@ -116,6 +116,7 @@ import ch.protonmail.android.maildetail.presentation.model.MessageIdUiModel import ch.protonmail.android.maildetail.presentation.model.MoreActionsBottomSheetEntryPoint import ch.protonmail.android.maildetail.presentation.model.ParticipantUiModel import ch.protonmail.android.maildetail.presentation.previewdata.ConversationDetailsPreviewProvider +import ch.protonmail.android.maildetail.presentation.ui.dialog.BlockSenderDialog import ch.protonmail.android.maildetail.presentation.ui.dialog.EditScheduleSendDialog import ch.protonmail.android.maildetail.presentation.ui.dialog.MarkAsLegitimateDialog import ch.protonmail.android.maildetail.presentation.ui.dialog.ReportPhishingDialog @@ -242,6 +243,13 @@ fun ConversationDetailScreen( onDismiss = { viewModel.submit(ConversationDetailViewAction.EditScheduleSendMessageDismissed) } ) + BlockSenderDialog( + state = state.blockSenderDialogState, + onConfirm = { messageId, email -> + viewModel.submit(ConversationDetailViewAction.BlockSenderConfirmed(messageId, email)) + }, + onDismiss = { viewModel.submit(ConversationDetailViewAction.BlockSenderDismissed) } + ) ProtonModalBottomSheetLayout( showBottomSheet = showBottomSheet, @@ -435,10 +443,12 @@ fun ConversationDetailScreen( viewModel.submit(ConversationDetailViewAction.DismissBottomSheet) actions.onComposeNewMessage(it.address) }, - onBlockClicked = { participant, messageId -> + onBlockClicked = { participant, messageId, contactId -> viewModel.submit( ConversationDetailViewAction.BlockSender( - messageId?.let { MessageIdUiModel(it.id) }, participant.address + messageId = messageId?.let { MessageIdUiModel(it.id) }, + email = participant.address, + contactId = contactId ) ) diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/dialog/BlockSenderDialog.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/dialog/BlockSenderDialog.kt new file mode 100644 index 0000000000..738cc903c6 --- /dev/null +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/ui/dialog/BlockSenderDialog.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail 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. + * + * Proton Mail 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 Proton Mail. If not, see . + */ + +package ch.protonmail.android.maildetail.presentation.ui.dialog + +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import ch.protonmail.android.maildetail.presentation.R +import ch.protonmail.android.design.compose.component.ProtonAlertDialog +import ch.protonmail.android.design.compose.component.ProtonAlertDialogButton +import ch.protonmail.android.design.compose.component.ProtonAlertDialogText +import ch.protonmail.android.design.compose.theme.ProtonTheme +import ch.protonmail.android.mailcontact.domain.model.ContactId +import ch.protonmail.android.maildetail.presentation.model.BlockSenderDialogState +import ch.protonmail.android.maildetail.presentation.model.MessageIdUiModel + +@Composable +fun BlockSenderDialog( + state: BlockSenderDialogState, + onDismiss: () -> Unit, + onConfirm: (MessageIdUiModel?, String) -> Unit +) { + if (state is BlockSenderDialogState.Shown) { + when (state) { + is BlockSenderDialogState.Shown.ShowConfirmation -> BlockSenderDialog( + email = state.email, + contactId = state.contactId, + onDismiss = onDismiss, + onConfirm = { onConfirm(state.messageId, state.email) } + ) + } + } +} + +@Composable +private fun BlockSenderDialog( + email: String, + contactId: ContactId?, + onDismiss: () -> Unit, + onConfirm: () -> Unit +) { + ProtonAlertDialog( + onDismissRequest = onDismiss, + confirmButton = { + ProtonAlertDialogButton( + titleResId = R.string.contact_actions_block_contact_dialog_button_block, + textColor = ProtonTheme.colors.notificationError + ) { onConfirm() } + }, + dismissButton = { + ProtonAlertDialogButton(R.string.contact_actions_block_contact_dialog_button_cancel) { onDismiss() } + }, + title = stringResource( + id = contactId?.let { R.string.contact_actions_block_contact } + ?: R.string.contact_actions_block_address + ), + text = { + ProtonAlertDialogText( + text = stringResource( + id = R.string.contact_actions_block_contact_dialog_text, + email + ) + ) + } + ) +} diff --git a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt index 15676f2374..c8c89484e7 100644 --- a/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt +++ b/mail-detail/presentation/src/main/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModel.kt @@ -387,6 +387,8 @@ class ConversationDetailViewModel @Inject constructor( is ScrollRequestCompleted, is ConversationDetailViewAction.ReportPhishing, is ConversationDetailViewAction.ReportPhishingDismissed, + is ConversationDetailViewAction.BlockSender, + is ConversationDetailViewAction.BlockSenderDismissed, is ConversationDetailViewAction.MarkMessageAsLegitimate, is ConversationDetailViewAction.MarkMessageAsLegitimateDismissed, is ConversationDetailViewAction.EditScheduleSendMessageDismissed, @@ -429,7 +431,7 @@ class ConversationDetailViewModel @Inject constructor( handleMarkMessageAsLegitimateConfirmed(action) is ConversationDetailViewAction.UnblockSender -> handleUnblockSender(action) - is ConversationDetailViewAction.BlockSender -> handleBlockSender(action) + is ConversationDetailViewAction.BlockSenderConfirmed -> handleBlockSenderConfirmed(action) is ConversationDetailViewAction.EditScheduleSendMessageConfirmed -> handleEditScheduleSendMessage(action) is ConversationDetailViewAction.PrintMessage -> handlePrintMessage(action.context, action.messageId) is ConversationDetailViewAction.RetryRsvpEventLoading -> @@ -1693,19 +1695,20 @@ class ConversationDetailViewModel @Inject constructor( } } - private fun handleBlockSender(action: ConversationDetailViewAction.BlockSender) = viewModelScope.launch { + private fun handleBlockSenderConfirmed(action: ConversationDetailViewAction.BlockSenderConfirmed) = viewModelScope.launch { - blockSender( - userId = primaryUserId.first(), - email = action.email - ).fold( - ifLeft = { Timber.e("Failed to block sender in message ${action.messageId?.id}") }, - ifRight = { action.messageId?.let { setOrRefreshMessageBody(it) } } - ) + viewModelScope.launch { + blockSender( + userId = primaryUserId.first(), + email = action.email + ).fold( + ifLeft = { Timber.e("Failed to block sender in message ${action.messageId?.id}") }, + ifRight = { action.messageId?.let { setOrRefreshMessageBody(it) } } + ) - emitNewStateFrom(action) + emitNewStateFrom(action) + } } - } private fun handleGetRsvpEvent(messageId: MessageId, refresh: Boolean) { viewModelScope.launch { diff --git a/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducerTest.kt b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducerTest.kt new file mode 100644 index 0000000000..c969c6cde4 --- /dev/null +++ b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationBlockSenderDialogReducerTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025 Proton Technologies AG + * This file is part of Proton Technologies AG and Proton Mail. + * + * Proton Mail 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. + * + * Proton Mail 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 Proton Mail. If not, see . + */ + +package ch.protonmail.android.maildetail.presentation.reducer + +import ch.protonmail.android.maildetail.presentation.model.BlockSenderDialogState +import ch.protonmail.android.maildetail.presentation.model.ConversationDetailOperation +import ch.protonmail.android.maildetail.presentation.model.ConversationDetailViewAction +import ch.protonmail.android.maildetail.presentation.model.MessageIdUiModel +import ch.protonmail.android.testdata.contact.ContactIdSample +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class ConversationBlockSenderDialogReducerTest( + private val testName: String, + private val testInput: TestInput +) { + + private val blockSenderDialogReducer = ConversationBlockSenderDialogReducer() + + @Test + fun `should produce the expected new state`() = with(testInput) { + val actualState = blockSenderDialogReducer.newStateFrom(operation) + + assertEquals(testName, expectedState, actualState) + } + + companion object { + + private val transitions = listOf( + TestInput( + operation = ConversationDetailViewAction.BlockSenderDismissed, + expectedState = BlockSenderDialogState.Hidden + ), + TestInput( + operation = ConversationDetailViewAction.BlockSenderConfirmed( + MessageIdUiModel("msg-123"), "test@example.com" + ), + expectedState = BlockSenderDialogState.Hidden + ), + TestInput( + operation = ConversationDetailViewAction.BlockSender( + messageId = MessageIdUiModel("HtmlInvoice"), + email = "test@example.com", + contactId = ContactIdSample.Doe + ), + expectedState = BlockSenderDialogState.Shown.ShowConfirmation( + messageId = MessageIdUiModel("HtmlInvoice"), + email = "test@example.com", + contactId = ContactIdSample.Doe + ) + ) + ) + + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun data(): Collection> { + return transitions.map { + val testName = """ + Operation: ${it.operation} + Expected State: ${it.expectedState} + """.trimIndent() + arrayOf(testName, it) + } + } + + data class TestInput( + val operation: ConversationDetailOperation.AffectingBlockSenderDialog, + val expectedState: BlockSenderDialogState + ) + } +} diff --git a/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducerTest.kt b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducerTest.kt index bc31d4a39f..2d10cc096e 100644 --- a/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducerTest.kt +++ b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/reducer/ConversationDetailReducerTest.kt @@ -78,6 +78,7 @@ class ConversationDetailReducerTest( private val bottomSheetReducer = mockk(relaxed = true) private val deleteDialogReducer = mockk(relaxed = true) private val reportPhishingDialogReducer = mockk(relaxed = true) + private val blockSenderDialogReducer = mockk(relaxed = true) private val trashedMessagesBannerReducer = mockk(relaxed = true) private val markAsLegitimateDialogReducer = mockk(relaxed = true) private val editScheduledMessageDialogReducer = mockk(relaxed = true) @@ -88,6 +89,7 @@ class ConversationDetailReducerTest( bottomSheetReducer = bottomSheetReducer, deleteDialogReducer = deleteDialogReducer, reportPhishingDialogReducer = reportPhishingDialogReducer, + blockSenderDialogReducer = blockSenderDialogReducer, hiddenMessagesBannerReducer = trashedMessagesBannerReducer, markAsLegitimateDialogReducer = markAsLegitimateDialogReducer, editScheduledMessageDialogReducer = editScheduledMessageDialogReducer, @@ -181,6 +183,12 @@ class ConversationDetailReducerTest( verify { reportPhishingDialogReducer wasNot Called } } + if (reducesBlockSenderDialog) { + verify { blockSenderDialogReducer.newStateFrom(any()) } + } else { + verify { blockSenderDialogReducer wasNot Called } + } + if (reducesMarkAsLegitimateDialog) { verify { markAsLegitimateDialogReducer.newStateFrom(any()) } } else { @@ -210,6 +218,7 @@ class ConversationDetailReducerTest( val reducesDeleteDialog: Boolean, val reducesTrashedMessagesBanner: Boolean, val reducesReportPhishingDialog: Boolean, + val reducesBlockSenderDialog: Boolean, val reducesMarkAsLegitimateDialog: Boolean, val reducesEditScheduleSendDialog: Boolean ) { @@ -278,6 +287,13 @@ class ConversationDetailReducerTest( MessageId(messageId.id) ) affects listOf(ReportPhishingDialog), ConversationDetailViewAction.ReportPhishingDismissed affects listOf(ReportPhishingDialog), + ConversationDetailViewAction.BlockSender( + MessageIdUiModel(messageId.id), participant.participantAddress, null + ) affects listOf(BottomSheet, BlockSenderDialog), + ConversationDetailViewAction.BlockSenderConfirmed( + MessageIdUiModel(messageId.id), participant.participantAddress + ) affects listOf(BlockSenderDialog), + ConversationDetailViewAction.BlockSenderDismissed affects listOf(BlockSenderDialog), ConversationDetailViewAction.MarkMessageAsLegitimate( MessageId(messageId.id), isPhishing = true ) affects listOf(MarkAsLegitimateDialog), @@ -401,6 +417,7 @@ private infix fun ConversationDetailOperation.affects(entities: List) = reducesDeleteDialog = entities.contains(DeleteDialog), reducesTrashedMessagesBanner = entities.contains(HiddenMessagesBanner), reducesReportPhishingDialog = entities.contains(ReportPhishingDialog), + reducesBlockSenderDialog = entities.contains(BlockSenderDialog), reducesMarkAsLegitimateDialog = entities.contains(MarkAsLegitimateDialog), reducesEditScheduleSendDialog = entities.contains(EditScheduleSendDialog) ) @@ -422,6 +439,7 @@ private data object MessageScroll : Entity private data object DeleteDialog : Entity private data object HiddenMessagesBanner : Entity private data object ReportPhishingDialog : Entity +private data object BlockSenderDialog : Entity private data object MarkAsLegitimateDialog : Entity private data object EditScheduleSendDialog : Entity diff --git a/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelIntegrationTest.kt b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelIntegrationTest.kt index 24a73aabb3..e7a85d4454 100644 --- a/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelIntegrationTest.kt +++ b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelIntegrationTest.kt @@ -126,6 +126,7 @@ import ch.protonmail.android.maildetail.presentation.model.MarkAsLegitimateDialo import ch.protonmail.android.maildetail.presentation.model.MessageIdUiModel import ch.protonmail.android.maildetail.presentation.model.MoreActionsBottomSheetEntryPoint import ch.protonmail.android.maildetail.presentation.model.ReportPhishingDialogState +import ch.protonmail.android.maildetail.presentation.reducer.ConversationBlockSenderDialogReducer import ch.protonmail.android.maildetail.presentation.reducer.ConversationDeleteDialogReducer import ch.protonmail.android.maildetail.presentation.reducer.ConversationDetailMessagesReducer import ch.protonmail.android.maildetail.presentation.reducer.ConversationDetailMetadataReducer @@ -483,6 +484,7 @@ internal class ConversationDetailViewModelIntegrationTest { ), deleteDialogReducer = ConversationDeleteDialogReducer(), reportPhishingDialogReducer = ConversationReportPhishingDialogReducer(), + blockSenderDialogReducer = ConversationBlockSenderDialogReducer(), hiddenMessagesBannerReducer = HiddenMessagesBannerReducer(), markAsLegitimateDialogReducer = MarkAsLegitimateDialogReducer(), editScheduledMessageDialogReducer = EditScheduledMessageDialogReducer(), diff --git a/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelTest.kt b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelTest.kt index 6fdab104da..94ef91c49c 100644 --- a/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelTest.kt +++ b/mail-detail/presentation/src/test/kotlin/ch/protonmail/android/maildetail/presentation/viewmodel/ConversationDetailViewModelTest.kt @@ -2043,7 +2043,7 @@ class ConversationDetailViewModelTest { // When viewModel.state.test { initialStateEmitted() - viewModel.submit(ConversationDetailViewAction.BlockSender(messageIdUiModel, email)) + viewModel.submit(ConversationDetailViewAction.BlockSenderConfirmed(messageIdUiModel, email)) advanceUntilIdle() @@ -2053,6 +2053,23 @@ class ConversationDetailViewModelTest { } } + @Test + fun `do not call block sender when dismissed`() = runTest { + // Given + viewModel.state.test { + initialStateEmitted() + + // When + viewModel.submit(ConversationDetailViewAction.BlockSenderDismissed) + + advanceUntilIdle() + + // Then + coVerify(exactly = 0) { blockSender(userId, any()) } + cancelAndIgnoreRemainingEvents() + } + } + @Suppress("LongMethod") private fun setupCollapsedToExpandMessagesState( withUnreadMessage: Boolean = false diff --git a/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/model/ContactActionUiModel.kt b/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/model/ContactActionUiModel.kt index cb4e1abcfe..150a224f1a 100644 --- a/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/model/ContactActionUiModel.kt +++ b/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/model/ContactActionUiModel.kt @@ -20,6 +20,7 @@ package ch.protonmail.android.mailmessage.presentation.model import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import ch.protonmail.android.mailcontact.domain.model.ContactId import ch.protonmail.android.mailmessage.domain.model.Participant import ch.protonmail.android.mailmessage.presentation.R @@ -53,7 +54,7 @@ sealed class ContactActionUiModel( descriptionRes = R.string.contact_actions_add_contact_description ) - data class BlockContact(val participant: Participant) : ContactActionUiModel( + data class BlockContact(val participant: Participant, val contactId: ContactId) : ContactActionUiModel( iconRes = R.drawable.ic_proton_circle_slash, textRes = R.string.contact_actions_block_contact, descriptionRes = R.string.contact_actions_block_contact_description diff --git a/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducer.kt b/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducer.kt index 7c63329730..0c2f7a5e4f 100644 --- a/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducer.kt +++ b/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducer.kt @@ -54,11 +54,18 @@ class ContactActionsBottomSheetReducer @Inject constructor() { emptyList() } else { buildList { - val action = when { - contactId == null && isSenderBlocked -> ContactActionUiModel.UnblockAddress(participant) - contactId == null && !isSenderBlocked -> ContactActionUiModel.BlockAddress(participant) - contactId != null && isSenderBlocked -> ContactActionUiModel.UnblockContact(participant) - else -> ContactActionUiModel.BlockContact(participant) + val action = if (contactId == null) { + if (isSenderBlocked) { + ContactActionUiModel.UnblockAddress(participant) + } else { + ContactActionUiModel.BlockAddress(participant) + } + } else { + if (isSenderBlocked) { + ContactActionUiModel.UnblockContact(participant) + } else { + ContactActionUiModel.BlockContact(participant, contactId) + } } add(action) } diff --git a/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/ui/bottomsheet/ContactActionsBottomSheetContent.kt b/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/ui/bottomsheet/ContactActionsBottomSheetContent.kt index 905968e12e..a9fecfae64 100644 --- a/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/ui/bottomsheet/ContactActionsBottomSheetContent.kt +++ b/mail-message/presentation/src/main/kotlin/ch/protonmail/android/mailmessage/presentation/ui/bottomsheet/ContactActionsBottomSheetContent.kt @@ -42,6 +42,7 @@ import ch.protonmail.android.design.compose.theme.titleLargeNorm import ch.protonmail.android.mailcommon.presentation.compose.Avatar import ch.protonmail.android.mailcommon.presentation.compose.MailDimens import ch.protonmail.android.mailcommon.presentation.model.AvatarUiModel +import ch.protonmail.android.mailcontact.domain.model.ContactId import ch.protonmail.android.mailmessage.domain.model.MessageId import ch.protonmail.android.mailmessage.domain.model.Participant import ch.protonmail.android.mailmessage.presentation.model.ContactActionUiModel @@ -133,9 +134,13 @@ private fun callbackForActions( is ContactActionUiModel.CopyAddress -> actions.onCopyAddressClicked(action.address) is ContactActionUiModel.CopyName -> actions.onCopyNameClicked(action.name) is ContactActionUiModel.NewMessage -> actions.onNewMessageClicked(action.participant) - is ContactActionUiModel.BlockContact -> actions.onBlockClicked(action.participant, sheetOrigin.getMessageId()) + is ContactActionUiModel.BlockContact -> actions.onBlockClicked( + action.participant, + sheetOrigin.getMessageId(), action.contactId + ) + is ContactActionUiModel.UnblockContact -> actions.onUnblockClicked(action.participant, sheetOrigin.getMessageId()) - is ContactActionUiModel.BlockAddress -> actions.onBlockClicked(action.participant, sheetOrigin.getMessageId()) + is ContactActionUiModel.BlockAddress -> actions.onBlockClicked(action.participant, sheetOrigin.getMessageId(), null) is ContactActionUiModel.UnblockAddress -> actions.onUnblockClicked(action.participant, sheetOrigin.getMessageId()) } @@ -194,7 +199,7 @@ object ContactActionsBottomSheetContent { val onCopyNameClicked: (name: String) -> Unit, val onNewMessageClicked: (participant: Participant) -> Unit, val onAddContactClicked: (participant: Participant) -> Unit, - val onBlockClicked: (participant: Participant, messageId: MessageId?) -> Unit, + val onBlockClicked: (participant: Participant, messageId: MessageId?, contactId: ContactId?) -> Unit, val onUnblockClicked: (participant: Participant, messageId: MessageId?) -> Unit ) } @@ -228,7 +233,7 @@ fun ContactActionsBottomSheetContentPreview() { onCopyNameClicked = {}, onNewMessageClicked = {}, onAddContactClicked = {}, - onBlockClicked = { _, _ -> }, + onBlockClicked = { _, _, _ -> }, onUnblockClicked = { _, _ -> } ) ) diff --git a/mail-message/presentation/src/main/res/values/strings.xml b/mail-message/presentation/src/main/res/values/strings.xml index bce0ef565b..ef0221d242 100644 --- a/mail-message/presentation/src/main/res/values/strings.xml +++ b/mail-message/presentation/src/main/res/values/strings.xml @@ -16,7 +16,7 @@ ~ along with Proton Mail. If not, see . --> - + %d Selected %d Selected @@ -73,6 +73,9 @@ Save this recipient as a contact Block this contact Block this contact + Emails from %1$s will no longer be delivered and will be permanently deleted. You can manage blocked email addresses in the settings. + Block + Cancel Block this contact Block this contact Block this address diff --git a/mail-message/presentation/src/test/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducerTest.kt b/mail-message/presentation/src/test/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducerTest.kt index 19ee443c24..7cee4f609a 100644 --- a/mail-message/presentation/src/test/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducerTest.kt +++ b/mail-message/presentation/src/test/kotlin/ch/protonmail/android/mailmessage/presentation/reducer/ContactActionsBottomSheetReducerTest.kt @@ -20,11 +20,13 @@ package ch.protonmail.android.mailmessage.presentation.reducer import ch.protonmail.android.mailcommon.presentation.model.BottomSheetState import ch.protonmail.android.mailcommon.presentation.sample.ParticipantAvatarSample +import ch.protonmail.android.mailcontact.domain.model.ContactId import ch.protonmail.android.mailmessage.domain.model.MessageId import ch.protonmail.android.mailmessage.domain.model.Participant +import ch.protonmail.android.mailmessage.presentation.model.ContactActionUiModel import ch.protonmail.android.mailmessage.presentation.model.bottomsheet.ContactActionsBottomSheetState -import ch.protonmail.android.testdata.contact.ContactActionsGroupsSample import ch.protonmail.android.testdata.contact.ContactSample +import kotlinx.collections.immutable.toImmutableList import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -61,6 +63,7 @@ internal class ContactActionsBottomSheetReducerTest( private val sampleOrigin = ContactActionsBottomSheetState.Origin.MessageDetails(MessageId("msg-1")) + private val transitionsFromLoadingState = listOf( TestInput( currentState = BottomSheetState(ContactActionsBottomSheetState.Loading), @@ -76,7 +79,12 @@ internal class ContactActionsBottomSheetReducerTest( contentState = ContactActionsBottomSheetState.Data( participant = sampleParticipant, avatarUiModel = sampleAvatar, - actions = ContactActionsGroupsSample.defaultForContact(sampleParticipant, false), + actions = expectedGroupsFor( + participant = sampleParticipant, + contactId = sampleContact.id, + isSenderBlocked = false, + isPrimaryUserAddress = false + ), origin = sampleOrigin ) ) @@ -95,7 +103,12 @@ internal class ContactActionsBottomSheetReducerTest( contentState = ContactActionsBottomSheetState.Data( participant = sampleParticipant, avatarUiModel = sampleAvatar, - actions = ContactActionsGroupsSample.defaultForContact(sampleParticipant, true), + actions = expectedGroupsFor( + participant = sampleParticipant, + contactId = sampleContact.id, + isSenderBlocked = true, + isPrimaryUserAddress = false + ), origin = sampleOrigin ) ) @@ -114,7 +127,12 @@ internal class ContactActionsBottomSheetReducerTest( contentState = ContactActionsBottomSheetState.Data( participant = sampleParticipant, avatarUiModel = sampleAvatar, - actions = ContactActionsGroupsSample.defaultForNoContact(sampleParticipant, true), + actions = expectedGroupsFor( + participant = sampleParticipant, + contactId = null, + isSenderBlocked = true, + isPrimaryUserAddress = false + ), origin = sampleOrigin ) ) @@ -133,7 +151,12 @@ internal class ContactActionsBottomSheetReducerTest( contentState = ContactActionsBottomSheetState.Data( participant = sampleParticipant, avatarUiModel = sampleAvatar, - actions = ContactActionsGroupsSample.defaultForNoContact(sampleParticipant, false), + actions = expectedGroupsFor( + participant = sampleParticipant, + contactId = null, + isSenderBlocked = false, + isPrimaryUserAddress = false + ), origin = sampleOrigin ) ) @@ -152,8 +175,11 @@ internal class ContactActionsBottomSheetReducerTest( contentState = ContactActionsBottomSheetState.Data( participant = primaryUserParticipant, avatarUiModel = sampleAvatar, - actions = ContactActionsGroupsSample.defaultForNoContact( - primaryUserParticipant, isAddressBlocked = false, isPrimaryUserAddress = true + actions = expectedGroupsFor( + participant = primaryUserParticipant, + contactId = null, + isSenderBlocked = false, + isPrimaryUserAddress = true ), origin = sampleOrigin ) @@ -161,6 +187,44 @@ internal class ContactActionsBottomSheetReducerTest( ) ) + private fun expectedGroupsFor( + participant: Participant, + contactId: ContactId?, + isSenderBlocked: Boolean, + isPrimaryUserAddress: Boolean + ): ContactActionsBottomSheetState.ContactActionsGroups { + val first = listOf( + ContactActionUiModel.NewMessage(participant) + ).toImmutableList() + + val second = listOf( + ContactActionUiModel.CopyAddress(participant.address), + ContactActionUiModel.CopyName(participant.name) + ).toImmutableList() + + val third = if (isPrimaryUserAddress) { + emptyList() + } else { + listOf( + when { + contactId == null && !isSenderBlocked -> ContactActionUiModel.BlockAddress(participant) + contactId == null && isSenderBlocked -> ContactActionUiModel.UnblockAddress(participant) + contactId != null && !isSenderBlocked -> ContactActionUiModel.BlockContact( + participant, contactId + ) + + else -> ContactActionUiModel.UnblockContact(participant) + } + ) + }.toImmutableList() + + return ContactActionsBottomSheetState.ContactActionsGroups( + firstGroup = first, + secondGroup = second, + thirdGroup = third + ) + } + @JvmStatic @Parameterized.Parameters(name = "{0}") fun data() = transitionsFromLoadingState diff --git a/test/test-data/src/main/kotlin/ch/protonmail/android/testdata/contact/ContactActionsGroupsSample.kt b/test/test-data/src/main/kotlin/ch/protonmail/android/testdata/contact/ContactActionsGroupsSample.kt index f45da9dbb6..373caa8d31 100644 --- a/test/test-data/src/main/kotlin/ch/protonmail/android/testdata/contact/ContactActionsGroupsSample.kt +++ b/test/test-data/src/main/kotlin/ch/protonmail/android/testdata/contact/ContactActionsGroupsSample.kt @@ -45,7 +45,7 @@ object ContactActionsGroupsSample { if (isContactBlocked) { add(ContactActionUiModel.UnblockContact(participant)) } else { - add(ContactActionUiModel.BlockContact(participant)) + add(ContactActionUiModel.BlockContact(participant, ContactIdSample.Doe)) } }.toImmutableList()