From 8579eb2518acc680df93d447ca81f0ab5a3e5164 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 20 Jun 2025 10:42:21 +0700 Subject: [PATCH] TF-3792 Hide buttons of blue bar in case of event not belong to this account and change Signed-off-by: dab246 --- core/lib/utils/preview_eml_file_utils.dart | 4 +- .../email_datasource_impl.dart | 2 +- .../email_local_storage_datasource_impl.dart | 2 +- .../list_event_actions_extension.dart | 15 - .../model/preview_email_eml_request.dart | 11 +- .../model/view_entire_message_request.dart | 7 +- .../controller/single_email_controller.dart | 31 +- .../email/presentation/email_view.dart | 11 +- .../extensions/calendar_event_extension.dart | 39 ++- ...dar_event_action_button_widget_styles.dart | 1 - ...endar_event_information_widget_styles.dart | 3 +- .../calendar_event_action_button_widget.dart | 24 +- .../calendar_event_information_widget.dart | 311 ++++++++---------- .../email_previewer_controller.dart | 2 +- lib/l10n/intl_messages.arb | 8 +- lib/main/localizations/app_localizations.dart | 7 + .../list_event_actions_extension_test.dart | 46 --- .../calendar_event_extension_test.dart | 249 ++++++++------ 18 files changed, 377 insertions(+), 396 deletions(-) delete mode 100644 lib/features/email/domain/extensions/list_event_actions_extension.dart delete mode 100644 test/features/email/domain/extensions/list_event_actions_extension_test.dart diff --git a/core/lib/utils/preview_eml_file_utils.dart b/core/lib/utils/preview_eml_file_utils.dart index b25dcc164..ce0192047 100644 --- a/core/lib/utils/preview_eml_file_utils.dart +++ b/core/lib/utils/preview_eml_file_utils.dart @@ -134,7 +134,7 @@ class PreviewEmlFileUtils { String generatePreviewEml({ required String appName, - required String userName, + required String ownEmailAddress, required String subjectPrefix, required String subject, required String emailContent, @@ -154,7 +154,7 @@ class PreviewEmlFileUtils { String? replyToAddress, List? listAttachment, }) { - Document document = parse(HtmlUtils.createTemplateHtmlDocument(title: '$subject - $userName')); + Document document = parse(HtmlUtils.createTemplateHtmlDocument(title: '$subject - $ownEmailAddress')); Element? emailElement = _createEmailElement( subjectPrefix: subjectPrefix, diff --git a/lib/features/email/data/datasource_impl/email_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_datasource_impl.dart index ce5e8ebed..cd5cef4b7 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -491,7 +491,7 @@ class EmailDataSourceImpl extends EmailDataSource { final previewEmlHtmlDocument = _previewEmailUtils.generatePreviewEml( appName: appLocalizations.app_name, - userName: previewEmailEMLRequest.userName.value, + ownEmailAddress: previewEmailEMLRequest.ownEmailAddress, subjectPrefix: appLocalizations.subject, subject: previewEmailEMLRequest.email.subject?.escapeLtGtHtmlString() ?? '', emailContent: emailContentEscaped, diff --git a/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart index 4f01fce44..5aa4e965d 100644 --- a/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart @@ -331,7 +331,7 @@ class EmailLocalStorageDataSourceImpl extends EmailDataSource { final htmlDocument = _previewEmlFileUtils.generatePreviewEml( appName: appLocalizations.app_name, - userName: entireMessageRequest.userName.value, + ownEmailAddress: entireMessageRequest.ownEmailAddress, subjectPrefix: appLocalizations.subject, subject: email.subject?.escapeLtGtHtmlString() ?? '', emailContent: emailContentEscaped, diff --git a/lib/features/email/domain/extensions/list_event_actions_extension.dart b/lib/features/email/domain/extensions/list_event_actions_extension.dart deleted file mode 100644 index 08ad29631..000000000 --- a/lib/features/email/domain/extensions/list_event_actions_extension.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_method.dart'; -import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; - -extension ListEventActionsExtension on List { - List validActionsOfEventMethod(EventMethod? eventMethod) { - return where((action) { - if (eventMethod != EventMethod.counter) { - return action != EventActionType.acceptCounter; - } - - return action == EventActionType.acceptCounter - || action == EventActionType.mailToAttendees; - }).toList(); - } -} \ No newline at end of file diff --git a/lib/features/email/domain/model/preview_email_eml_request.dart b/lib/features/email/domain/model/preview_email_eml_request.dart index 3830d1739..74e521194 100644 --- a/lib/features/email/domain/model/preview_email_eml_request.dart +++ b/lib/features/email/domain/model/preview_email_eml_request.dart @@ -2,13 +2,12 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; -import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class PreviewEmailEMLRequest with EquatableMixin { final AccountId accountId; - final UserName userName; + final String ownEmailAddress; final Id blobId; final Email email; final Locale locale; @@ -18,7 +17,7 @@ class PreviewEmailEMLRequest with EquatableMixin { PreviewEmailEMLRequest({ required this.accountId, - required this.userName, + required this.ownEmailAddress, required this.blobId, required this.email, required this.locale, @@ -29,12 +28,14 @@ class PreviewEmailEMLRequest with EquatableMixin { String get keyStored => blobId.value; - String get title => '${email.subject?.trim().isNotEmpty == true ? email.subject : appLocalizations.app_name} - ${userName.value}'; + String get title => '${email.subject?.trim().isNotEmpty == true + ? email.subject + : appLocalizations.app_name} - $ownEmailAddress'; @override List get props => [ accountId, - userName, + ownEmailAddress, blobId, email, locale, diff --git a/lib/features/email/domain/model/view_entire_message_request.dart b/lib/features/email/domain/model/view_entire_message_request.dart index 956b7aa3c..a5b21eb75 100644 --- a/lib/features/email/domain/model/view_entire_message_request.dart +++ b/lib/features/email/domain/model/view_entire_message_request.dart @@ -1,12 +1,11 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:model/email/attachment.dart'; import 'package:model/email/presentation_email.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class ViewEntireMessageRequest with EquatableMixin { - final UserName userName; + final String ownEmailAddress; final PresentationEmail presentationEmail; final List attachments; final String emailContent; @@ -14,7 +13,7 @@ class ViewEntireMessageRequest with EquatableMixin { final AppLocalizations appLocalizations; ViewEntireMessageRequest({ - required this.userName, + required this.ownEmailAddress, required this.presentationEmail, required this.attachments, required this.emailContent, @@ -24,7 +23,7 @@ class ViewEntireMessageRequest with EquatableMixin { @override List get props => [ - userName, + ownEmailAddress, presentationEmail, attachments, emailContent, diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 6b6afd917..693985258 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -23,7 +23,6 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; -import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/identities/identity.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/attendance/calendar_event_attendance.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; @@ -45,7 +44,6 @@ import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/base/state/button_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; -import 'package:tmail_ui_user/features/email/domain/exceptions/calendar_event_exceptions.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; @@ -222,7 +220,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { Session? get session => mailboxDashBoardController.sessionCurrent; - UserName? get userName => session?.username; + String get ownEmailAddress => mailboxDashBoardController.getOwnEmailAddress(); SingleEmailController( this._getEmailContentInteractor, @@ -776,6 +774,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { emailUnsubscribe.value = null; _identitySelected = null; isEmailContentClipped.value = false; + attendanceStatus.value = null; if (isEmailClosing) { emailLoadedViewState.value = Right(UIState.idle); viewState.value = Right(UIState.idle); @@ -2195,26 +2194,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ); } - void _calendarEventFailure(Failure failure) { - if (currentOverlayContext == null || currentContext == null) { - return; - } - - if (failure is CalendarEventReplyFailure && failure.exception is CannotReplyCalendarEventException) { - final replyEventException = failure.exception as CannotReplyCalendarEventException; - - if (replyEventException.mapErrors?.isNotEmpty == true) { - appToast.showToastErrorMessage( - currentOverlayContext!, - replyEventException.mapErrors!.values.first.description - ?? AppLocalizations.of(currentContext!).eventReplyWasSentUnsuccessfully); - return; - } - } - - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).eventReplyWasSentUnsuccessfully); + void _calendarEventFailure(CalendarEventReplyFailure failure) { + toastManager.showMessageFailure(failure); } void _downloadMessageAsEML(PresentationEmail presentationEmail) { @@ -2408,7 +2389,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { consumeState(_previewEmailFromEmlFileInteractor.execute( PreviewEmailEMLRequest( accountId: accountId!, - userName: userName!, + ownEmailAddress: ownEmailAddress, blobId: success.blobId, email: success.email, locale: Localizations.localeOf(currentContext!), @@ -2589,7 +2570,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ); final viewEntireMessageRequest = ViewEntireMessageRequest( - userName: userName ?? UserName(''), + ownEmailAddress: ownEmailAddress, presentationEmail: presentationEmail, attachments: List.from(attachments), emailContent: emailContent, diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index d9bcf3abc..75b5ae626 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -367,13 +367,22 @@ class EmailView extends GetWidget { onOpenComposerAction: controller.openNewComposerAction, onOpenNewTabAction: controller.openNewTabAction, onCalendarEventReplyActionClick: (eventActionType) => - controller.onCalendarEventReplyAction(eventActionType, presentationEmail.id!), + controller.onCalendarEventReplyAction( + eventActionType, + presentationEmail.id!, + ), calendarEventReplying: controller.calendarEventProcessing, attendanceStatus: controller.attendanceStatus.value, + ownEmailAddress: controller + .mailboxDashBoardController + .getOwnEmailAddress(), onMailtoAttendeesAction: controller.handleMailToAttendees, openEmailAddressDetailAction: controller.openEmailAddressDialog, isFree: controller.isCalendarEventFree, listEmailAddressSender: emailAddressSender ?? [], + isPortraitMobile: controller + .responsiveUtils + .isPortraitMobile(context), )), if (_validateDisplayEventActionBanner( context: context, diff --git a/lib/features/email/presentation/extensions/calendar_event_extension.dart b/lib/features/email/presentation/extensions/calendar_event_extension.dart index c058436af..a45fd3041 100644 --- a/lib/features/email/presentation/extensions/calendar_event_extension.dart +++ b/lib/features/email/presentation/extensions/calendar_event_extension.dart @@ -20,6 +20,7 @@ import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_sequence import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_id.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_method.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/recurrence_rule/recurrence_rule.dart'; +import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; @@ -382,16 +383,50 @@ extension CalendarEventExtension on CalendarEvent { return []; } - bool get isDisplayedEventReplyAction => method != null + bool isDisplayedEventReplyAction(String ownerEmailAddress) => method != null && _methodIsRepliable && organizer != null - && participants?.isNotEmpty == true; + && participants?.isNotEmpty == true + && !validateUserIsNotListedInParticipants(ownerEmailAddress); bool get _methodIsRepliable => method == EventMethod.request || method == EventMethod.add || method == EventMethod.counter; + bool validateUserIsNotListedInParticipants(String ownEmailAddress) { + final participant = participants?.firstWhereOrNull((participant) => + participant.mailto?.mailAddress.value == ownEmailAddress); + return participant == null; + } + + bool get isDisplayedMailToAttendees => + organizer != null || participants?.isNotEmpty == true; + + List getEventActionTypesIsDisplayed( + String ownerEmailAddress, + ) { + if (isDisplayedEventReplyAction(ownerEmailAddress)) { + return [ + if (method == EventMethod.counter) + EventActionType.acceptCounter + else + ...[ + EventActionType.yes, + EventActionType.maybe, + EventActionType.no, + ], + EventActionType.mailToAttendees, + ]; + } else if (isDisplayedMailToAttendees) { + return [ + EventActionType.mailToAttendees, + ]; + } else { + return []; + } + } + CalendarEvent copyWith({ EventId? eventId, String? title, diff --git a/lib/features/email/presentation/styles/calendar_event_action_button_widget_styles.dart b/lib/features/email/presentation/styles/calendar_event_action_button_widget_styles.dart index 53c1eac76..9fc4bff71 100644 --- a/lib/features/email/presentation/styles/calendar_event_action_button_widget_styles.dart +++ b/lib/features/email/presentation/styles/calendar_event_action_button_widget_styles.dart @@ -20,5 +20,4 @@ class CalendarEventActionButtonWidgetStyles { static const EdgeInsetsGeometry buttonPadding = EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 12); static const EdgeInsetsGeometry paddingMobile = EdgeInsetsDirectional.only(top: 16); static const EdgeInsetsGeometry paddingWeb = EdgeInsetsDirectional.only(start: 100, end: 16, top: 16); - static const EdgeInsetsGeometry margin = EdgeInsetsDirectional.only(top: 16); } \ No newline at end of file diff --git a/lib/features/email/presentation/styles/calendar_event_information_widget_styles.dart b/lib/features/email/presentation/styles/calendar_event_information_widget_styles.dart index 507a7b643..03415d288 100644 --- a/lib/features/email/presentation/styles/calendar_event_information_widget_styles.dart +++ b/lib/features/email/presentation/styles/calendar_event_information_widget_styles.dart @@ -3,7 +3,6 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:flutter/material.dart'; class CalendarEventInformationWidgetStyles { - static const double borderRadius = 16; static const double contentPadding = 16; static const double verticalMargin = 12; static const double horizontalMargin = 16; @@ -15,4 +14,6 @@ class CalendarEventInformationWidgetStyles { static const double space = 8; static const Color titleColor = Colors.black; static const Color invitationMessageColor = Colors.black; + + static const radius = Radius.circular(16); } \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/calendar_event/calendar_event_action_button_widget.dart b/lib/features/email/presentation/widgets/calendar_event/calendar_event_action_button_widget.dart index e2a6a1a54..8dbc4c61e 100644 --- a/lib/features/email/presentation/widgets/calendar_event/calendar_event_action_button_widget.dart +++ b/lib/features/email/presentation/widgets/calendar_event/calendar_event_action_button_widget.dart @@ -1,10 +1,6 @@ -import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/attendance/calendar_event_attendance.dart'; -import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_method.dart'; -import 'package:tmail_ui_user/features/email/domain/extensions/list_event_actions_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; import 'package:tmail_ui_user/features/email/presentation/styles/calendar_event_action_button_widget_styles.dart'; @@ -12,22 +8,20 @@ typedef OnCalendarEventReplyActionClick = void Function(EventActionType eventAct class CalendarEventActionButtonWidget extends StatelessWidget { - final EdgeInsetsGeometry? margin; + final List eventActions; final OnCalendarEventReplyActionClick onCalendarEventReplyActionClick; final bool calendarEventReplying; - final EventMethod? eventMethod; final AttendanceStatus? attendanceStatus; + final bool isPortraitMobile; final VoidCallback? onMailToAttendeesAction; - final _responsiveUtils = Get.find(); - - CalendarEventActionButtonWidget({ + const CalendarEventActionButtonWidget({ super.key, + required this.eventActions, required this.onCalendarEventReplyActionClick, required this.calendarEventReplying, - required this.eventMethod, - this.margin, this.attendanceStatus, + this.isPortraitMobile = false, this.onMailToAttendeesAction, }); @@ -35,15 +29,13 @@ class CalendarEventActionButtonWidget extends StatelessWidget { Widget build(BuildContext context) { return Container( width: double.infinity, - margin: margin ?? CalendarEventActionButtonWidgetStyles.margin, - padding: _responsiveUtils.isPortraitMobile(context) + padding: isPortraitMobile ? CalendarEventActionButtonWidgetStyles.paddingMobile : CalendarEventActionButtonWidgetStyles.paddingWeb, child: Wrap( spacing: CalendarEventActionButtonWidgetStyles.space, runSpacing: CalendarEventActionButtonWidgetStyles.space, - children: EventActionType.values - .validActionsOfEventMethod(eventMethod) + children: eventActions .map((action) => AbsorbPointer( absorbing: _getCallbackFunction(action) == null, child: TMailButtonWidget( @@ -58,7 +50,7 @@ class CalendarEventActionButtonWidget extends StatelessWidget { ), textAlign: TextAlign.center, minWidth: CalendarEventActionButtonWidgetStyles.minWidth, - width: _responsiveUtils.isPortraitMobile(context) ? double.infinity : null, + width: isPortraitMobile ? double.infinity : null, border: Border.all( width: CalendarEventActionButtonWidgetStyles.borderWidth, color: _getButtonBorderColor(action) diff --git a/lib/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart b/lib/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart index cebd3617a..86d4ccfbb 100644 --- a/lib/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart +++ b/lib/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart @@ -1,7 +1,5 @@ import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/attendance/calendar_event_attendance.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart'; @@ -16,6 +14,7 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/event_time_information_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/event_title_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_sender_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; typedef OnOpenNewTabAction = void Function(String link); @@ -33,25 +32,159 @@ class CalendarEventInformationWidget extends StatelessWidget { final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction; final bool isFree; final List listEmailAddressSender; + final String ownEmailAddress; + final bool isPortraitMobile; - final _responsiveUtils = Get.find(); - - CalendarEventInformationWidget({ + const CalendarEventInformationWidget({ super.key, required this.calendarEvent, required this.onCalendarEventReplyActionClick, required this.calendarEventReplying, required this.isFree, + required this.ownEmailAddress, this.onOpenNewTabAction, this.onOpenComposerAction, this.attendanceStatus, this.onMailtoAttendeesAction, this.openEmailAddressDetailAction, this.listEmailAddressSender = const [], + this.isPortraitMobile = false, }); @override Widget build(BuildContext context) { + final bodyBorderRadius = isPortraitMobile + ? const BorderRadiusDirectional.only( + bottomStart: CalendarEventInformationWidgetStyles.radius, + bottomEnd: CalendarEventInformationWidgetStyles.radius, + ) + : const BorderRadiusDirectional.only( + topEnd: CalendarEventInformationWidgetStyles.radius, + bottomEnd: CalendarEventInformationWidgetStyles.radius, + ); + + final eventActionTypes = calendarEvent.getEventActionTypesIsDisplayed( + ownEmailAddress, + ); + + final bodyWidget = Container( + decoration: ShapeDecoration( + shape: RoundedRectangleBorder(borderRadius: bodyBorderRadius), + color: Colors.white, + ), + clipBehavior: Clip.antiAlias, + padding: const EdgeInsets.all( + CalendarEventInformationWidgetStyles.calendarInformationMargin, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (calendarEvent.organizerName.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + bottom: CalendarEventInformationWidgetStyles.space, + ), + child: Text.rich( + TextSpan( + style: const TextStyle( + fontSize: CalendarEventInformationWidgetStyles + .invitationMessageTextSize, + fontWeight: FontWeight.w500, + color: CalendarEventInformationWidgetStyles + .invitationMessageColor, + ), + children: [ + TextSpan( + text: calendarEvent.organizerName, + style: const TextStyle( + color: CalendarEventInformationWidgetStyles + .invitationMessageColor, + fontSize: CalendarEventInformationWidgetStyles + .invitationMessageTextSize, + fontWeight: FontWeight.w700, + ), + ), + TextSpan( + text: calendarEvent.getTitleEventAction( + context, + listEmailAddressSender, + ), + ) + ], + ), + ), + ), + if (calendarEvent.title?.isNotEmpty == true) + EventTitleWidget(title: calendarEvent.title!), + _buildEventTimeInformationWidget(), + if (calendarEvent.location?.isNotEmpty == true) + Padding( + padding: const EdgeInsets.only( + top: CalendarEventInformationWidgetStyles.fieldTopPadding, + ), + child: EventLocationInformationWidget( + locationEvent: calendarEvent.location!, + onOpenComposerAction: onOpenComposerAction, + onOpenNewTabAction: onOpenNewTabAction, + ), + ), + if (calendarEvent.videoConferences.isNotEmpty) + Padding( + padding: const EdgeInsets.only( + top: CalendarEventDetailWidgetStyles.fieldTopPadding, + ), + child: EventLinkDetailWidget( + listHyperLink: calendarEvent.videoConferences, + ), + ), + if (calendarEvent.participants?.isNotEmpty == true || + calendarEvent.organizer != null) + Padding( + padding: const EdgeInsets.only( + top: CalendarEventDetailWidgetStyles.fieldTopPadding, + ), + child: EventAttendeeDetailWidget( + attendees: calendarEvent.participants ?? [], + organizer: calendarEvent.organizer, + openEmailAddressDetailAction: openEmailAddressDetailAction, + ), + ), + if (calendarEvent.validateUserIsNotListedInParticipants( + ownEmailAddress, + )) + Padding( + padding: EdgeInsetsDirectional.only( + top: CalendarEventInformationWidgetStyles.fieldTopPadding, + start: isPortraitMobile ? 0 : 100, + ), + child: Text( + AppLocalizations + .of(context) + .youAreNotInvitedToThisEventPleaseContactTheOrganiser, + style: const TextStyle( + fontSize: CalendarEventInformationWidgetStyles + .invitationMessageTextSize, + fontWeight: FontWeight.w500, + color: AppColor.colorMaybeEventActionText, + ), + ), + ), + if (eventActionTypes.isNotEmpty) + CalendarEventActionButtonWidget( + eventActions: eventActionTypes, + onCalendarEventReplyActionClick: onCalendarEventReplyActionClick, + calendarEventReplying: calendarEventReplying, + attendanceStatus: attendanceStatus, + isPortraitMobile: isPortraitMobile, + onMailToAttendeesAction: () => onMailtoAttendeesAction?.call( + calendarEvent.organizer, + calendarEvent.participants, + ), + ), + ], + ), + ); + return Container( clipBehavior: Clip.antiAlias, decoration: const ShapeDecoration( @@ -61,99 +194,22 @@ class CalendarEventInformationWidget extends StatelessWidget { width: 0.5, color: AppColor.colorCalendarEventInformationStroke, ), - borderRadius: BorderRadius.all(Radius.circular(CalendarEventInformationWidgetStyles.borderRadius)), + borderRadius: BorderRadius.all( + CalendarEventInformationWidgetStyles.radius, + ), ), ), margin: const EdgeInsetsDirectional.symmetric( vertical: CalendarEventInformationWidgetStyles.verticalMargin, horizontal: CalendarEventInformationWidgetStyles.horizontalMargin), - child: _responsiveUtils.isPortraitMobile(context) + child: isPortraitMobile ? Column( children: [ CalendarDateIconWidget( calendarEvent: calendarEvent, width: double.infinity, ), - Container( - decoration: const ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(CalendarEventInformationWidgetStyles.borderRadius), - bottomRight: Radius.circular(CalendarEventInformationWidgetStyles.borderRadius) - ) - ), - color: Colors.white - ), - clipBehavior: Clip.antiAlias, - padding: const EdgeInsets.all(CalendarEventInformationWidgetStyles.calendarInformationMargin), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (calendarEvent.organizerName.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: CalendarEventInformationWidgetStyles.space), - child: Text.rich( - TextSpan( - style: const TextStyle( - fontSize: CalendarEventInformationWidgetStyles.invitationMessageTextSize, - fontWeight: FontWeight.w500, - color: CalendarEventInformationWidgetStyles.invitationMessageColor - ), - children: [ - TextSpan( - text: calendarEvent.organizerName, - style: const TextStyle( - color: CalendarEventInformationWidgetStyles.invitationMessageColor, - fontSize: CalendarEventInformationWidgetStyles.invitationMessageTextSize, - fontWeight: FontWeight.w700 - ), - ), - TextSpan(text: calendarEvent.getTitleEventAction(context, listEmailAddressSender)) - ] - ) - ), - ), - if (calendarEvent.title?.isNotEmpty == true) - EventTitleWidget(title: calendarEvent.title!), - _buildEventTimeInformationWidget(), - if (calendarEvent.location?.isNotEmpty == true) - Padding( - padding: const EdgeInsets.only(top: CalendarEventInformationWidgetStyles.fieldTopPadding), - child: EventLocationInformationWidget( - locationEvent: calendarEvent.location!, - onOpenComposerAction: onOpenComposerAction, - onOpenNewTabAction: onOpenNewTabAction, - ), - ), - if (calendarEvent.videoConferences.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: CalendarEventDetailWidgetStyles.fieldTopPadding), - child: EventLinkDetailWidget(listHyperLink: calendarEvent.videoConferences), - ), - if (calendarEvent.participants?.isNotEmpty == true || calendarEvent.organizer != null) - Padding( - padding: const EdgeInsets.only(top: CalendarEventDetailWidgetStyles.fieldTopPadding), - child: EventAttendeeDetailWidget( - attendees: calendarEvent.participants ?? [], - organizer: calendarEvent.organizer, - openEmailAddressDetailAction: openEmailAddressDetailAction, - ), - ), - if (calendarEvent.isDisplayedEventReplyAction) - CalendarEventActionButtonWidget( - margin: EdgeInsetsDirectional.zero, - onCalendarEventReplyActionClick: onCalendarEventReplyActionClick, - eventMethod: calendarEvent.method, - calendarEventReplying: calendarEventReplying, - attendanceStatus: attendanceStatus, - onMailToAttendeesAction: () => onMailtoAttendeesAction?.call( - calendarEvent.organizer, - calendarEvent.participants, - ), - ), - ], - ), - ) + bodyWidget, ], ) : IntrinsicHeight( @@ -161,86 +217,7 @@ class CalendarEventInformationWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ CalendarDateIconWidget(calendarEvent: calendarEvent), - Expanded(child: Container( - clipBehavior: Clip.antiAlias, - decoration: const ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topRight: Radius.circular(CalendarEventInformationWidgetStyles.borderRadius), - bottomRight: Radius.circular(CalendarEventInformationWidgetStyles.borderRadius) - ) - ), - color: Colors.white - ), - padding: const EdgeInsets.all(CalendarEventInformationWidgetStyles.calendarInformationMargin), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (calendarEvent.organizerName.isNotEmpty) - Padding( - padding: const EdgeInsets.only(bottom: CalendarEventInformationWidgetStyles.space), - child: Text.rich( - TextSpan( - style: const TextStyle( - fontSize: CalendarEventInformationWidgetStyles.invitationMessageTextSize, - fontWeight: FontWeight.w500, - color: CalendarEventInformationWidgetStyles.invitationMessageColor - ), - children: [ - TextSpan( - text: calendarEvent.organizerName, - style: const TextStyle( - color: CalendarEventInformationWidgetStyles.invitationMessageColor, - fontSize: CalendarEventInformationWidgetStyles.invitationMessageTextSize, - fontWeight: FontWeight.w700 - ), - ), - TextSpan(text: calendarEvent.getTitleEventAction(context, listEmailAddressSender)) - ] - ) - ), - ), - if (calendarEvent.title?.isNotEmpty == true) - EventTitleWidget(title: calendarEvent.title!), - _buildEventTimeInformationWidget(), - if (calendarEvent.location?.isNotEmpty == true) - Padding( - padding: const EdgeInsets.only(top: CalendarEventInformationWidgetStyles.fieldTopPadding), - child: EventLocationInformationWidget( - locationEvent: calendarEvent.location!, - onOpenComposerAction: onOpenComposerAction, - onOpenNewTabAction: onOpenNewTabAction, - ), - ), - if (calendarEvent.videoConferences.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: CalendarEventDetailWidgetStyles.fieldTopPadding), - child: EventLinkDetailWidget(listHyperLink: calendarEvent.videoConferences), - ), - if (calendarEvent.participants?.isNotEmpty == true || calendarEvent.organizer != null) - Padding( - padding: const EdgeInsets.only(top: CalendarEventDetailWidgetStyles.fieldTopPadding), - child: EventAttendeeDetailWidget( - attendees: calendarEvent.participants ?? [], - organizer: calendarEvent.organizer, - openEmailAddressDetailAction: openEmailAddressDetailAction, - ), - ), - if (calendarEvent.isDisplayedEventReplyAction) - CalendarEventActionButtonWidget( - margin: EdgeInsetsDirectional.zero, - onCalendarEventReplyActionClick: onCalendarEventReplyActionClick, - eventMethod: calendarEvent.method, - calendarEventReplying: calendarEventReplying, - attendanceStatus: attendanceStatus, - onMailToAttendeesAction: () => onMailtoAttendeesAction?.call( - calendarEvent.organizer, - calendarEvent.participants, - ), - ), - ], - ), - )) + Expanded(child: bodyWidget), ], ), ), diff --git a/lib/features/email_previewer/email_previewer_controller.dart b/lib/features/email_previewer/email_previewer_controller.dart index 863cce09a..ffd9b2750 100644 --- a/lib/features/email_previewer/email_previewer_controller.dart +++ b/lib/features/email_previewer/email_previewer_controller.dart @@ -271,7 +271,7 @@ class EmailPreviewerController extends ReloadableController { consumeState(_previewEmailFromEmlFileInteractor.execute( PreviewEmailEMLRequest( accountId: _accountId!, - userName: _session!.username, + ownEmailAddress: _session!.getOwnEmailAddressOrEmpty(), blobId: success.blobId, email: success.email, locale: Localizations.localeOf(currentContext!), diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index bd1007560..3f364600d 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2025-06-02T18:48:11.159281", + "@@last_modified": "2025-06-19T11:06:29.579174", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -4551,5 +4551,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "youAreNotInvitedToThisEventPleaseContactTheOrganiser": "You are not invited to this event. Please contact the organiser.", + "@youAreNotInvitedToThisEventPleaseContactTheOrganiser": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 80da4b019..a55a12ce2 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4794,4 +4794,11 @@ class AppLocalizations { name: 'createFilterRuleFailed', ); } + + String get youAreNotInvitedToThisEventPleaseContactTheOrganiser { + return Intl.message( + 'You are not invited to this event. Please contact the organiser.', + name: 'youAreNotInvitedToThisEventPleaseContactTheOrganiser', + ); + } } diff --git a/test/features/email/domain/extensions/list_event_actions_extension_test.dart b/test/features/email/domain/extensions/list_event_actions_extension_test.dart deleted file mode 100644 index 08694d9b4..000000000 --- a/test/features/email/domain/extensions/list_event_actions_extension_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_method.dart'; -import 'package:tmail_ui_user/features/email/domain/extensions/list_event_actions_extension.dart'; -import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; - -void main() { - group('list event actions extension test:', () { - group('validActionsOfEventMethod test:', () { - test( - 'should return all but acceptCounter ' - 'when event method is not counter', - () { - // arrange - const method = EventMethod.add; - - // act - final result = EventActionType.values.validActionsOfEventMethod(method); - - // assert - expect(result, equals([ - EventActionType.yes, - EventActionType.maybe, - EventActionType.no, - EventActionType.mailToAttendees, - ])); - }); - - test( - 'should only return acceptCounter and mailToAttendees ' - 'when event method is counter', - () { - // arrange - const method = EventMethod.counter; - - // act - final result = EventActionType.values.validActionsOfEventMethod(method); - - // assert - expect(result, equals([ - EventActionType.acceptCounter, - EventActionType.mailToAttendees, - ])); - }); - }); - }); -} \ No newline at end of file diff --git a/test/features/email/presentation/extensions/calendar_event_extension_test.dart b/test/features/email/presentation/extensions/calendar_event_extension_test.dart index 96e968e7e..323ed2f8a 100644 --- a/test/features/email/presentation/extensions/calendar_event_extension_test.dart +++ b/test/features/email/presentation/extensions/calendar_event_extension_test.dart @@ -4,11 +4,22 @@ import 'package:date_format/date_format.dart' as date_format; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/attendee/calendar_attendee.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/attendee/calendar_attendee_mail_to.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_organizer.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_method.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/mail_address.dart'; +import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart'; void main() { + const ownerEmail = 'user@example.com'; + final matchingParticipant = CalendarAttendee( + mailto: CalendarAttendeeMailTo(MailAddress(ownerEmail)), + ); + final nonMatchingParticipant = CalendarAttendee( + mailto: CalendarAttendeeMailTo(MailAddress('someone@else.com')), + ); + group('calendar_event_extension::formatDateTime::test', () { final dateTime = DateTime(2021, 10, 10, 10, 30, 00, 00, 00); @@ -77,121 +88,145 @@ void main() { }); }); - group('calendar_event_extension::isDisplayedEventReplyAction::test:', () { - CalendarEvent calendarEventFromEventMethod(EventMethod eventMethod) { - return CalendarEvent( - method: eventMethod, - organizer: CalendarOrganizer(), - participants: [CalendarAttendee()] + group('isDisplayedEventReplyAction', () { + test('returns true when all conditions are met and user is in participants', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [matchingParticipant], ); - } - group('should return true', () { - test( - 'when event method is request', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.request); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, true); - }); - - test( - 'when event method is add', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.add); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, true); - }); - - test( - 'when event method is counter', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.counter); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, true); - }); + expect(event.isDisplayedEventReplyAction(ownerEmail), isTrue); }); - group('should return false', () { - test( - 'when event method is publish', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.publish); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, false); - }); + test('returns false when method is null', () { + final event = CalendarEvent( + method: null, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [matchingParticipant], + ); - test( - 'when event method is reply', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.reply); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, false); - }); + expect(event.isDisplayedEventReplyAction(ownerEmail), isFalse); + }); - test( - 'when event method is cancel', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.cancel); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, false); - }); + test('returns false when method is not repliable', () { + final event = CalendarEvent( + method: EventMethod.cancel, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [matchingParticipant], + ); - test( - 'when event method is refresh', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.refresh); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, false); - }); + expect(event.isDisplayedEventReplyAction(ownerEmail), isFalse); + }); - test( - 'when event method is declineCounter', - () { - // arrange - final calendarEvent = calendarEventFromEventMethod(EventMethod.declineCounter); - - // act - final result = calendarEvent.isDisplayedEventReplyAction; - - // assert - expect(result, false); - }); + test('returns false when organizer is null', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: null, + participants: [matchingParticipant], + ); + + expect(event.isDisplayedEventReplyAction(ownerEmail), isFalse); + }); + + test('returns false when participants is empty', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [], + ); + + expect(event.isDisplayedEventReplyAction(ownerEmail), isFalse); + }); + + test('returns false when participants is null', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: null, + ); + + expect(event.isDisplayedEventReplyAction(ownerEmail), isFalse); + }); + + test('returns false when user is NOT listed in participants', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [nonMatchingParticipant], + ); + + expect(event.isDisplayedEventReplyAction(ownerEmail), isFalse); + }); + }); + + group('calendar_event_extension::getEventActionTypesIsDisplayed:', () { + test('Should returns yes/maybe/no + mailToAttendees when method is request and user is in participants', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [matchingParticipant], + ); + + final actions = event.getEventActionTypesIsDisplayed(ownerEmail); + + expect(actions, [ + EventActionType.yes, + EventActionType.maybe, + EventActionType.no, + EventActionType.mailToAttendees, + ]); + }); + + test('Should returns acceptCounter + mailToAttendees when method is counter and user is in participants', () { + final event = CalendarEvent( + method: EventMethod.counter, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [matchingParticipant], + ); + + final actions = event.getEventActionTypesIsDisplayed(ownerEmail); + + expect(actions, [ + EventActionType.acceptCounter, + EventActionType.mailToAttendees, + ]); + }); + + test('Should returns only mailToAttendees when method is not repliable but organizer is present', () { + final event = CalendarEvent( + method: EventMethod.cancel, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [matchingParticipant], + ); + + final actions = event.getEventActionTypesIsDisplayed(ownerEmail); + + expect(actions, [EventActionType.mailToAttendees]); + }); + + test('Should returns only mailToAttendees when user is NOT in participants but organizer is present', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: CalendarOrganizer(mailto: MailAddress(ownerEmail)), + participants: [nonMatchingParticipant], + ); + + final actions = event.getEventActionTypesIsDisplayed(ownerEmail); + + expect(actions, [EventActionType.mailToAttendees]); + }); + + test('Should returns empty list when no organizer and no participants', () { + final event = CalendarEvent( + method: EventMethod.request, + organizer: null, + participants: null, + ); + + final actions = event.getEventActionTypesIsDisplayed(ownerEmail); + + expect(actions, []); }); }); } \ No newline at end of file