TF-3578 Calendar event show free busy status
This commit is contained in:
@@ -653,7 +653,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ extension AppColor on Color {
|
|||||||
static const m3Neutral70 = Color(0xFFAEAAAE);
|
static const m3Neutral70 = Color(0xFFAEAAAE);
|
||||||
static const grayBackgroundColor = Color(0xFFF3F6F9);
|
static const grayBackgroundColor = Color(0xFFF3F6F9);
|
||||||
static const m3SurfaceBackground = Color(0xFF1C1B1F);
|
static const m3SurfaceBackground = Color(0xFF1C1B1F);
|
||||||
|
static const warningColor = Color(0xFFFFC107);
|
||||||
|
|
||||||
static const mapGradientColor = [
|
static const mapGradientColor = [
|
||||||
[Color(0xFF21D4FD), Color(0xFFB721FF)],
|
[Color(0xFF21D4FD), Color(0xFFB721FF)],
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
+1
-1
@@ -296,7 +296,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:jmap_dart_client/http/http_client.dart';
|
import 'package:jmap_dart_client/http/http_client.dart';
|
||||||
import 'package:jmap_dart_client/jmap/account_id.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/id.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/request/request_invocation.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/response/response_object.dart';
|
||||||
import 'package:jmap_dart_client/jmap/jmap_request.dart';
|
import 'package:jmap_dart_client/jmap/jmap_request.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/calendar/attendance/get_calendar_event_attendance_method.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/mail/calendar/attendance/get_calendar_event_attendance_response.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/calendar/parse/calendar_event_parse_method.dart';
|
import 'package:jmap_dart_client/jmap/mail/calendar/parse/calendar_event_parse_method.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/calendar/parse/calendar_event_parse_response.dart';
|
import 'package:jmap_dart_client/jmap/mail/calendar/parse/calendar_event_parse_response.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_method.dart';
|
import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_method.dart';
|
||||||
@@ -23,8 +28,20 @@ class CalendarEventAPI {
|
|||||||
|
|
||||||
Future<List<BlobCalendarEvent>> parse(AccountId accountId, Set<Id> blobIds) async {
|
Future<List<BlobCalendarEvent>> parse(AccountId accountId, Set<Id> blobIds) async {
|
||||||
final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
|
final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
|
||||||
|
|
||||||
|
// Parse
|
||||||
final calendarEventParseMethod = CalendarEventParseMethod(accountId, blobIds);
|
final calendarEventParseMethod = CalendarEventParseMethod(accountId, blobIds);
|
||||||
final calendarEventParseInvocation = requestBuilder.invocation(calendarEventParseMethod);
|
final calendarEventParseInvocation = requestBuilder.invocation(calendarEventParseMethod);
|
||||||
|
|
||||||
|
// Free/Busy query
|
||||||
|
final calendarEventAttendanceGetMethod = GetCalendarEventAttendanceMethod(
|
||||||
|
accountId,
|
||||||
|
blobIds.toList(),
|
||||||
|
);
|
||||||
|
final calendarEventAttendanceGetInvocation = requestBuilder.invocation(
|
||||||
|
calendarEventAttendanceGetMethod,
|
||||||
|
);
|
||||||
|
|
||||||
final response = await (requestBuilder
|
final response = await (requestBuilder
|
||||||
..usings(calendarEventParseMethod.requiredCapabilities))
|
..usings(calendarEventParseMethod.requiredCapabilities))
|
||||||
.build()
|
.build()
|
||||||
@@ -33,11 +50,24 @@ class CalendarEventAPI {
|
|||||||
final calendarEventParseResponse = response.parse<CalendarEventParseResponse>(
|
final calendarEventParseResponse = response.parse<CalendarEventParseResponse>(
|
||||||
calendarEventParseInvocation.methodCallId,
|
calendarEventParseInvocation.methodCallId,
|
||||||
CalendarEventParseResponse.deserialize);
|
CalendarEventParseResponse.deserialize);
|
||||||
|
final calendarEventAttendanceGetResponse = _parseCalendarEventAttendance(
|
||||||
|
response,
|
||||||
|
calendarEventAttendanceGetInvocation.methodCallId,
|
||||||
|
);
|
||||||
|
final calendarBlobIdStatusMap = Map.fromEntries(
|
||||||
|
(calendarEventAttendanceGetResponse?.list ?? []).map(
|
||||||
|
(calendarEventAttendance) => MapEntry(
|
||||||
|
calendarEventAttendance.blobId,
|
||||||
|
calendarEventAttendance.isFree,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (calendarEventParseResponse?.parsed?.isNotEmpty == true) {
|
if (calendarEventParseResponse?.parsed?.isNotEmpty == true) {
|
||||||
return calendarEventParseResponse!.parsed!.entries
|
return calendarEventParseResponse!.parsed!.entries
|
||||||
.map((entry) => BlobCalendarEvent(
|
.map((entry) => BlobCalendarEvent(
|
||||||
blobId: entry.key,
|
blobId: entry.key,
|
||||||
|
isFree: calendarBlobIdStatusMap[entry.key] ?? true,
|
||||||
calendarEventList: entry.value))
|
calendarEventList: entry.value))
|
||||||
.toList();
|
.toList();
|
||||||
} else if (calendarEventParseResponse?.notParsable?.isNotEmpty == true) {
|
} else if (calendarEventParseResponse?.notParsable?.isNotEmpty == true) {
|
||||||
@@ -49,6 +79,20 @@ class CalendarEventAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetCalendarEventAttendanceResponse? _parseCalendarEventAttendance(
|
||||||
|
ResponseObject response,
|
||||||
|
MethodCallId methodCallId,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
return response.parse<GetCalendarEventAttendanceResponse>(
|
||||||
|
methodCallId,
|
||||||
|
GetCalendarEventAttendanceResponse.deserialize);
|
||||||
|
} catch (e) {
|
||||||
|
logError('CalendarEventAPI.parse free/busy query error: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<CalendarEventAcceptResponse> acceptEventInvitation(
|
Future<CalendarEventAcceptResponse> acceptEventInvitation(
|
||||||
AccountId accountId,
|
AccountId accountId,
|
||||||
Set<Id> blobIds,
|
Set<Id> blobIds,
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class CalendarEventRepositoryImpl extends CalendarEventRepository {
|
|||||||
calendarEventList: await Future.wait(blobCalendarEvent.calendarEventList.map((calendarEvent) {
|
calendarEventList: await Future.wait(blobCalendarEvent.calendarEventList.map((calendarEvent) {
|
||||||
return _transformCalendarEventDescription(calendarEvent, transformConfiguration);
|
return _transformCalendarEventDescription(calendarEvent, transformConfiguration);
|
||||||
})),
|
})),
|
||||||
|
isFree: blobCalendarEvent.isFree,
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
|
|
||||||
CalendarEvent? get calendarEvent => blobCalendarEvent.value?.calendarEventList.firstOrNull;
|
CalendarEvent? get calendarEvent => blobCalendarEvent.value?.calendarEventList.firstOrNull;
|
||||||
Id? get _displayingEventBlobId => blobCalendarEvent.value?.blobId;
|
Id? get _displayingEventBlobId => blobCalendarEvent.value?.blobId;
|
||||||
|
bool get isCalendarEventFree => blobCalendarEvent.value?.isFree ?? true;
|
||||||
|
|
||||||
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
||||||
|
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
presentationEmail: controller.currentEmail,
|
presentationEmail: controller.currentEmail,
|
||||||
onMailtoAttendeesAction: controller.handleMailToAttendees,
|
onMailtoAttendeesAction: controller.handleMailToAttendees,
|
||||||
openEmailAddressDetailAction: controller.openEmailAddressDialog,
|
openEmailAddressDetailAction: controller.openEmailAddressDialog,
|
||||||
|
isFree: controller.isCalendarEventFree,
|
||||||
)),
|
)),
|
||||||
if (_validateDisplayEventActionBanner(
|
if (_validateDisplayEventActionBanner(
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -5,9 +5,14 @@ import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart';
|
|||||||
class BlobCalendarEvent with EquatableMixin {
|
class BlobCalendarEvent with EquatableMixin {
|
||||||
final Id blobId;
|
final Id blobId;
|
||||||
final List<CalendarEvent> calendarEventList;
|
final List<CalendarEvent> calendarEventList;
|
||||||
|
final bool isFree;
|
||||||
|
|
||||||
BlobCalendarEvent({required this.blobId, required this.calendarEventList});
|
BlobCalendarEvent({
|
||||||
|
required this.blobId,
|
||||||
|
required this.calendarEventList,
|
||||||
|
this.isFree = true,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [blobId, calendarEventList];
|
List<Object?> get props => [blobId, calendarEventList, isFree];
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
|
|||||||
class EventTimeInformationWidgetStyles {
|
class EventTimeInformationWidgetStyles {
|
||||||
static const double maxWidth = 100;
|
static const double maxWidth = 100;
|
||||||
static const double textSize = 16;
|
static const double textSize = 16;
|
||||||
|
static const double horizontalSpacing = 8;
|
||||||
static const Color labelColor = AppColor.colorSubTitleEventActionText;
|
static const Color labelColor = AppColor.colorSubTitleEventActionText;
|
||||||
static const Color valueColor = Colors.black;
|
static const Color valueColor = Colors.black;
|
||||||
|
static const Color conflictColor = AppColor.warningColor;
|
||||||
}
|
}
|
||||||
+6
-1
@@ -32,6 +32,7 @@ class CalendarEventInformationWidget extends StatelessWidget {
|
|||||||
final PresentationEmail? presentationEmail;
|
final PresentationEmail? presentationEmail;
|
||||||
final OnMailtoAttendeesAction? onMailtoAttendeesAction;
|
final OnMailtoAttendeesAction? onMailtoAttendeesAction;
|
||||||
final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction;
|
final OnOpenEmailAddressDetailAction? openEmailAddressDetailAction;
|
||||||
|
final bool isFree;
|
||||||
|
|
||||||
final _responsiveUtils = Get.find<ResponsiveUtils>();
|
final _responsiveUtils = Get.find<ResponsiveUtils>();
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ class CalendarEventInformationWidget extends StatelessWidget {
|
|||||||
required this.calendarEvent,
|
required this.calendarEvent,
|
||||||
required this.onCalendarEventReplyActionClick,
|
required this.onCalendarEventReplyActionClick,
|
||||||
required this.calendarEventReplying,
|
required this.calendarEventReplying,
|
||||||
|
required this.isFree,
|
||||||
this.onOpenNewTabAction,
|
this.onOpenNewTabAction,
|
||||||
this.onOpenComposerAction,
|
this.onOpenComposerAction,
|
||||||
this.presentationEmail,
|
this.presentationEmail,
|
||||||
@@ -250,7 +252,10 @@ class CalendarEventInformationWidget extends StatelessWidget {
|
|||||||
if (dateTimeEvent.isNotEmpty) {
|
if (dateTimeEvent.isNotEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: CalendarEventInformationWidgetStyles.fieldTopPadding),
|
padding: const EdgeInsets.only(top: CalendarEventInformationWidgetStyles.fieldTopPadding),
|
||||||
child: EventTimeInformationWidget(timeEvent: dateTimeEvent),
|
child: EventTimeInformationWidget(
|
||||||
|
timeEvent: dateTimeEvent,
|
||||||
|
isFree: isFree,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
|
|||||||
+25
-4
@@ -1,7 +1,10 @@
|
|||||||
|
|
||||||
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
import 'package:core/presentation/utils/responsive_utils.dart';
|
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||||
import 'package:core/presentation/utils/style_utils.dart';
|
import 'package:core/presentation/utils/style_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/styles/event_time_information_widget_styles.dart';
|
import 'package:tmail_ui_user/features/email/presentation/styles/event_time_information_widget_styles.dart';
|
||||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
@@ -9,17 +12,21 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
|||||||
class EventTimeInformationWidget extends StatelessWidget {
|
class EventTimeInformationWidget extends StatelessWidget {
|
||||||
|
|
||||||
final String timeEvent;
|
final String timeEvent;
|
||||||
|
final bool isFree;
|
||||||
|
|
||||||
const EventTimeInformationWidget({
|
const EventTimeInformationWidget({
|
||||||
super.key,
|
super.key,
|
||||||
required this.timeEvent
|
required this.timeEvent,
|
||||||
|
required this.isFree,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final responsiveUtils = Get.find<ResponsiveUtils>();
|
final responsiveUtils = Get.find<ResponsiveUtils>();
|
||||||
|
final imagePaths = Get.find<ImagePaths>();
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: EventTimeInformationWidgetStyles.maxWidth,
|
width: EventTimeInformationWidgetStyles.maxWidth,
|
||||||
@@ -32,7 +39,7 @@ class EventTimeInformationWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(child: Text(
|
Flexible(child: Text(
|
||||||
timeEvent,
|
timeEvent,
|
||||||
overflow: responsiveUtils.isPortraitMobile(context) ? null : CommonTextStyle.defaultTextOverFlow,
|
overflow: responsiveUtils.isPortraitMobile(context) ? null : CommonTextStyle.defaultTextOverFlow,
|
||||||
softWrap: responsiveUtils.isPortraitMobile(context) ? null : CommonTextStyle.defaultSoftWrap,
|
softWrap: responsiveUtils.isPortraitMobile(context) ? null : CommonTextStyle.defaultSoftWrap,
|
||||||
@@ -42,7 +49,21 @@ class EventTimeInformationWidget extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: EventTimeInformationWidgetStyles.valueColor
|
color: EventTimeInformationWidgetStyles.valueColor
|
||||||
),
|
),
|
||||||
))
|
)),
|
||||||
|
if (!isFree)
|
||||||
|
...[
|
||||||
|
const SizedBox(width: EventTimeInformationWidgetStyles.horizontalSpacing),
|
||||||
|
Tooltip(
|
||||||
|
message: AppLocalizations.of(context).youHaveAnotherEventAtThatSameTime,
|
||||||
|
child: SvgPicture.asset(
|
||||||
|
imagePaths.icError,
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
colorFilter: EventTimeInformationWidgetStyles.conflictColor.asFilter(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4370,6 +4370,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"youHaveAnotherEventAtThatSameTime": "You have another event at that same time",
|
||||||
|
"@youHaveAnotherEventAtThatSameTime": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"exitFullscreen": "Exit fullscreen",
|
"exitFullscreen": "Exit fullscreen",
|
||||||
"@exitFullscreen": {
|
"@exitFullscreen": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -4591,6 +4591,13 @@ class AppLocalizations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get youHaveAnotherEventAtThatSameTime {
|
||||||
|
return Intl.message(
|
||||||
|
'You have another event at that same time',
|
||||||
|
name: 'youHaveAnotherEventAtThatSameTime',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String get exitFullscreen {
|
String get exitFullscreen {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'Exit fullscreen',
|
'Exit fullscreen',
|
||||||
|
|||||||
+1
-1
@@ -645,7 +645,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
+1
-1
@@ -1325,7 +1325,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "00b8c073e9e8b6f1caa831c400a5c31e56424c08"
|
resolved-ref: "708658c13e013882fde91c5afba886bd67641f8f"
|
||||||
url: "https://github.com/linagora/jmap-dart-client.git"
|
url: "https://github.com/linagora/jmap-dart-client.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.3"
|
version: "0.3.3"
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:jmap_dart_client/http/http_client.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
|
import 'package:mockito/annotations.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:model/extensions/account_id_extensions.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/data/network/calendar_event_api.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/domain/exceptions/calendar_event_exceptions.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/model/blob_calendar_event.dart';
|
||||||
|
|
||||||
|
import '../../../../fixtures/account_fixtures.dart';
|
||||||
|
import 'calendar_event_api_test.mocks.dart';
|
||||||
|
|
||||||
|
@GenerateNiceMocks([MockSpec<HttpClient>()])
|
||||||
|
void main() {
|
||||||
|
group('CalendarEventAPI::test', () {
|
||||||
|
late HttpClient httpClient;
|
||||||
|
late CalendarEventAPI calendarEventAPI;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
httpClient = MockHttpClient();
|
||||||
|
calendarEventAPI = CalendarEventAPI(httpClient);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('parse::test', () {
|
||||||
|
test('SHOULD return list of BlobCalendarEvent WHEN parse method success', () async {
|
||||||
|
final blobIds = {Id('blob_1'), Id('blob_2')};
|
||||||
|
|
||||||
|
when(httpClient.post(
|
||||||
|
'',
|
||||||
|
data: anyNamed('data'),
|
||||||
|
cancelToken: anyNamed('cancelToken'),
|
||||||
|
)).thenAnswer((_) async => {
|
||||||
|
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
|
||||||
|
"methodResponses": [
|
||||||
|
[
|
||||||
|
"CalendarEvent/parse",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"parsed": {
|
||||||
|
"blob_1": [
|
||||||
|
{
|
||||||
|
"uid": "event1",
|
||||||
|
"title": "Meeting 1",
|
||||||
|
"start": "2024-03-20T10:00:00Z",
|
||||||
|
"duration": "PT1H"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blob_2": [
|
||||||
|
{
|
||||||
|
"uid": "event2",
|
||||||
|
"title": "Meeting 2",
|
||||||
|
"start": "2024-03-21T14:00:00Z",
|
||||||
|
"duration": "PT2H"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"CalendarEventAttendance/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"blobId": "blob_1",
|
||||||
|
"isFree": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"blobId": "blob_2",
|
||||||
|
"isFree": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
final result = await calendarEventAPI.parse(
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
blobIds
|
||||||
|
);
|
||||||
|
|
||||||
|
verify(httpClient.post(
|
||||||
|
'',
|
||||||
|
data: {
|
||||||
|
"using": [
|
||||||
|
"urn:ietf:params:jmap:core",
|
||||||
|
"com:linagora:params:calendar:event",
|
||||||
|
],
|
||||||
|
"methodCalls": [
|
||||||
|
[
|
||||||
|
"CalendarEvent/parse",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"blobIds": ["blob_1", "blob_2"]
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"CalendarEventAttendance/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"blobIds": ["blob_1", "blob_2"]
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
cancelToken: anyNamed('cancelToken'),
|
||||||
|
)).called(1);
|
||||||
|
|
||||||
|
expect(result, isA<List<BlobCalendarEvent>>());
|
||||||
|
expect(result.length, 2);
|
||||||
|
expect(result[0].blobId.value, 'blob_1');
|
||||||
|
expect(result[0].isFree, true);
|
||||||
|
expect(result[1].blobId.value, 'blob_2');
|
||||||
|
expect(result[1].isFree, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD throw NotParsableCalendarEventException WHEN response has notParsable', () async {
|
||||||
|
final blobIds = {Id('blob_1')};
|
||||||
|
|
||||||
|
when(httpClient.post(
|
||||||
|
'',
|
||||||
|
data: anyNamed('data'),
|
||||||
|
cancelToken: anyNamed('cancelToken'),
|
||||||
|
)).thenAnswer((_) async => {
|
||||||
|
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
|
||||||
|
"methodResponses": [
|
||||||
|
[
|
||||||
|
"CalendarEvent/parse",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"notParsable": ["blob_1"]
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"CalendarEventAttendance/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => calendarEventAPI.parse(AccountFixtures.aliceAccountId, blobIds),
|
||||||
|
throwsA(isA<NotParsableCalendarEventException>())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD throw NotFoundCalendarEventException WHEN response has notFound', () async {
|
||||||
|
final blobIds = {Id('blob_1')};
|
||||||
|
|
||||||
|
when(httpClient.post(
|
||||||
|
'',
|
||||||
|
data: anyNamed('data'),
|
||||||
|
cancelToken: anyNamed('cancelToken'),
|
||||||
|
)).thenAnswer((_) async => {
|
||||||
|
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
|
||||||
|
"methodResponses": [
|
||||||
|
[
|
||||||
|
"CalendarEvent/parse",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"notFound": ["blob_1"]
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"CalendarEventAttendance/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => calendarEventAPI.parse(AccountFixtures.aliceAccountId, blobIds),
|
||||||
|
throwsA(isA<NotFoundCalendarEventException>())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD throw NotParsableCalendarEventException WHEN response is empty', () async {
|
||||||
|
final blobIds = {Id('blob_1')};
|
||||||
|
|
||||||
|
when(httpClient.post(
|
||||||
|
'',
|
||||||
|
data: anyNamed('data'),
|
||||||
|
cancelToken: anyNamed('cancelToken'),
|
||||||
|
)).thenAnswer((_) async => {
|
||||||
|
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
|
||||||
|
"methodResponses": [
|
||||||
|
[
|
||||||
|
"CalendarEvent/parse",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"CalendarEventAttendance/get",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"list": []
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
() => calendarEventAPI.parse(AccountFixtures.aliceAccountId, blobIds),
|
||||||
|
throwsA(isA<NotParsableCalendarEventException>())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SHOULD return calendar events with default isFree value WHEN CalendarEventAttendance/get returns error', () async {
|
||||||
|
final blobIds = {Id('blob_1')};
|
||||||
|
|
||||||
|
when(httpClient.post(
|
||||||
|
'',
|
||||||
|
data: anyNamed('data'),
|
||||||
|
cancelToken: anyNamed('cancelToken'),
|
||||||
|
)).thenAnswer((_) async => {
|
||||||
|
"sessionState": "2c9f1b12-b35a-43e6-9af2-0106fb53a943",
|
||||||
|
"methodResponses": [
|
||||||
|
[
|
||||||
|
"CalendarEvent/parse",
|
||||||
|
{
|
||||||
|
"accountId": AccountFixtures.aliceAccountId.asString,
|
||||||
|
"parsed": {
|
||||||
|
"blob_1": [
|
||||||
|
{
|
||||||
|
"uid": "event1",
|
||||||
|
"title": "Meeting 1",
|
||||||
|
"start": "2024-03-20T10:00:00Z",
|
||||||
|
"duration": "PT1H"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"c0"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"type": "invalidArguments",
|
||||||
|
"description": "Invalid blobIds"
|
||||||
|
},
|
||||||
|
"c1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
final result = await calendarEventAPI.parse(
|
||||||
|
AccountFixtures.aliceAccountId,
|
||||||
|
blobIds
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, isA<List<BlobCalendarEvent>>());
|
||||||
|
expect(result.length, 1);
|
||||||
|
expect(result[0].blobId.value, 'blob_1');
|
||||||
|
expect(result[0].isFree, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/event_time_information_widget.dart';
|
||||||
|
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
|
||||||
|
import '../../../../../fixtures/widget_fixtures.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
Get.put<ResponsiveUtils>(ResponsiveUtils());
|
||||||
|
Get.put<ImagePaths>(ImagePaths());
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
Get.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('EventTimeInformationWidget tests', () {
|
||||||
|
Widget makeTestableWidget({
|
||||||
|
required String timeEvent,
|
||||||
|
required bool isFree,
|
||||||
|
}) {
|
||||||
|
return WidgetFixtures.makeTestableWidget(
|
||||||
|
child: EventTimeInformationWidget(
|
||||||
|
timeEvent: timeEvent,
|
||||||
|
isFree: isFree,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('should show error icon with tooltip when isFree is false', (tester) async {
|
||||||
|
// arrange
|
||||||
|
final widget = makeTestableWidget(
|
||||||
|
timeEvent: '10:00 AM - 11:00 AM',
|
||||||
|
isFree: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
await tester.pumpWidget(widget);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// assert
|
||||||
|
final svgFinder = find.byWidgetPredicate((widget) {
|
||||||
|
return widget is SvgPicture
|
||||||
|
&& (widget.bytesLoader as SvgAssetLoader).assetName == Get.find<ImagePaths>().icError;
|
||||||
|
});
|
||||||
|
expect(svgFinder, findsOneWidget);
|
||||||
|
expect(find.byType(Tooltip), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('should not show error icon when isFree is true', (tester) async {
|
||||||
|
// arrange
|
||||||
|
final widget = makeTestableWidget(
|
||||||
|
timeEvent: '10:00 AM - 11:00 AM',
|
||||||
|
isFree: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
await tester.pumpWidget(widget);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(find.byType(SvgPicture), findsNothing);
|
||||||
|
expect(find.byType(Tooltip), findsNothing);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user