Add copy address and message actions to RsvpWidget

ET-4189
This commit is contained in:
Stefanija Boshkovska
2025-08-08 13:02:31 +02:00
committed by MargeBot
parent 7aec1bcb31
commit fb818f5658
5 changed files with 137 additions and 26 deletions
@@ -272,9 +272,12 @@ private fun ColumnScope.ConversationDetailExpandedItem(
onRetry = { actions.onRetryRsvpEventLoading(uiModel.messageId) }
)
is RsvpWidgetUiModel.Shown -> RsvpWidget(
uiModel.messageRsvpWidgetUiModel.event,
{ actions.onOpenInProtonCalendar(uiModel.messageId) },
{ actions.onAnswerRsvpEvent(uiModel.messageId, it) }
uiModel = uiModel.messageRsvpWidgetUiModel.event,
actions = RsvpWidget.Actions(
onOpenInProtonCalendar = { actions.onOpenInProtonCalendar(uiModel.messageId) },
onAnswerRsvpEvent = { actions.onAnswerRsvpEvent(uiModel.messageId, it) },
onMessage = actions.onMessage
)
)
}
@@ -390,7 +393,8 @@ object ConversationDetailItem {
val onUnblockSender: (MessageIdUiModel, String) -> Unit,
val onEditScheduleSendMessage: (MessageIdUiModel) -> Unit,
val onRetryRsvpEventLoading: (MessageIdUiModel) -> Unit,
val onAnswerRsvpEvent: (MessageIdUiModel, RsvpAnswer) -> Unit
val onAnswerRsvpEvent: (MessageIdUiModel, RsvpAnswer) -> Unit,
val onMessage: (String) -> Unit
)
val previewActions = Actions(
@@ -422,7 +426,8 @@ object ConversationDetailItem {
{ model: MessageIdUiModel, string: String -> },
{ model: MessageIdUiModel -> },
{},
{ _, _ -> }
{ _, _ -> },
{}
)
}
@@ -523,7 +523,8 @@ fun ConversationDetailScreen(
},
onAnswerRsvpEvent = { messageId, answer ->
viewModel.submit(ConversationDetailViewAction.AnswerRsvpEvent(MessageId(messageId.id), answer))
}
},
onMessage = actions.onComposeNewMessage
),
scrollToMessageId = state.scrollToMessage?.id
)
@@ -725,7 +726,8 @@ fun ConversationDetailScreen(
onUnblockSender = actions.onUnblockSender,
onEditScheduleSendMessage = actions.onEditScheduleSendMessage,
onRetryRsvpEventLoading = actions.onRetryRsvpEventLoading,
onAnswerRsvpEvent = actions.onAnswerRsvpEvent
onAnswerRsvpEvent = actions.onAnswerRsvpEvent,
onMessage = actions.onMessage
)
MessagesContentWithHiddenEdges(
uiModels = state.messagesState.messages,
@@ -1092,7 +1094,8 @@ object ConversationDetailScreen {
val onEditScheduleSendMessage: (MessageIdUiModel) -> Unit,
val onExitWithOpenInComposer: (MessageIdUiModel) -> Unit,
val onRetryRsvpEventLoading: (MessageIdUiModel) -> Unit,
val onAnswerRsvpEvent: (MessageIdUiModel, RsvpAnswer) -> Unit
val onAnswerRsvpEvent: (MessageIdUiModel, RsvpAnswer) -> Unit,
val onMessage: (String) -> Unit
) {
companion object {
@@ -1145,7 +1148,8 @@ object ConversationDetailScreen {
onEditScheduleSendMessage = {},
onExitWithOpenInComposer = {},
onRetryRsvpEventLoading = {},
onAnswerRsvpEvent = { _, _ -> }
onAnswerRsvpEvent = { _, _ -> },
onMessage = {}
)
}
}
@@ -47,6 +47,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
@@ -62,6 +63,7 @@ import ch.protonmail.android.design.compose.theme.labelMediumNorm
import ch.protonmail.android.design.compose.theme.titleLargeNorm
import ch.protonmail.android.mailcommon.presentation.NO_CONTENT_DESCRIPTION
import ch.protonmail.android.mailcommon.presentation.compose.MailDimens
import ch.protonmail.android.mailcommon.presentation.extension.copyTextToClipboard
import ch.protonmail.android.mailcommon.presentation.model.TextUiModel
import ch.protonmail.android.mailcommon.presentation.model.string
import ch.protonmail.android.maildetail.presentation.R
@@ -76,10 +78,11 @@ import ch.protonmail.android.mailmessage.domain.model.RsvpAnswer
@Composable
fun RsvpWidget(
uiModel: RsvpEventUiModel,
onOpenInProtonCalendar: () -> Unit,
onAnswerRsvpEvent: (RsvpAnswer) -> Unit,
actions: RsvpWidget.Actions,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
Column(
modifier = modifier
.padding(horizontal = ProtonDimens.Spacing.Large)
@@ -105,10 +108,10 @@ fun RsvpWidget(
title = uiModel.title,
dateTime = uiModel.dateTime,
isAttendanceOptional = uiModel.isAttendanceOptional,
onOpenInProtonCalendar = onOpenInProtonCalendar
onOpenInProtonCalendar = actions.onOpenInProtonCalendar
)
RsvpResponse(uiModel.buttons, onAnswerRsvpEvent)
RsvpResponse(uiModel.buttons, actions.onAnswerRsvpEvent)
Spacer(modifier = Modifier.size(ProtonDimens.Spacing.Large))
uiModel.calendar?.let {
@@ -134,12 +137,15 @@ fun RsvpWidget(
}
val organizerName = (uiModel.organizer.name ?: uiModel.organizer.email).string()
RsvpDetailsRow(
val organizerEmail = uiModel.organizer.email.string()
RsvpParticipantRow(
icon = R.drawable.ic_proton_user,
text = "$organizerName ${stringResource(id = R.string.rsvp_widget_organizer)}"
text = "$organizerName ${stringResource(id = R.string.rsvp_widget_organizer)}",
onCopyAddress = { context.copyTextToClipboard("Email", organizerEmail) },
onMessage = { actions.onMessage(organizerEmail) }
)
RsvpAttendees(uiModel.attendees)
RsvpAttendees(uiModel.attendees, actions.onMessage)
}
}
}
@@ -446,9 +452,81 @@ private fun RsvpDetailsRow(
}
@Composable
private fun RsvpAttendees(attendees: List<RsvpAttendeeUiModel>) {
private fun RsvpParticipantRow(
@DrawableRes icon: Int,
text: String,
onCopyAddress: () -> Unit,
onMessage: () -> Unit,
modifier: Modifier = Modifier,
iconTint: Color = ProtonTheme.colors.iconWeak
) {
Row {
val expanded = remember { mutableStateOf(false) }
RsvpDetailsRow(
modifier = modifier
.clip(ProtonTheme.shapes.mediumLarge)
.clickable(
onClick = { expanded.value = !expanded.value }
),
icon = icon,
text = text,
iconTint = iconTint
)
DropdownMenu(
expanded = expanded.value,
onDismissRequest = { expanded.value = false },
containerColor = ProtonTheme.colors.backgroundNorm
) {
DropdownMenuItem(
text = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.ic_proton_squares),
contentDescription = NO_CONTENT_DESCRIPTION
)
Spacer(modifier = Modifier.size(ProtonDimens.Spacing.Standard))
Text(
text = stringResource(id = R.string.rsvp_widget_copy_address),
style = ProtonTheme.typography.bodyLargeNorm
)
}
},
onClick = {
expanded.value = false
onCopyAddress()
}
)
DropdownMenuItem(
text = {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.ic_proton_pen_square),
contentDescription = NO_CONTENT_DESCRIPTION
)
Spacer(modifier = Modifier.size(ProtonDimens.Spacing.Standard))
Text(
text = stringResource(id = R.string.rsvp_widget_message),
style = ProtonTheme.typography.bodyLargeNorm
)
}
},
onClick = {
expanded.value = false
onMessage()
}
)
}
}
}
@Composable
private fun RsvpAttendees(attendees: List<RsvpAttendeeUiModel>, onMessage: (String) -> Unit) {
if (attendees.isEmpty()) return
val context = LocalContext.current
if (attendees.size == 1) {
val attendee = attendees.first()
val text = if (attendee.name == null) {
@@ -457,10 +535,13 @@ private fun RsvpAttendees(attendees: List<RsvpAttendeeUiModel>) {
"${attendee.name.string()}${attendee.email.string()}"
}
RsvpDetailsRow(
val attendeeEmail = attendee.email.string()
RsvpParticipantRow(
icon = attendee.answer.getIcon(isOnlyAttendee = true),
text = text,
iconTint = attendee.answer.getIconTint(isOnlyAttendee = true)
iconTint = attendee.answer.getIconTint(isOnlyAttendee = true),
onCopyAddress = { context.copyTextToClipboard("Email", attendeeEmail) },
onMessage = { onMessage(attendeeEmail) }
)
} else {
val isExpanded = remember { mutableStateOf(false) }
@@ -493,10 +574,13 @@ private fun RsvpAttendees(attendees: List<RsvpAttendeeUiModel>) {
"${attendee.name.string()}${attendee.email.string()}"
}
RsvpDetailsRow(
val attendeeEmail = attendee.email.string()
RsvpParticipantRow(
icon = attendee.answer.getIcon(isOnlyAttendee = false),
text = text,
iconTint = attendee.answer.getIconTint(isOnlyAttendee = false)
iconTint = attendee.answer.getIconTint(isOnlyAttendee = false),
onCopyAddress = { context.copyTextToClipboard("Email", attendeeEmail) },
onMessage = { onMessage(attendeeEmail) }
)
}
}
@@ -570,8 +654,11 @@ private fun RsvpStatusUiModel.getBackgroundColor() = when (this) {
fun RsvpWidgetUnansweredPreview() {
RsvpWidget(
uiModel = RsvpWidgetPreviewData.UnansweredWithMultipleParticipants,
onOpenInProtonCalendar = {},
onAnswerRsvpEvent = {}
actions = RsvpWidget.Actions(
onOpenInProtonCalendar = {},
onAnswerRsvpEvent = {},
onMessage = {}
)
)
}
@@ -580,7 +667,19 @@ fun RsvpWidgetUnansweredPreview() {
fun RsvpWidgetAnsweredPreview() {
RsvpWidget(
uiModel = RsvpWidgetPreviewData.AnsweredWithOneParticipantAndStatus,
onOpenInProtonCalendar = {},
onAnswerRsvpEvent = {}
actions = RsvpWidget.Actions(
onOpenInProtonCalendar = {},
onAnswerRsvpEvent = {},
onMessage = {}
)
)
}
object RsvpWidget {
data class Actions(
val onOpenInProtonCalendar: () -> Unit,
val onAnswerRsvpEvent: (RsvpAnswer) -> Unit,
val onMessage: (String) -> Unit
)
}
@@ -166,4 +166,6 @@
<string name="rsvp_widget_button_retry">Retry</string>
<string name="rsvp_widget_error_answering">An error occurred while answering this event</string>
<string name="rsvp_widget_open_in_proton_calendar">Open in Proton Calendar</string>
<string name="rsvp_widget_copy_address">Copy address</string>
<string name="rsvp_widget_message">Message</string>
</resources>
@@ -65,7 +65,8 @@ internal class MessageDetailFooterActionsTest {
onUnblockSender = { _, _ -> },
onEditScheduleSendMessage = {},
onRetryRsvpEventLoading = {},
onAnswerRsvpEvent = { _, _ -> }
onAnswerRsvpEvent = { _, _ -> },
onMessage = {}
)
// When