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