From 6acf70e61b0eff7a2c66f0708394c7d3817dca29 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 19 Aug 2022 13:28:27 +0700 Subject: [PATCH] TF-832 Implement delete multiple recipient in forwarding --- .../extensions/tmail_forward_extension.dart | 12 ++ .../forward/forward_controller.dart | 104 ++++++++++++++++-- .../presentation/forward/forward_view.dart | 3 +- .../widgets/email_forward_item_widget.dart | 90 ++++++++++----- .../email_forward_item_widget_for_web.dart | 97 ++++++++++++++++ .../widgets/list_email_forward_widget.dart | 82 ++++++++++---- .../presentation/model/recipient_forward.dart | 35 ++++++ lib/l10n/intl_messages.arb | 8 +- lib/main/localizations/app_localizations.dart | 7 ++ 9 files changed, 377 insertions(+), 61 deletions(-) create mode 100644 lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart create mode 100644 lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart create mode 100644 lib/features/manage_account/presentation/model/recipient_forward.dart diff --git a/lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart b/lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart new file mode 100644 index 000000000..134250fb2 --- /dev/null +++ b/lib/features/manage_account/presentation/extensions/tmail_forward_extension.dart @@ -0,0 +1,12 @@ + +import 'package:forward/forward/tmail_forward.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; + +extension TMailForwardExtension on TMailForward { + + List get listRecipientForward { + return forwards + .map((emailAddress) => RecipientForward(emailAddress)) + .toList(); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/forward/forward_controller.dart b/lib/features/manage_account/presentation/forward/forward_controller.dart index 07736569b..3b96488a5 100644 --- a/lib/features/manage_account/presentation/forward/forward_controller.dart +++ b/lib/features/manage_account/presentation/forward/forward_controller.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:forward/forward/tmail_forward.dart'; import 'package:get/get.dart'; +import 'package:model/mailbox/select_mode.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/manage_account/domain/model/delete_recipient_in_forwarding_request.dart'; @@ -15,7 +16,9 @@ import 'package:tmail_ui_user/features/manage_account/domain/state/delete_recipi import 'package:tmail_ui_user/features/manage_account/domain/state/get_forward_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/delete_recipient_in_forwarding_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_forward_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/extensions/tmail_forward_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; @@ -29,7 +32,10 @@ class ForwardController extends BaseController { final _imagePaths = Get.find(); final _appToast = Get.find(); - final currentForward = Rxn(); + final selectionMode = Rx(SelectMode.INACTIVE); + final listRecipientForward = RxList(); + + TMailForward? currentForward; ForwardController( this._getForwardInteractor, @@ -38,13 +44,20 @@ class ForwardController extends BaseController { @override void onDone() { - viewState.value.fold((failure) {}, (success) { - if (success is GetForwardSuccess) { - currentForward.value = success.forward; - } else if (success is DeleteRecipientInForwardingSuccess) { - _handleDeleteRecipientSuccess(success); - } - }); + viewState.value.fold( + (failure) { + if (failure is DeleteRecipientInForwardingFailure) { + cancelSelectionMode(); + } + }, + (success) { + if (success is GetForwardSuccess) { + currentForward = success.forward; + listRecipientForward.value = currentForward!.listRecipientForward; + } else if (success is DeleteRecipientInForwardingSuccess) { + _handleDeleteRecipientSuccess(success); + } + }); } @override @@ -100,11 +113,11 @@ class ForwardController extends BaseController { popBack(); final accountId = _accountDashBoardController.accountId.value; - if (accountId != null && currentForward.value != null) { + if (accountId != null && currentForward != null) { consumeState(_deleteRecipientInForwardingInteractor.execute( accountId, DeleteRecipientInForwardingRequest( - currentForward: currentForward.value!, + currentForward: currentForward!, listRecipientDeleted: listRecipients))); } } @@ -117,6 +130,75 @@ class ForwardController extends BaseController { icon: _imagePaths.icSelected); } - currentForward.value = success.forward; + currentForward = success.forward; + listRecipientForward.value = currentForward!.listRecipientForward; + selectionMode.value = SelectMode.INACTIVE; + } + + List get listRecipientForwardSelected => listRecipientForward + .where((recipient) => recipient.selectMode == SelectMode.ACTIVE) + .toList(); + + bool get isAllUnSelected => + listRecipientForward.every((recipient) => recipient.selectMode == SelectMode.INACTIVE); + + void selectRecipientForward(RecipientForward recipientForward) { + if (selectionMode.value == SelectMode.INACTIVE) { + selectionMode.value = SelectMode.ACTIVE; + } + + final matchRecipientForward = listRecipientForward.indexOf(recipientForward); + if (matchRecipientForward >= 0) { + final newRecipientForward = recipientForward.toggleSelection(); + listRecipientForward[matchRecipientForward] = newRecipientForward; + listRecipientForward.refresh(); + } + + if (isAllUnSelected) { + selectionMode.value = SelectMode.INACTIVE; + } + } + + void cancelSelectionMode() { + selectionMode.value = SelectMode.INACTIVE; + listRecipientForward.value = listRecipientForward + .map((recipient) => recipient.cancelSelection()) + .toList(); + } + + void deleteMultipleRecipients(BuildContext context, Set listEmailAddress) { + if (_responsiveUtils.isMobile(context)) { + (ConfirmationDialogActionSheetBuilder(context) + ..messageText(AppLocalizations.of(context).messageConfirmationDialogDeleteAllRecipientForward) + ..onCancelAction(AppLocalizations.of(context).cancel, () => + popBack()) + ..onConfirmAction(AppLocalizations.of(context).delete, () { + _handleDeleteRecipientAction(listEmailAddress); + })) + .show(); + } else { + showDialog( + context: context, + barrierColor: AppColor.colorDefaultCupertinoActionSheet, + builder: (BuildContext context) => + PointerInterceptor(child: (ConfirmDialogBuilder(_imagePaths) + ..title(AppLocalizations.of(context).deleteRecipient) + ..content(AppLocalizations.of(context).messageConfirmationDialogDeleteAllRecipientForward) + ..addIcon(SvgPicture.asset(_imagePaths.icRemoveDialog, + fit: BoxFit.fill)) + ..marginIcon(EdgeInsets.zero) + ..colorConfirmButton(AppColor.colorConfirmActionDialog) + ..styleTextConfirmButton(const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: AppColor.colorActionDeleteConfirmDialog)) + ..onCloseButtonAction(() => popBack()) + ..onConfirmButtonAction(AppLocalizations.of(context).delete, () { + _handleDeleteRecipientAction(listEmailAddress); + }) + ..onCancelButtonAction(AppLocalizations.of(context).cancel, () => + popBack())) + .build())); + } } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/forward/forward_view.dart b/lib/features/manage_account/presentation/forward/forward_view.dart index abf9e2055..3cb37985e 100644 --- a/lib/features/manage_account/presentation/forward/forward_view.dart +++ b/lib/features/manage_account/presentation/forward/forward_view.dart @@ -22,9 +22,8 @@ class ForwardView extends GetWidget with AppLoaderMixin { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: _responsiveUtils.isWebDesktop(context) ? 24 : 16), _buildLoadingView(), - const Expanded(child: ListEmailForwardsWidget()) + Expanded(child: ListEmailForwardsWidget()) ], ), ), diff --git a/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart b/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart index 094b40338..38a7bb335 100644 --- a/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart +++ b/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart @@ -2,46 +2,84 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; class EmailForwardItemWidget extends StatelessWidget { - final _responsiveUtils = Get.find(); + final _imagePaths = Get.find(); final _emailForwardController = Get.find(); - final String emailAddress; + final RecipientForward recipientForward; + final SelectMode selectionMode; EmailForwardItemWidget({ Key? key, - required this.emailAddress, + required this.recipientForward, + this.selectionMode = SelectMode.INACTIVE, }) : super(key: key); @override Widget build(BuildContext context) { - return Container( - padding: EdgeInsets.only( - top: 15, - bottom: 15, - left: _responsiveUtils.isMobile(context) ? 16 : 24, - right: _responsiveUtils.isMobile(context) ? 8 : 24 + return GestureDetector( + onLongPress: () => _emailForwardController.selectRecipientForward(recipientForward), + onTap: () { + if (selectionMode == SelectMode.ACTIVE) { + _emailForwardController.selectRecipientForward(recipientForward); + } + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + color: Colors.white, + child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + Container( + width: 32, + height: 32, + color: Colors.transparent, + alignment: Alignment.center, + child: _buildAvatarIcon()), + const SizedBox(width: 10), + Expanded(child: Text(recipientForward.emailAddress, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Colors.black))), + buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icDeleteEmailForward, + fit: BoxFit.fill, + width: 18, + height: 18, + ), + onTap: () => + _emailForwardController.deleteRecipients(context, recipientForward.emailAddress)), + ]), ), - color: Colors.white, - child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text(emailAddress, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: Colors.black)), - const Spacer(), - buildIconWeb( - icon: SvgPicture.asset( - _imagePaths.icDeleteEmailForward, - fit: BoxFit.fill, - ), - onTap: () { - _emailForwardController.deleteRecipients(context, emailAddress); - }), - ]), ); } + + Widget _buildAvatarIcon() { + if (selectionMode == SelectMode.ACTIVE) { + return Container( + alignment: Alignment.center, + child: SvgPicture.asset( + recipientForward.selectMode == SelectMode.ACTIVE + ? _imagePaths.icSelected + : _imagePaths.icUnSelected, + width: 24, + height: 24)); + } else { + return CircleAvatar( + backgroundColor: AppColor.colorTextButton, + radius: 16, + child: Text(recipientForward.emailAddress[0].toUpperCase(), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500))); + } + } } diff --git a/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart b/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart new file mode 100644 index 000000000..221a67989 --- /dev/null +++ b/lib/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart @@ -0,0 +1,97 @@ +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:html_editor_enhanced/utils/utils.dart'; +import 'package:model/mailbox/select_mode.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; + +class EmailForwardItemWidget extends StatefulWidget { + + final RecipientForward recipientForward; + final SelectMode selectionMode; + + const EmailForwardItemWidget({ + Key? key, + required this.recipientForward, + this.selectionMode = SelectMode.INACTIVE, + }) : super(key: key); + + @override + State createState() => _EmailForwardItemWidgetState(); +} + +class _EmailForwardItemWidgetState extends State { + final _imagePaths = Get.find(); + + final _emailForwardController = Get.find(); + + bool isHoverIcon = false; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + color: Colors.white, + child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + InkWell( + onTap: () { + if (isHoverIcon) { + _emailForwardController.selectRecipientForward(widget.recipientForward); + } + }, + onHover: (value) { + setState(mounted, this.setState, () { + isHoverIcon = value; + }); + }, + child: Container( + width: 32, + height: 32, + color: Colors.transparent, + alignment: Alignment.center, + child: _buildAvatarIcon())), + const SizedBox(width: 10), + Expanded(child: Text(widget.recipientForward.emailAddress, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Colors.black))), + buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icDeleteEmailForward, + fit: BoxFit.fill, + width: 18, + height: 18, + ), + onTap: () => + _emailForwardController.deleteRecipients(context, widget.recipientForward.emailAddress)), + ]), + ); + } + + Widget _buildAvatarIcon() { + if (isHoverIcon || widget.recipientForward.selectMode == SelectMode.ACTIVE) { + return Container( + alignment: Alignment.center, + child: SvgPicture.asset( + widget.recipientForward.selectMode == SelectMode.ACTIVE + ? _imagePaths.icSelected + : _imagePaths.icUnSelected, + width: 24, + height: 24)); + } else { + return CircleAvatar( + backgroundColor: AppColor.colorTextButton, + radius: 16, + child: Text(widget.recipientForward.emailAddress[0].toUpperCase(), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500))); + } + } +} diff --git a/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart b/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart index c6e8609b2..17da0be02 100644 --- a/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart +++ b/lib/features/manage_account/presentation/forward/widgets/list_email_forward_widget.dart @@ -1,13 +1,19 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/utils/app_logger.dart'; +import 'package:core/core.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/forward/forward_controller.dart'; -import 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/email_forward_item_widget.dart' + if (dart.library.html) 'package:tmail_ui_user/features/manage_account/presentation/forward/widgets/email_forward_item_widget_for_web.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/recipient_forward.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class ListEmailForwardsWidget extends GetWidget { - const ListEmailForwardsWidget({Key? key}) : super(key: key); + + final _imagePaths = Get.find(); + + ListEmailForwardsWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -25,7 +31,7 @@ class ListEmailForwardsWidget extends GetWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Container( + Obx(() => Container( width: double.infinity, decoration: const BoxDecoration( color: AppColor.colorBackgroundHeaderListForwards, @@ -33,16 +39,27 @@ class ListEmailForwardsWidget extends GetWidget { topLeft: Radius.circular(16), topRight: Radius.circular(16)), ), - padding: const EdgeInsets.symmetric( - vertical: 28, - horizontal: 24, - ), - child: Text(AppLocalizations.of(context).headerEmailsForward, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: AppColor.colorTextButtonHeaderThread)), - ), + padding: controller.selectionMode.value == SelectMode.INACTIVE + ? const EdgeInsets.all(24) + : const EdgeInsets.symmetric(vertical: 13, horizontal: 24), + child: Row(children: [ + Expanded(child: Text( + AppLocalizations.of(context).headerEmailsForward, + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: AppColor.colorTextButtonHeaderThread))), + Obx(() { + if (controller.selectionMode.value == SelectMode.ACTIVE) { + return _buildListButtonSelection(context); + } else { + return const SizedBox.shrink(); + } + }) + ]), + )), const Divider( color: AppColor.lineItemListColor, height: 1, @@ -50,14 +67,14 @@ class ListEmailForwardsWidget extends GetWidget { ), Expanded( child: Obx(() { - final listRecipients = controller.currentForward.value?.forwards.toList() ?? []; return ListView.separated( shrinkWrap: true, - itemCount: listRecipients.length, + itemCount: controller.listRecipientForward.length, itemBuilder: (context, index) { - final emailAddress = listRecipients[index]; - log('ListEmailForwardsWidget::build(): $emailAddress'); - return EmailForwardItemWidget(emailAddress: emailAddress); + final recipientForward = controller.listRecipientForward[index]; + return EmailForwardItemWidget( + recipientForward: recipientForward, + selectionMode: controller.selectionMode.value); }, separatorBuilder: (context, index) => const Divider( color: AppColor.lineItemListColor, @@ -71,4 +88,27 @@ class ListEmailForwardsWidget extends GetWidget { ), ); } -} + + Widget _buildListButtonSelection(BuildContext context) { + return Row(mainAxisSize: MainAxisSize.min, children: [ + buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icCloseComposer, + color: AppColor.colorTextButton, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).cancel, + onTap: () => controller.cancelSelectionMode()), + const SizedBox(width: 5), + buildIconWeb( + icon: SvgPicture.asset( + _imagePaths.icDelete, + color: AppColor.colorActionDeleteConfirmDialog, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).delete_all, + onTap: () => + controller.deleteMultipleRecipients( + context, + controller.listRecipientForwardSelected.listEmailAddress)) + ]); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/model/recipient_forward.dart b/lib/features/manage_account/presentation/model/recipient_forward.dart new file mode 100644 index 000000000..1cb60cd10 --- /dev/null +++ b/lib/features/manage_account/presentation/model/recipient_forward.dart @@ -0,0 +1,35 @@ + +import 'package:equatable/equatable.dart'; +import 'package:model/mailbox/select_mode.dart'; + +class RecipientForward with EquatableMixin { + final String emailAddress; + final SelectMode selectMode; + + RecipientForward(this.emailAddress, {this.selectMode = SelectMode.INACTIVE}); + + @override + List get props => [emailAddress, selectMode]; +} + +extension RecipientForwardExtension on RecipientForward { + + RecipientForward toggleSelection() { + return RecipientForward( + emailAddress, + selectMode: selectMode == SelectMode.ACTIVE + ? SelectMode.INACTIVE + : SelectMode.ACTIVE + ); + } + + RecipientForward cancelSelection() { + return RecipientForward(emailAddress, selectMode: SelectMode.INACTIVE); + } +} + +extension ListRecipientForwardExtension on List { + + Set get listEmailAddress => + map((recipient) => recipient.emailAddress).toSet(); +} \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index e7bba2d2c..77317d6c6 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-08-19T11:57:02.557202", + "@@last_modified": "2022-08-19T13:28:03.023650", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -2059,5 +2059,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "messageConfirmationDialogDeleteAllRecipientForward": "Do you want to delete all email?", + "@messageConfirmationDialogDeleteAllRecipientForward": { + "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 979b8cbc5..2020c4904 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -2126,4 +2126,11 @@ class AppLocalizations { 'The email has been removed from the recipient list.', name: 'toastMessageDeleteRecipientSuccessfully'); } + + String get messageConfirmationDialogDeleteAllRecipientForward { + return Intl.message( + 'Do you want to delete all email?', + name: 'messageConfirmationDialogDeleteAllRecipientForward' + ); + } } \ No newline at end of file