TF-3792 Hide buttons of blue bar in case of event not belong to this account and change

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-06-20 10:42:21 +07:00
committed by Dat H. Pham
parent 3e48d9cf92
commit 8579eb2518
18 changed files with 377 additions and 396 deletions
+2 -2
View File
@@ -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<PreviewAttachment>? listAttachment,
}) {
Document document = parse(HtmlUtils.createTemplateHtmlDocument(title: '$subject - $userName'));
Document document = parse(HtmlUtils.createTemplateHtmlDocument(title: '$subject - $ownEmailAddress'));
Element? emailElement = _createEmailElement(
subjectPrefix: subjectPrefix,
@@ -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,
@@ -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,
@@ -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<EventActionType> {
List<EventActionType> validActionsOfEventMethod(EventMethod? eventMethod) {
return where((action) {
if (eventMethod != EventMethod.counter) {
return action != EventActionType.acceptCounter;
}
return action == EventActionType.acceptCounter
|| action == EventActionType.mailToAttendees;
}).toList();
}
}
@@ -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<Object?> get props => [
accountId,
userName,
ownEmailAddress,
blobId,
email,
locale,
@@ -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<Attachment> 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<Object?> get props => [
userName,
ownEmailAddress,
presentationEmail,
attachments,
emailContent,
@@ -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,
@@ -367,13 +367,22 @@ class EmailView extends GetWidget<SingleEmailController> {
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,
@@ -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<EventActionType> 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,
@@ -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);
}
@@ -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);
}
@@ -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<EventActionType> eventActions;
final OnCalendarEventReplyActionClick onCalendarEventReplyActionClick;
final bool calendarEventReplying;
final EventMethod? eventMethod;
final AttendanceStatus? attendanceStatus;
final bool isPortraitMobile;
final VoidCallback? onMailToAttendeesAction;
final _responsiveUtils = Get.find<ResponsiveUtils>();
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)
@@ -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<String> listEmailAddressSender;
final String ownEmailAddress;
final bool isPortraitMobile;
final _responsiveUtils = Get.find<ResponsiveUtils>();
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),
],
),
),
@@ -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!),
+7 -1
View File
@@ -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": {}
}
}
@@ -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',
);
}
}
@@ -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,
]));
});
});
});
}
@@ -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, []);
});
});
}