507 lines
17 KiB
Dart
507 lines
17 KiB
Dart
|
|
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:date_format/date_format.dart' as date_format;
|
|
import 'package:flutter/material.dart';
|
|
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_participation_status.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_duration.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_event_status.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_extension_fields.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_free_busy_status.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_organizer.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_priority.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_privacy.dart';
|
|
import 'package:jmap_dart_client/jmap/mail/calendar/properties/calendar_sequence.dart';
|
|
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/features/email/presentation/utils/email_utils.dart';
|
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
|
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
|
|
|
extension CalendarEventExtension on CalendarEvent {
|
|
|
|
static const String acceptedParticipationStatus = 'ACCEPTED';
|
|
static const String tentativeParticipationStatus = 'TENTATIVE';
|
|
static const String declinedParticipationStatus = 'DECLINED';
|
|
|
|
Color? getColorEventActionBanner(List<String> listEmailAddressSender) {
|
|
switch(method) {
|
|
case EventMethod.request:
|
|
case EventMethod.add:
|
|
return AppColor.colorInvitedEventActionBanner;
|
|
case EventMethod.refresh:
|
|
case EventMethod.counter:
|
|
return AppColor.colorUpdatedEventActionBanner;
|
|
case EventMethod.cancel:
|
|
case EventMethod.declineCounter:
|
|
return AppColor.colorCanceledEventActionBanner;
|
|
case EventMethod.reply:
|
|
final matchedAttendee = findAttendeeHasUpdatedStatus(listEmailAddressSender);
|
|
if (matchedAttendee != null) {
|
|
return getAttendeeMessageBannerColor(matchedAttendee.participationStatus);
|
|
} else {
|
|
return null;
|
|
}
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Color? getColorEventActionText(List<String> listEmailAddressSender) {
|
|
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(listEmailAddressSender);
|
|
if (matchedAttendee != null) {
|
|
return getAttendeeMessageTextColor(matchedAttendee.participationStatus);
|
|
} else {
|
|
return null;
|
|
}
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
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, List<String> listEmailAddressSender) {
|
|
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(listEmailAddressSender);
|
|
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 getUserNameEventAction({
|
|
required BuildContext context,
|
|
required ImagePaths imagePaths,
|
|
required List<String> listEmailAddressSender
|
|
}) {
|
|
switch(method) {
|
|
case EventMethod.request:
|
|
case EventMethod.add:
|
|
case EventMethod.refresh:
|
|
case EventMethod.cancel:
|
|
case EventMethod.declineCounter:
|
|
return organizerName;
|
|
case EventMethod.reply:
|
|
case EventMethod.counter:
|
|
return getAttendeeNameEvent(context, listEmailAddressSender);
|
|
default:
|
|
return '';
|
|
}
|
|
}
|
|
|
|
String get organizerName => organizer?.name ?? organizer?.mailto?.value ?? '';
|
|
|
|
String getAttendeeNameEvent(BuildContext context, List<String> listEmailAddressSender) {
|
|
final matchedAttendee = findAttendeeHasUpdatedStatus(listEmailAddressSender);
|
|
if (matchedAttendee != null) {
|
|
return matchedAttendee.name?.name ?? AppLocalizations.of(context).anAttendee;
|
|
} else {
|
|
return AppLocalizations.of(context).anAttendee;
|
|
}
|
|
}
|
|
|
|
CalendarAttendee? findAttendeeHasUpdatedStatus(List<String> listEmailAddressSender) {
|
|
if (participants?.isNotEmpty == true) {
|
|
final listMatchedAttendee = participants
|
|
!.where((attendee) => attendee.mailto != null && listEmailAddressSender.contains(attendee.mailto!.mailAddress.value))
|
|
.nonNulls;
|
|
log('CalendarEventExtension::findAttendeeHasUpdatedStatus:listMatchedAttendee: $listMatchedAttendee');
|
|
if (listMatchedAttendee.isNotEmpty) {
|
|
return listMatchedAttendee.first;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
String getAttendeeMessageStatus(BuildContext context, CalendarAttendeeParticipationStatus? status) {
|
|
if (status == CalendarAttendeeParticipationStatus(acceptedParticipationStatus)) {
|
|
return AppLocalizations.of(context).messageEventActionBannerAttendeeAccepted;
|
|
} else if (status == CalendarAttendeeParticipationStatus(tentativeParticipationStatus)) {
|
|
return AppLocalizations.of(context).messageEventActionBannerAttendeeTentative;
|
|
} else if (status == CalendarAttendeeParticipationStatus(declinedParticipationStatus)) {
|
|
return AppLocalizations.of(context).messageEventActionBannerAttendeeDeclined;
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
Color? getAttendeeMessageBannerColor(CalendarAttendeeParticipationStatus? status) {
|
|
if (status == CalendarAttendeeParticipationStatus(acceptedParticipationStatus)) {
|
|
return AppColor.colorUpdatedEventActionBanner;
|
|
} else if (status == CalendarAttendeeParticipationStatus(tentativeParticipationStatus)) {
|
|
return AppColor.colorMaybeEventActionBanner;
|
|
} else if (status == CalendarAttendeeParticipationStatus(declinedParticipationStatus)) {
|
|
return AppColor.colorCanceledEventActionBanner;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Color? getAttendeeMessageTextColor(CalendarAttendeeParticipationStatus? status) {
|
|
if (status == CalendarAttendeeParticipationStatus(acceptedParticipationStatus)) {
|
|
return AppColor.colorUpdatedEventActionText;
|
|
} else if (status == CalendarAttendeeParticipationStatus(tentativeParticipationStatus)) {
|
|
return AppColor.colorMaybeEventActionText;
|
|
} else if (status == CalendarAttendeeParticipationStatus(declinedParticipationStatus)) {
|
|
return AppColor.colorCanceledEventActionText;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
DateTime? get localStartDate => startUtcDate?.value.toLocal();
|
|
|
|
DateTime? get localEndDate => endUtcDate?.value.toLocal();
|
|
|
|
String get monthStartDateAsString {
|
|
if (localStartDate != null) {
|
|
return date_format.formatDate(
|
|
localStartDate!,
|
|
[date_format.M],
|
|
locale: AppUtils.getCurrentDateLocale()
|
|
);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
String get dayStartDateAsString {
|
|
if (localStartDate != null) {
|
|
return date_format.formatDate(
|
|
localStartDate!,
|
|
[date_format.d],
|
|
locale: AppUtils.getCurrentDateLocale()
|
|
);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
String get weekDayStartDateAsString {
|
|
if (localStartDate != null) {
|
|
return date_format.formatDate(
|
|
localStartDate!,
|
|
[date_format.D],
|
|
locale: AppUtils.getCurrentDateLocale()
|
|
);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
String formatDateTime(date_format.DateLocale locale, DateTime dateTime) {
|
|
return date_format.formatDate(
|
|
dateTime,
|
|
[
|
|
date_format.DD,
|
|
', ',
|
|
date_format.MM,
|
|
' ',
|
|
date_format.dd,
|
|
', ',
|
|
date_format.yyyy,
|
|
' ',
|
|
date_format.hh,
|
|
':',
|
|
date_format.nn,
|
|
' ',
|
|
date_format.am
|
|
],
|
|
locale: locale
|
|
);
|
|
}
|
|
|
|
String formatTime(date_format.DateLocale locale, DateTime dateTime) {
|
|
return date_format.formatDate(
|
|
dateTime,
|
|
[
|
|
date_format.hh,
|
|
':',
|
|
date_format.nn,
|
|
' ',
|
|
date_format.am
|
|
],
|
|
locale: locale
|
|
);
|
|
}
|
|
|
|
String formatDate(date_format.DateLocale locale, DateTime dateTime) {
|
|
return date_format.formatDate(
|
|
dateTime,
|
|
[
|
|
date_format.DD,
|
|
', ',
|
|
date_format.MM,
|
|
' ',
|
|
date_format.dd,
|
|
', ',
|
|
date_format.yyyy
|
|
],
|
|
locale: locale
|
|
);
|
|
}
|
|
|
|
bool get isAllDayEvent {
|
|
if (startUtcDate != null && endUtcDate != null) {
|
|
final startDateValue = startUtcDate!.value;
|
|
final endDateValue = endUtcDate!.value;
|
|
|
|
final eventDurationInHours = startDateValue.difference(endDateValue).inHours;
|
|
|
|
final startHour = startDateValue.hour;
|
|
final startMinute = startDateValue.minute;
|
|
final startSecond = startDateValue.second;
|
|
|
|
final endHour = endDateValue.hour;
|
|
final endMinute = endDateValue.minute;
|
|
final endSecond = endDateValue.second;
|
|
|
|
return startHour == 0
|
|
&& startMinute == 0
|
|
&& startSecond == 0
|
|
&& endHour == 0
|
|
&& endMinute == 0
|
|
&& endSecond == 0
|
|
&& eventDurationInHours % 24 == 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String dateTimeStringForAllDayEvent({
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
required date_format.DateLocale dateLocale,
|
|
required String timeZone
|
|
}) {
|
|
final dateStart = formatDate(dateLocale, startDate);
|
|
final endDateToDisplay = endDate.subtract(const Duration(days: 1));
|
|
final dateEnd = formatDate(dateLocale, endDateToDisplay);
|
|
|
|
if (DateUtils.isSameDay(startDate, endDateToDisplay)) {
|
|
return '$dateStart ($timeZone)';
|
|
} else {
|
|
return '$dateStart - $dateEnd ($timeZone)';
|
|
}
|
|
}
|
|
|
|
String getDateTimeEvent({
|
|
required date_format.DateLocale dateLocale,
|
|
required String timeZone
|
|
}) {
|
|
if (isAllDayEvent) {
|
|
return dateTimeStringForAllDayEvent(
|
|
startDate: startUtcDate!.value,
|
|
endDate: endUtcDate!.value,
|
|
dateLocale: dateLocale,
|
|
timeZone: timeZone,
|
|
);
|
|
}
|
|
|
|
if (localStartDate != null && localEndDate != null) {
|
|
final timeStart = formatDateTime(dateLocale, localStartDate!);
|
|
final timeEnd = DateUtils.isSameDay(localStartDate, localEndDate)
|
|
? formatTime(dateLocale, localEndDate!)
|
|
: formatDateTime(dateLocale, localEndDate!);
|
|
return '$timeStart - $timeEnd';
|
|
} else if (localStartDate != null) {
|
|
return formatDateTime(dateLocale, localStartDate!);
|
|
} else if (localEndDate != null) {
|
|
return formatDateTime(dateLocale, localEndDate!);
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
List<String> get videoConferences {
|
|
if (extensionFields != null && extensionFields!.mapFields.isNotEmpty) {
|
|
final videoConferences = List<String>.empty(growable: true);
|
|
|
|
final openPaasVideoConferences = extensionFields?.mapFields['X-OPENPAAS-VIDEOCONFERENCE']
|
|
?.nonNulls
|
|
.where((link) => link.isNotEmpty)
|
|
.toList() ?? [];
|
|
log('CalendarEventExtension::openPaasVideoConferences: $openPaasVideoConferences');
|
|
final googleVideoConferences = extensionFields!.mapFields['X-GOOGLE-CONFERENCE']
|
|
?.nonNulls
|
|
.where((link) => link.isNotEmpty)
|
|
.toList() ?? [];
|
|
log('CalendarEventExtension::googleVideoConferences: $googleVideoConferences');
|
|
if (openPaasVideoConferences.isNotEmpty) {
|
|
videoConferences.addAll(openPaasVideoConferences);
|
|
}
|
|
if (googleVideoConferences.isNotEmpty) {
|
|
videoConferences.addAll(googleVideoConferences);
|
|
}
|
|
return videoConferences;
|
|
}
|
|
return [];
|
|
}
|
|
|
|
bool isDisplayedEventReplyAction(String ownerEmailAddress) =>
|
|
method != null &&
|
|
_methodIsRepliable &&
|
|
organizer != null &&
|
|
participants?.isNotEmpty == true &&
|
|
userIsListedInParticipants(ownerEmailAddress);
|
|
|
|
bool get _methodIsRepliable =>
|
|
method == EventMethod.request ||
|
|
method == EventMethod.add ||
|
|
method == EventMethod.counter;
|
|
|
|
bool userIsListedInParticipants(String ownEmailAddress) {
|
|
final participant = participants?.firstWhereOrNull((participant) =>
|
|
participant.mailto?.mailAddress.value == ownEmailAddress);
|
|
return participant != null;
|
|
}
|
|
|
|
bool userIsOrganizer(String ownEmailAddress) {
|
|
return organizer?.mailto?.value == ownEmailAddress;
|
|
}
|
|
|
|
bool isDisplayedWarningMessage(String ownerEmailAddress) {
|
|
return !userIsListedInParticipants(ownerEmailAddress) &&
|
|
!userIsOrganizer(ownerEmailAddress);
|
|
}
|
|
|
|
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 [];
|
|
}
|
|
}
|
|
|
|
bool isIMIPResponsesAvailable(List<String> listEmailAddressSender) {
|
|
final participationStatus = findAttendeeHasUpdatedStatus(
|
|
listEmailAddressSender,
|
|
)?.participationStatus?.value;
|
|
|
|
return method == EventMethod.reply &&
|
|
(participationStatus == acceptedParticipationStatus ||
|
|
participationStatus == tentativeParticipationStatus ||
|
|
participationStatus == declinedParticipationStatus);
|
|
}
|
|
|
|
CalendarEvent copyWith({
|
|
EventId? eventId,
|
|
String? title,
|
|
String? description,
|
|
DateTime? startDate,
|
|
DateTime? endDate,
|
|
UTCDate? startUtcDate,
|
|
UTCDate? endUtcDate,
|
|
CalendarDuration? duration,
|
|
String? timeZone,
|
|
String? location,
|
|
EventMethod? method,
|
|
CalendarSequence? sequence,
|
|
CalendarPrivacy? privacy,
|
|
CalendarPriority? priority,
|
|
CalendarFreeBusyStatus? freeBusyStatus,
|
|
CalendarEventStatus? status,
|
|
CalendarOrganizer? organizer,
|
|
List<CalendarAttendee>? participants,
|
|
CalendarExtensionFields? extensionFields,
|
|
List<RecurrenceRule>? recurrenceRules,
|
|
List<RecurrenceRule>? excludedCalendarEvents,
|
|
}) {
|
|
return CalendarEvent(
|
|
eventId: eventId ?? this.eventId,
|
|
title: title ?? this.title,
|
|
description: description ?? this.description,
|
|
startDate: startDate ?? this.startDate,
|
|
endDate: endDate ?? this.endDate,
|
|
startUtcDate: startUtcDate ?? this.startUtcDate,
|
|
endUtcDate: endUtcDate ?? this.endUtcDate,
|
|
duration: duration ?? this.duration,
|
|
timeZone: timeZone ?? this.timeZone,
|
|
location: location ?? this.location,
|
|
method: method ?? this.method,
|
|
sequence: sequence ?? this.sequence,
|
|
privacy: privacy ?? this.privacy,
|
|
priority: priority ?? this.priority,
|
|
freeBusyStatus: freeBusyStatus ?? this.freeBusyStatus,
|
|
status: status ?? this.status,
|
|
organizer: organizer ?? this.organizer,
|
|
participants: participants ?? this.participants,
|
|
extensionFields: extensionFields ?? this.extensionFields,
|
|
recurrenceRules: recurrenceRules ?? this.recurrenceRules,
|
|
excludedCalendarEvents: excludedCalendarEvents ?? this.excludedCalendarEvents,
|
|
);
|
|
}
|
|
|
|
String getMailToAttendeesEventTitle(AppLocalizations appLocalizations) {
|
|
return EmailUtils.applyPrefix(
|
|
subject: title ?? '',
|
|
defaultPrefix: EmailUtils.defaultReplyPrefix,
|
|
localizedPrefix: appLocalizations.prefix_reply_email,
|
|
);
|
|
}
|
|
} |