diff --git a/assets/images/ic_event_canceled.svg b/assets/images/ic_event_canceled.svg new file mode 100644 index 000000000..7b011b5a9 --- /dev/null +++ b/assets/images/ic_event_canceled.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/images/ic_event_invited.svg b/assets/images/ic_event_invited.svg new file mode 100644 index 000000000..4497cc4d9 --- /dev/null +++ b/assets/images/ic_event_invited.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/images/ic_event_updated.svg b/assets/images/ic_event_updated.svg new file mode 100644 index 000000000..c339cd6a4 --- /dev/null +++ b/assets/images/ic_event_updated.svg @@ -0,0 +1,5 @@ + + + diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index b2282e779..be56651b3 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -186,6 +186,11 @@ extension AppColor on Color { static const colorNetworkConnectionLabel = Color(0xFF818C99); static const colorCalendarEventRead = Color(0xFF818C99); static const colorCalendarEventUnread = Color(0xFF1C1B1F); + static const colorMaybeEventActionText = Color(0xFFFFC107); + static const colorInvitedEventActionText = Color(0xFF007AFF); + static const colorUpdatedEventActionText = Color(0xFF4BB34B); + static const colorCanceledEventActionText = Color(0xFFFF3347); + static const colorSubTitleEventActionText = Color(0xFF939393); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 4508fc577..d5a010dd7 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -195,6 +195,9 @@ class ImagePaths { String get icArrowRight => _getImagePath('ic_arrow_right.svg'); String get icAddPicture => _getImagePath('ic_add_picture.svg'); String get icCalendarEvent => _getImagePath('ic_calendar_event.svg'); + String get icEventInvited => _getImagePath('ic_event_invited.svg'); + String get icEventUpdated => _getImagePath('ic_event_updated.svg'); + String get icEventCanceled => _getImagePath('ic_event_canceled.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/lib/features/email/domain/extensions/list_attachments_extension.dart b/lib/features/email/domain/extensions/list_attachments_extension.dart index 1cbfc601a..bd9a0fc08 100644 --- a/lib/features/email/domain/extensions/list_attachments_extension.dart +++ b/lib/features/email/domain/extensions/list_attachments_extension.dart @@ -1,8 +1,17 @@ +import 'package:collection/collection.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:model/email/attachment.dart'; import 'package:tmail_ui_user/features/email/domain/extensions/attachment_extension.dart'; import 'package:tmail_ui_user/features/offline_mode/model/attachment_hive_cache.dart'; extension ListAttachmentsExtension on List { List toHiveCache() => map((attachment) => attachment.toHiveCache()).toList(); + + Set get calendarAttachments => where((attachment) => attachment.isCalendarEvent).toSet(); + + Set get calendarEventBlobIds => calendarAttachments + .map((attachment) => attachment.blobId) + .whereNotNull() + .toSet(); } \ No newline at end of file diff --git a/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart b/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart index d85bdb780..dc1fa15f5 100644 --- a/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart +++ b/lib/features/email/presentation/bindings/calendar_event_interactor_bindings.dart @@ -1,4 +1,5 @@ import 'package:get/get.dart'; +import 'package:jmap_dart_client/http/http_client.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; import 'package:tmail_ui_user/features/email/data/datasource/calendar_event_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/calendar_event_datasource_impl.dart'; @@ -17,6 +18,7 @@ class CalendarEventInteractorBindings extends InteractorsBindings { @override void bindingsDataSourceImpl() { + Get.lazyPut(() => CalendarEventAPI(Get.find())); Get.lazyPut(() => CalendarEventDataSourceImpl( Get.find(), Get.find())); diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index a40843dba..8d75bf248 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -6,7 +6,6 @@ import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -14,6 +13,7 @@ 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/identities/identity.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:jmap_dart_client/jmap/mdn/disposition.dart'; @@ -27,7 +27,9 @@ import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.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/extensions/list_attachments_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; +import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/calendar_event_interactor_bindings.dart'; @@ -109,6 +111,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { final attachmentsExpandMode = ExpandMode.COLLAPSE.obs; final emailContents = RxnString(); final attachments = [].obs; + final calendarEvent = Rxn(); + EmailId? _currentEmailId; Identity? _identitySelected; String? initialEmailContents; @@ -175,6 +179,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _sendReceiptToSenderSuccess(success); } else if (success is CreateNewRuleFilterSuccess) { _createNewRuleFilterSuccess(success); + } else if (success is ParseCalendarEventSuccess) { + _handleParseCalendarEventSuccess(success); } } @@ -391,6 +397,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { initialEmailContents = success.emailContent; attachments.value = success.attachments; + _loadCalendarEventAction(success.attachments.calendarEventBlobIds); + final isShowMessageReadReceipt = success.emailCurrent?.hasReadReceipt(mailboxDashBoardController.mapMailboxById) == true; if (isShowMessageReadReceipt) { _handleReadReceipt(); @@ -416,6 +424,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { initialEmailContents = success.emailContent; attachments.value = success.attachments; + _loadCalendarEventAction(success.attachments.calendarEventBlobIds); + if (PlatformInfo.isMobile) { final detailedEmail = DetailedEmail( emailId: currentEmail!.id!, @@ -461,6 +471,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { emailContents.value = null; initialEmailContents = null; attachments.clear(); + calendarEvent.value = null; } PresentationMailbox? getMailboxContain(PresentationEmail email) { @@ -1229,4 +1240,45 @@ class SingleEmailController extends BaseController with AppLoaderMixin { consumeState(_storeOpenedEmailInteractor.execute(session, accountId, detailedEmail)); } } + + void _loadCalendarEventAction(Set blobIds) { + log('SingleEmailController::_loadCalendarEventAction:blobIds: $blobIds'); + if (_isCalendarEventSupported) { + if (currentEmail?.hasCalendarEvent == true && + blobIds.isNotEmpty && + mailboxDashBoardController.accountId.value != null) { + _parseCalendarEventAction( + mailboxDashBoardController.accountId.value!, + blobIds + ); + } else { + logError('SingleEmailController::_loadCalendarEventAction: not found calendar event header'); + } + } else { + logError('SingleEmailController::_loadCalendarEventAction: calendar event not supported'); + } + } + + bool get _isCalendarEventSupported { + final accountId = mailboxDashBoardController.accountId.value; + final session = mailboxDashBoardController.sessionCurrent; + return session != null && + accountId != null && + CapabilityIdentifier.jamesCalendarEvent.isSupported(session, accountId); + } + + void _parseCalendarEventAction(AccountId accountId, Set blobIds) { + log("SingleEmailController::_parseCalendarEventAction:blobIds: $blobIds"); + if (_parseCalendarEventInteractor != null) { + consumeState(_parseCalendarEventInteractor!.execute(accountId, blobIds)); + } else { + logError("SingleEmailController::_parseCalendarEventAction: _parseCalendarEventInteractor is NULL"); + } + } + + void _handleParseCalendarEventSuccess(ParseCalendarEventSuccess success) { + if (success.calendarEventList.isNotEmpty) { + calendarEvent.value = success.calendarEventList.first; + } + } } \ No newline at end of file diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index c86218365..c74f9657a 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -1,6 +1,5 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; -import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/utils/icon_utils.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/icon_button_web.dart'; @@ -11,7 +10,6 @@ import 'package:core/utils/app_logger.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:filesize/filesize.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; @@ -21,12 +19,17 @@ import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/list_attachment_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/base/widget/custom_scroll_behavior.dart'; import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart'; +import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; +import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/app_bar_mail_widget_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event_action_banner_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/information_sender_and_receiver_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; @@ -34,7 +37,7 @@ import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widg import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; -class EmailView extends GetWidget { +class EmailView extends GetWidget with AppLoaderMixin { final responsiveUtils = Get.find(); final imagePaths = Get.find(); @@ -278,38 +281,57 @@ class EmailView extends GetWidget { imagePaths: imagePaths, responsiveUtils: responsiveUtils, ), - _buildLoadingView(), + _buildLoadingContentView(), _buildAttachments(context), + Obx(() { + if (controller.calendarEvent.value != null) { + return CalendarEventActionBannerWidget( + calendarEvent: controller.calendarEvent.value!, + listFromEmailAddress: controller.currentEmail?.from + ); + } else { + return _buildLoadingCalendarEventBanner(); + } + }), if (PlatformInfo.isWeb) Expanded(child: Padding( - padding: EdgeInsets.only( - left: AppUtils.isDirectionRTL(context) ? 0 : 16, - right: AppUtils.isDirectionRTL(context) ? 16 : 0, - bottom: 16 - ), + padding: const EdgeInsetsDirectional.only(start: 16, bottom: 16), child: _buildEmailContent(context, constraints, email) )) else Padding( - padding: const EdgeInsets.all(16), - child: _buildEmailContent(context, constraints, email)) + padding: const EdgeInsetsDirectional.symmetric( + vertical: EmailViewStyles.mobileContentVerticalMargin, + horizontal: EmailViewStyles.mobileContentHorizontalMargin + ), + child: _buildEmailContent(context, constraints, email) + ) ], ); }); } - Widget _buildLoadingView() { + Widget _buildLoadingContentView() { return Obx(() { return controller.viewState.value.fold( (failure) => const SizedBox.shrink(), (success) { - if (success is LoadingState) { - return const Align(alignment: Alignment.topCenter, child: Padding( - padding: EdgeInsets.all(16), - child: SizedBox( - width: 30, - height: 30, - child: CupertinoActivityIndicator(color: AppColor.colorLoading)))); + if (success is GetEmailContentLoading) { + return loadingWidget; + } else { + return const SizedBox.shrink(); + } + }); + }); + } + + Widget _buildLoadingCalendarEventBanner() { + return Obx(() { + return controller.viewState.value.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is ParseCalendarEventLoading) { + return loadingWidget; } else { return const SizedBox.shrink(); } @@ -329,7 +351,7 @@ class EmailView extends GetWidget { Widget _buildAttachmentsBody(BuildContext context, List attachments) { return Container( color: Colors.white, - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 12, top: 10), + padding: const EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 16), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildAttachmentsHeader(context, attachments), diff --git a/lib/features/email/presentation/extensions/calendar_event_extension.dart b/lib/features/email/presentation/extensions/calendar_event_extension.dart new file mode 100644 index 000000000..92f1428f9 --- /dev/null +++ b/lib/features/email/presentation/extensions/calendar_event_extension.dart @@ -0,0 +1,178 @@ + +import 'package:collection/collection.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:flutter/material.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_participation_status.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/properties/event_method.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +extension CalendarEventExtension on CalendarEvent { + + Color getColorEventActionBanner(String senderEmailAddress) { + switch(method) { + case EventMethod.request: + case EventMethod.add: + return AppColor.colorInvitedEventActionText; + case EventMethod.refresh: + case EventMethod.counter: + return AppColor.colorUpdatedEventActionText; + case EventMethod.cancel: + case EventMethod.declineCounter: + return AppColor.colorCanceledEventActionText; + case EventMethod.reply: + final matchedAttendee = findAttendeeHasUpdatedStatus(senderEmailAddress); + if (matchedAttendee != null) { + return getAttendeeMessageTextColor(matchedAttendee.participationStatus); + } else { + return Colors.transparent; + } + default: + return Colors.transparent; + } + } + + Color getColorEventActionText(String senderEmailAddress) { + switch(method) { + case EventMethod.request: + case EventMethod.add: + return AppColor.colorInvitedEventActionText; + case EventMethod.refresh: + case EventMethod.counter: + return AppColor.colorUpdatedEventActionText; + case EventMethod.cancel: + case EventMethod.declineCounter: + return AppColor.colorCanceledEventActionText; + case EventMethod.reply: + final matchedAttendee = findAttendeeHasUpdatedStatus(senderEmailAddress); + if (matchedAttendee != null) { + return getAttendeeMessageTextColor(matchedAttendee.participationStatus); + } else { + return Colors.transparent; + } + default: + return Colors.transparent; + } + } + + String getIconEventAction(ImagePaths imagePaths) { + switch(method) { + case EventMethod.request: + case EventMethod.add: + return imagePaths.icEventInvited; + case EventMethod.refresh: + return imagePaths.icEventUpdated; + case EventMethod.cancel: + return imagePaths.icEventCanceled; + default: + return ''; + } + } + + String getTitleEventAction(BuildContext context, String senderEmailAddress) { + switch(method) { + case EventMethod.request: + case EventMethod.add: + return AppLocalizations.of(context).messageEventActionBannerOrganizerInvited; + case EventMethod.refresh: + return AppLocalizations.of(context).messageEventActionBannerOrganizerUpdated; + case EventMethod.cancel: + return AppLocalizations.of(context).messageEventActionBannerOrganizerCanceled; + case EventMethod.reply: + final matchedAttendee = findAttendeeHasUpdatedStatus(senderEmailAddress); + if (matchedAttendee != null) { + return getAttendeeMessageStatus(context, matchedAttendee.participationStatus); + } else { + return ''; + } + case EventMethod.counter: + return AppLocalizations.of(context).messageEventActionBannerAttendeeCounter; + case EventMethod.declineCounter: + return AppLocalizations.of(context).messageEventActionBannerAttendeeCounterDeclined; + default: + return ''; + } + } + + String getSubTitleEventAction(BuildContext context) { + switch(method) { + case EventMethod.refresh: + return AppLocalizations.of(context).subMessageEventActionBannerUpdated; + case EventMethod.cancel: + return AppLocalizations.of(context).subMessageEventActionBannerCanceled; + default: + return ''; + } + } + + String getUserNameEventAction({ + required BuildContext context, + required ImagePaths imagePaths, + required String senderEmailAddress + }) { + switch(method) { + case EventMethod.request: + case EventMethod.add: + case EventMethod.refresh: + case EventMethod.cancel: + case EventMethod.declineCounter: + return getOrganizerName(context); + case EventMethod.reply: + case EventMethod.counter: + return getAttendeeName(context, senderEmailAddress); + default: + return ''; + } + } + + String getOrganizerName(BuildContext context) => organizer?.name ?? AppLocalizations.of(context).you; + + String getAttendeeName(BuildContext context, String senderEmailAddress) { + final matchedAttendee = findAttendeeHasUpdatedStatus(senderEmailAddress); + if (matchedAttendee != null) { + return matchedAttendee.name?.name ?? AppLocalizations.of(context).anAttendee; + } else { + return AppLocalizations.of(context).anAttendee; + } + } + + CalendarAttendee? findAttendeeHasUpdatedStatus(String senderEmailAddress) { + if (participants?.isNotEmpty == true) { + final listMatchedAttendee = participants + !.where((attendee) => attendee.mailto?.mailAddress.value == senderEmailAddress) + .whereNotNull(); + log('CalendarEventExtension::findAttendeeHasUpdatedStatus:listMatchedAttendee: $listMatchedAttendee'); + if (listMatchedAttendee.isNotEmpty) { + return listMatchedAttendee.first; + } + } + return null; + } + + String getAttendeeMessageStatus(BuildContext context, CalendarAttendeeParticipationStatus? status) { + if (status == CalendarAttendeeParticipationStatus('ACCEPTED')) { + return AppLocalizations.of(context).messageEventActionBannerAttendeeAccepted; + } else if (status == CalendarAttendeeParticipationStatus('TENTATIVE')) { + return AppLocalizations.of(context).messageEventActionBannerAttendeeTentative; + } else if (status == CalendarAttendeeParticipationStatus('DECLINED')) { + return AppLocalizations.of(context).messageEventActionBannerAttendeeDeclined; + } else { + return ''; + } + } + + Color getAttendeeMessageTextColor(CalendarAttendeeParticipationStatus? status) { + if (status == CalendarAttendeeParticipationStatus('ACCEPTED')) { + return AppColor.colorUpdatedEventActionText; + } else if (status == CalendarAttendeeParticipationStatus('TENTATIVE')) { + return AppColor.colorMaybeEventActionText; + } else if (status == CalendarAttendeeParticipationStatus('DECLINED')) { + return AppColor.colorCanceledEventActionText; + } else { + return Colors.transparent; + } + } +} \ No newline at end of file diff --git a/lib/features/email/presentation/styles/calendar_event_action_banner_styles.dart b/lib/features/email/presentation/styles/calendar_event_action_banner_styles.dart new file mode 100644 index 000000000..e3c995d69 --- /dev/null +++ b/lib/features/email/presentation/styles/calendar_event_action_banner_styles.dart @@ -0,0 +1,7 @@ + +class CalendarEventActionBannerStyles { + static const double borderRadius = 16; + static const double contentPadding = 16; + static const double viewHorizontalMargin = 16; + static const double viewVerticalMargin = 12; +} \ No newline at end of file diff --git a/lib/features/email/presentation/styles/email_view_styles.dart b/lib/features/email/presentation/styles/email_view_styles.dart new file mode 100644 index 000000000..1f331a756 --- /dev/null +++ b/lib/features/email/presentation/styles/email_view_styles.dart @@ -0,0 +1,5 @@ + +class EmailViewStyles { + static const double mobileContentHorizontalMargin = 16; + static const double mobileContentVerticalMargin = 12; +} \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/calendar_event_action_banner_widget.dart b/lib/features/email/presentation/widgets/calendar_event_action_banner_widget.dart new file mode 100644 index 000000000..c2608f18f --- /dev/null +++ b/lib/features/email/presentation/widgets/calendar_event_action_banner_widget.dart @@ -0,0 +1,104 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/extensions/email_address_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/styles/calendar_event_action_banner_styles.dart'; + +class CalendarEventActionBannerWidget extends StatelessWidget { + + final CalendarEvent calendarEvent; + final Set? listFromEmailAddress; + + const CalendarEventActionBannerWidget({ + super.key, + required this.calendarEvent, + required this.listFromEmailAddress, + }); + + @override + Widget build(BuildContext context) { + final imagePaths = Get.find(); + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(CalendarEventActionBannerStyles.borderRadius)), + color: calendarEvent.getColorEventActionBanner(_getSenderEmailAddress()).withOpacity(0.12) + ), + padding: const EdgeInsets.all(CalendarEventActionBannerStyles.contentPadding), + margin: const EdgeInsets.symmetric( + vertical: CalendarEventActionBannerStyles.viewVerticalMargin, + horizontal: CalendarEventActionBannerStyles.viewHorizontalMargin, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (calendarEvent.getIconEventAction(imagePaths).isNotEmpty) + Padding( + padding: const EdgeInsetsDirectional.only(end: 8), + child: SvgPicture.asset( + calendarEvent.getIconEventAction(imagePaths), + width: 24, + height: 24, + fit: BoxFit.fill, + ), + ), + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + RichText( + text: TextSpan( + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w400, + color: calendarEvent.getColorEventActionText(_getSenderEmailAddress()) + ), + children: [ + TextSpan( + text: calendarEvent.getUserNameEventAction( + context: context, + imagePaths: imagePaths, + senderEmailAddress: _getSenderEmailAddress() + ), + style: TextStyle( + color: calendarEvent.getColorEventActionText(_getSenderEmailAddress()), + fontSize: 20, + fontWeight: FontWeight.w700 + ), + ), + TextSpan(text: calendarEvent.getTitleEventAction(context, _getSenderEmailAddress())) + ] + ) + ), + if (calendarEvent.getSubTitleEventAction(context).isNotEmpty) + Text( + calendarEvent.getSubTitleEventAction(context), + style: const TextStyle( + color: AppColor.colorSubTitleEventActionText, + fontSize: 16, + fontWeight: FontWeight.w400 + ), + ) + ] + )) + ] + ), + ); + } + + String _getSenderEmailAddress() { + if (listFromEmailAddress?.isNotEmpty == true) { + final senderEmailAddress = listFromEmailAddress!.first.emailAddress; + log('CalendarEventActionBannerWidget::getSenderEmailAddress: $senderEmailAddress'); + return senderEmailAddress; + } else { + return ''; + } + } +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index b5af62273..0e2cb5f62 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2023-07-12T18:53:48.611802", + "@@last_modified": "2023-07-24T18:44:17.206723", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -2975,5 +2975,77 @@ "placeholders": { "maxSize": {} } + }, + "messageEventActionBannerOrganizerInvited": " has invited you in to a meeting", + "@messageEventActionBannerOrganizerInvited": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageEventActionBannerOrganizerUpdated": " has updated a meeting", + "@messageEventActionBannerOrganizerUpdated": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageEventActionBannerOrganizerCanceled": " has canceled a meeting", + "@messageEventActionBannerOrganizerCanceled": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "subMessageEventActionBannerUpdated": "\"The time has been updated to better suit all of you\"", + "@subMessageEventActionBannerUpdated": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "subMessageEventActionBannerCanceled": "\"We are canceling the event due to bad weather.\"", + "@subMessageEventActionBannerCanceled": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "anAttendee": "An attendee", + "@anAttendee": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "you": "You", + "@you": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageEventActionBannerAttendeeAccepted": " has accepted this invitation", + "@messageEventActionBannerAttendeeAccepted": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageEventActionBannerAttendeeTentative": " has replied \"Maybe\" to this invitation", + "@messageEventActionBannerAttendeeTentative": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageEventActionBannerAttendeeDeclined": " has declined this invitation", + "@messageEventActionBannerAttendeeDeclined": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageEventActionBannerAttendeeCounter": " has proposed changes to the event", + "@messageEventActionBannerAttendeeCounter": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageEventActionBannerAttendeeCounterDeclined": "Your counter proposal was declined", + "@messageEventActionBannerAttendeeCounterDeclined": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 98215ff52..4065c58cb 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3071,4 +3071,76 @@ class AppLocalizations { name: 'pleaseChooseAnImageSizeCorrectly', args: [maxSize]); } + + String get messageEventActionBannerOrganizerInvited { + return Intl.message( + ' has invited you in to a meeting', + name: 'messageEventActionBannerOrganizerInvited'); + } + + String get messageEventActionBannerOrganizerUpdated { + return Intl.message( + ' has updated a meeting', + name: 'messageEventActionBannerOrganizerUpdated'); + } + + String get messageEventActionBannerOrganizerCanceled { + return Intl.message( + ' has canceled a meeting', + name: 'messageEventActionBannerOrganizerCanceled'); + } + + String get subMessageEventActionBannerUpdated { + return Intl.message( + '"The time has been updated to better suit all of you"', + name: 'subMessageEventActionBannerUpdated'); + } + + String get subMessageEventActionBannerCanceled { + return Intl.message( + '"We are canceling the event due to bad weather."', + name: 'subMessageEventActionBannerCanceled'); + } + + String get anAttendee { + return Intl.message( + 'An attendee', + name: 'anAttendee'); + } + + String get you { + return Intl.message( + 'You', + name: 'you'); + } + + String get messageEventActionBannerAttendeeAccepted { + return Intl.message( + ' has accepted this invitation', + name: 'messageEventActionBannerAttendeeAccepted'); + } + + String get messageEventActionBannerAttendeeTentative { + return Intl.message( + ' has replied "Maybe" to this invitation', + name: 'messageEventActionBannerAttendeeTentative'); + } + + String get messageEventActionBannerAttendeeDeclined { + return Intl.message( + ' has declined this invitation', + name: 'messageEventActionBannerAttendeeDeclined'); + } + + String get messageEventActionBannerAttendeeCounter { + return Intl.message( + ' has proposed changes to the event', + name: 'messageEventActionBannerAttendeeCounter'); + } + + String get messageEventActionBannerAttendeeCounterDeclined { + return Intl.message( + 'Your counter proposal was declined', + name: 'messageEventActionBannerAttendeeCounterDeclined'); + } } \ No newline at end of file diff --git a/model/lib/email/attachment.dart b/model/lib/email/attachment.dart index 2d56989d2..2e78df771 100644 --- a/model/lib/email/attachment.dart +++ b/model/lib/email/attachment.dart @@ -50,6 +50,8 @@ class Attachment with EquatableMixin { } } + bool get isCalendarEvent => type?.subtype == 'ics' || type?.subtype == 'calendar'; + @override List get props => [partId, blobId, size, name, type, cid, disposition]; }