From 8b424a03be55cc368a0dd2fbe1c1cd9ae649634e Mon Sep 17 00:00:00 2001 From: Florent Azavant Date: Thu, 14 Nov 2024 15:13:56 +0100 Subject: [PATCH] TF3219 display limits of email recovery in a yellow banner and filter out invalid suggestions --- .../base/mixin/date_range_picker_mixin.dart | 2 +- .../email_recovery_controller.dart | 35 ++++++++++++++++++- .../model/email_recovery_field.dart | 16 +++++---- .../model/email_recovery_time_type.dart | 8 +++++ .../date_selection_field_web_widget.dart | 4 ++- .../email_recovery_form_desktop_builder.dart | 7 ++++ .../email_recovery_form_mobile_builder.dart | 5 +++ .../email_recovery_form_tablet_builder.dart | 7 ++++ .../presentation/widgets/limits_banner.dart | 33 +++++++++++++++++ lib/l10n/intl_messages.arb | 32 +---------------- lib/main/localizations/app_localizations.dart | 14 ++++++++ pubspec.lock | 8 +++++ pubspec.yaml | 2 ++ 13 files changed, 133 insertions(+), 40 deletions(-) create mode 100644 lib/features/email_recovery/presentation/widgets/limits_banner.dart diff --git a/lib/features/base/mixin/date_range_picker_mixin.dart b/lib/features/base/mixin/date_range_picker_mixin.dart index 10f746f815..70ad1744f2 100644 --- a/lib/features/base/mixin/date_range_picker_mixin.dart +++ b/lib/features/base/mixin/date_range_picker_mixin.dart @@ -22,7 +22,7 @@ mixin DateRangePickerMixin { last7daysTitle: AppLocalizations.of(context).last7Days, last30daysTitle: AppLocalizations.of(context).last30Days, last6monthsTitle: AppLocalizations.of(context).last6Months, - lastYearTitle: AppLocalizations.of(context).lastYears, + lastYearTitle: AppLocalizations.of(context).last1Year, initStartDate: initStartDate, initEndDate: initEndDate, autoClose: false, diff --git a/lib/features/email_recovery/presentation/email_recovery_controller.dart b/lib/features/email_recovery/presentation/email_recovery_controller.dart index d48991ddb2..2a850b4a6d 100644 --- a/lib/features/email_recovery/presentation/email_recovery_controller.dart +++ b/lib/features/email_recovery/presentation/email_recovery_controller.dart @@ -1,10 +1,14 @@ import 'package:core/presentation/utils/keyboard_utils.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; +import 'package:duration/duration.dart'; +import 'package:email_recovery/email_recovery/capability_deleted_messages_vault.dart'; import 'package:email_recovery/email_recovery/email_recovery_action.dart'; import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; 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/capability/capability_properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; @@ -34,7 +38,7 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { GetAutoCompleteInteractor? _getAutoCompleteInteractor; GetDeviceContactSuggestionsInteractor? _getDeviceContactSuggestionsInteractor; - final deletionDateFieldSelected = EmailRecoveryTimeType.last1Year.obs; + final deletionDateFieldSelected = EmailRecoveryTimeType.last7Days.obs; final receptionDateFieldSelected = EmailRecoveryTimeType.allTime.obs; final startDeletionDate = Rxn(); final endDeletionDate = Rxn(); @@ -348,6 +352,35 @@ class EmailRecoveryController extends BaseController with DateRangePickerMixin { popBack(); } + String getRestorationHorizonAsString() { + return ((arguments!.session + .props[0] as Map?) + ?[capabilityDeletedMessagesVault] + ?.props[0] as Map?) + ?['restorationHorizon'] + ?? "15 days"; + } + + Duration getRestorationHorizonAsDuration() { + String horizonWithCorrectFormat = getRestorationHorizonAsString() + .replaceAll(" days", "d") + .replaceAll(" day", "d") + .replaceAll(" hours", "h") + .replaceAll(" hour", "h") + .replaceAll(" minutes", "m") + .replaceAll(" minute", "m") + .replaceAll(" seconds", "s") + .replaceAll(" second", "s") + .replaceAll(" milliseconds", "ms") + .replaceAll(" millisecond", "ms"); + + return parseDuration(horizonWithCorrectFormat, separator: ' '); + } + + DateTime getRestorationHorizonAsDateTime() { + return DateTime.now().subtract(getRestorationHorizonAsDuration()); + } + @override void dispose() { focusManager.subjectFieldFocusNode.removeListener(_onSubjectFieldFocusChanged); diff --git a/lib/features/email_recovery/presentation/model/email_recovery_field.dart b/lib/features/email_recovery/presentation/model/email_recovery_field.dart index fb98b11566..2557aeebcb 100644 --- a/lib/features/email_recovery/presentation/model/email_recovery_field.dart +++ b/lib/features/email_recovery/presentation/model/email_recovery_field.dart @@ -28,7 +28,7 @@ enum EmailRecoveryField { String getHintText(BuildContext context) { switch (this) { case EmailRecoveryField.deletionDate: - return AppLocalizations.of(context).last1Year; + return AppLocalizations.of(context).last7Days; case EmailRecoveryField.receptionDate: return AppLocalizations.of(context).allTime; case EmailRecoveryField.subject: @@ -40,16 +40,20 @@ enum EmailRecoveryField { } } - List getSupportedTimeTypes() { + List getSupportedTimeTypes(DateTime restorationHorizon) { switch (this) { case EmailRecoveryField.deletionDate: - return [ - EmailRecoveryTimeType.last1Year, + final supportedTypes = [ EmailRecoveryTimeType.last7Days, + EmailRecoveryTimeType.last15Days, EmailRecoveryTimeType.last30Days, EmailRecoveryTimeType.last6Months, - EmailRecoveryTimeType.customRange, - ]; + EmailRecoveryTimeType.last1Year, + ].where((type) => restorationHorizon + .subtract(const Duration(seconds:2)) // to allow "15 days" if restorationHorizon is exactly 15 days + .isBefore(type.toOldestUTCDate()!.value)).toList(); + + return [...supportedTypes, EmailRecoveryTimeType.customRange]; case EmailRecoveryField.receptionDate: return [ EmailRecoveryTimeType.allTime, diff --git a/lib/features/email_recovery/presentation/model/email_recovery_time_type.dart b/lib/features/email_recovery/presentation/model/email_recovery_time_type.dart index a9f553100b..632da6bb29 100644 --- a/lib/features/email_recovery/presentation/model/email_recovery_time_type.dart +++ b/lib/features/email_recovery/presentation/model/email_recovery_time_type.dart @@ -6,6 +6,7 @@ import 'package:jmap_dart_client/jmap/core/utc_date.dart'; enum EmailRecoveryTimeType { allTime, last7Days, + last15Days, last30Days, last6Months, last1Year, @@ -18,6 +19,8 @@ enum EmailRecoveryTimeType { return AppLocalizations.of(context).allTime; case EmailRecoveryTimeType.last7Days: return AppLocalizations.of(context).last7Days; + case EmailRecoveryTimeType.last15Days: + return AppLocalizations.of(context).last15Days; case EmailRecoveryTimeType.last30Days: return AppLocalizations.of(context).last30Days; case EmailRecoveryTimeType.last6Months: @@ -47,6 +50,10 @@ enum EmailRecoveryTimeType { final today = DateTime.now(); final last7Days = today.subtract(const Duration(days: 7)); return last7Days.toUTCDate(); + case EmailRecoveryTimeType.last15Days: + final today = DateTime.now(); + final last15Days = today.subtract(const Duration(days: 15)); + return last15Days.toUTCDate(); case EmailRecoveryTimeType.last30Days: final today = DateTime.now(); final last30Days = today.subtract(const Duration(days: 30)); @@ -68,6 +75,7 @@ enum EmailRecoveryTimeType { UTCDate? toLatestUTCDate() { switch(this) { case EmailRecoveryTimeType.last7Days: + case EmailRecoveryTimeType.last15Days: case EmailRecoveryTimeType.last30Days: case EmailRecoveryTimeType.last6Months: case EmailRecoveryTimeType.last1Year: diff --git a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart index d5e8acd028..17ce469618 100644 --- a/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart +++ b/lib/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart @@ -18,6 +18,7 @@ class DateSelectionFieldWebWidget extends StatelessWidget { final EmailRecoveryTimeType? recoveryTimeSelected; final OnRecoveryTimeSelected? onRecoveryTimeSelected; final VoidCallback? onTapCalendar; + final DateTime restorationHorizon; const DateSelectionFieldWebWidget({ super.key, @@ -29,6 +30,7 @@ class DateSelectionFieldWebWidget extends StatelessWidget { this.recoveryTimeSelected, this.onRecoveryTimeSelected, this.onTapCalendar, + required this.restorationHorizon }); @override @@ -54,7 +56,7 @@ class DateSelectionFieldWebWidget extends StatelessWidget { endDate: endDate, recoveryTimeSelected: recoveryTimeSelected, onRecoveryTimeSelected: onRecoveryTimeSelected, - items: field.getSupportedTimeTypes(), + items: field.getSupportedTimeTypes(restorationHorizon), ), ), const SizedBox(width: DateSelectionFieldStyles.icCalenderSpace), diff --git a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart index bf12da47f2..0e7567da3a 100644 --- a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart +++ b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_desktop_builder.dart @@ -7,6 +7,7 @@ import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_r import 'package:tmail_ui_user/features/email_recovery/presentation/styles/email_recovery_form_styles.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/limits_banner.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/list_button_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart'; @@ -41,6 +42,10 @@ class EmailRecoveryFormDesktopBuilder extends GetWidget child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + LimitsBanner( + bannerContent: AppLocalizations.of(context).recoverDeletedMessagesBannerContent(controller.getRestorationHorizonAsString()), + ), + const SizedBox(height: 16.0), Obx(() => DateSelectionFieldWebWidget( field: EmailRecoveryField.deletionDate, imagePaths: controller.imagePaths, @@ -50,6 +55,7 @@ class EmailRecoveryFormDesktopBuilder extends GetWidget recoveryTimeSelected: controller.deletionDateFieldSelected.value, onTapCalendar: () => controller.onSelectDeletionDateRange(context), onRecoveryTimeSelected: (type) => controller.onDeletionDateTypeSelected(context, type), + restorationHorizon: controller.getRestorationHorizonAsDateTime(), )), Obx(() => DateSelectionFieldWebWidget( field: EmailRecoveryField.receptionDate, @@ -60,6 +66,7 @@ class EmailRecoveryFormDesktopBuilder extends GetWidget recoveryTimeSelected: controller.receptionDateFieldSelected.value, onTapCalendar: () => controller.onSelectReceptionDateRange(context), onRecoveryTimeSelected: (type) => controller.onReceptionDateTypeSelected(context, type), + restorationHorizon: controller.getRestorationHorizonAsDateTime(), )), TextInputFieldWidget( field: EmailRecoveryField.subject, diff --git a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart index 8abd5307ea..318a312a58 100644 --- a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart +++ b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_mobile_builder.dart @@ -7,6 +7,7 @@ import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_r import 'package:tmail_ui_user/features/email_recovery/presentation/styles/email_recovery_form_styles.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_mobile_widget.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/limits_banner.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/list_button_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart'; @@ -41,6 +42,10 @@ class EmailRecoveryFormMobileBuilder extends GetWidget child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + LimitsBanner( + bannerContent: AppLocalizations.of(context).recoverDeletedMessagesBannerContent(controller.getRestorationHorizonAsString()), + ), + const SizedBox(height: 16.0), Obx(() => DateSelectionFieldMobileWidget( field: EmailRecoveryField.deletionDate, recoveryTimeSelected: controller.deletionDateFieldSelected.value, diff --git a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart index 0ad9f9170b..5d300845c2 100644 --- a/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart +++ b/lib/features/email_recovery/presentation/widgets/email_recovery_form/email_recovery_form_tablet_builder.dart @@ -7,6 +7,7 @@ import 'package:tmail_ui_user/features/email_recovery/presentation/model/email_r import 'package:tmail_ui_user/features/email_recovery/presentation/styles/email_recovery_form_styles.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/check_box_has_attachment_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/date_selection_field/date_selection_field_web_widget.dart'; +import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/limits_banner.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/list_button_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_suggestion_widget.dart'; import 'package:tmail_ui_user/features/email_recovery/presentation/widgets/text_input_field/text_input_field_widget.dart'; @@ -41,6 +42,10 @@ class EmailRecoveryFormTabletBuilder extends GetWidget child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + LimitsBanner( + bannerContent: AppLocalizations.of(context).recoverDeletedMessagesBannerContent(controller.getRestorationHorizonAsString()), + ), + const SizedBox(height: 16.0), Obx(() => DateSelectionFieldWebWidget( field: EmailRecoveryField.deletionDate, imagePaths: controller.imagePaths, @@ -50,6 +55,7 @@ class EmailRecoveryFormTabletBuilder extends GetWidget recoveryTimeSelected: controller.deletionDateFieldSelected.value, onTapCalendar: () => controller.onSelectDeletionDateRange(context), onRecoveryTimeSelected: (type) => controller.onDeletionDateTypeSelected(context, type), + restorationHorizon: controller.getRestorationHorizonAsDateTime(), )), Obx(() => DateSelectionFieldWebWidget( field: EmailRecoveryField.receptionDate, @@ -60,6 +66,7 @@ class EmailRecoveryFormTabletBuilder extends GetWidget recoveryTimeSelected: controller.receptionDateFieldSelected.value, onTapCalendar: () => controller.onSelectReceptionDateRange(context), onRecoveryTimeSelected: (type) => controller.onReceptionDateTypeSelected(context, type), + restorationHorizon: controller.getRestorationHorizonAsDateTime(), )), TextInputFieldWidget( field: EmailRecoveryField.subject, diff --git a/lib/features/email_recovery/presentation/widgets/limits_banner.dart b/lib/features/email_recovery/presentation/widgets/limits_banner.dart new file mode 100644 index 0000000000..562ff40387 --- /dev/null +++ b/lib/features/email_recovery/presentation/widgets/limits_banner.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class LimitsBanner extends StatelessWidget { + final String bannerContent; + + const LimitsBanner({ + super.key, + required this.bannerContent, + }); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0), + decoration: BoxDecoration( + color: Colors.yellow[400], + borderRadius: BorderRadius.circular(8.0), + ), + child: Center( + child: Text( + bannerContent, + style: const TextStyle( + fontSize: 16, + color: Colors.black, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index d4d794eb14..9e9ddc5275 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-10-31T13:18:32.336494", + "@@last_modified": "2024-11-18T11:47:38.788034", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -4041,35 +4041,5 @@ "type": "text", "placeholders_order": [], "placeholders": {} - }, - "descriptionWelcomeTo": "The new Open Source standard for\n secure professional e-mail", - "@descriptionWelcomeTo": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "createTwakeId": "Create Twake ID", - "@createTwakeId": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "useCompanyServer": "Use company server", - "@useCompanyServer": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "sigInSaasFailed": "Login failed. Please check again.", - "@sigInSaasFailed": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "createTwakeIdFailed": "Create Twake Id failed. Please check again.", - "@createTwakeIdFailed": { - "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 9dac836f6b..bf48809f06 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -1374,6 +1374,12 @@ class AppLocalizations { name: 'last7Days'); } + String get last15Days { + return Intl.message( + 'Last 15 days', + name: 'last15Days'); + } + String get fromMe { return Intl.message( 'From me', @@ -3678,6 +3684,14 @@ class AppLocalizations { ); } + String recoverDeletedMessagesBannerContent(String period) { + return Intl.message( + 'You can recover messages deleted during the past $period', + name: 'recoverDeletedMessages', + args: [period] + ); + } + String get deletionDate { return Intl.message( 'Deletion date', diff --git a/pubspec.lock b/pubspec.lock index ba1806e676..e92181aecb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -433,6 +433,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + duration: + dependency: "direct main" + description: + name: duration + sha256: "13e5d20723c9c1dde8fb318cf86716d10ce294734e81e44ae1a817f3ae714501" + url: "https://pub.dev" + source: hosted + version: "4.0.3" email_recovery: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index bf0b4a3a2c..3c96165502 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -259,6 +259,8 @@ dependencies: flutter_web_auth_2: 3.1.1 + duration: 4.0.3 + dev_dependencies: flutter_test: sdk: flutter