From c227df8df2cff2583e56e6413fa4bc3bd5ae0343 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 20 Sep 2021 23:26:48 +0700 Subject: [PATCH] TF-80 Create pick destination screen --- .../presentation/utils/responsive_utils.dart | 41 ++++ .../destination_picker_bindings.dart | 24 +++ .../destination_picker_controller.dart | 72 +++++++ .../presentation/destination_picker_view.dart | 182 ++++++++++++++++++ .../model/destination_picker_arguments.dart | 16 ++ .../app_bar_destination_picker_builder.dart | 65 +++++++ .../email/presentation/email_controller.dart | 13 ++ .../email/presentation/email_view.dart | 12 +- .../widgets/app_bar_mail_widget_builder.dart | 14 +- .../mailbox/presentation/mailbox_view.dart | 70 ++++--- .../presentation/model/mailbox_displayed.dart | 5 + .../widgets/mailbox_folder_tile_builder.dart | 45 +++-- .../widgets/mailbox_tile_builder.dart | 62 +++--- .../presentation/thread_controller.dart | 14 ++ .../thread/presentation/thread_view.dart | 63 ++++++ lib/main/pages/app_pages.dart | 7 + lib/main/routes/app_routes.dart | 1 + 17 files changed, 621 insertions(+), 85 deletions(-) create mode 100644 lib/features/destination_picker/presentation/destination_picker_bindings.dart create mode 100644 lib/features/destination_picker/presentation/destination_picker_controller.dart create mode 100644 lib/features/destination_picker/presentation/destination_picker_view.dart create mode 100644 lib/features/destination_picker/presentation/model/destination_picker_arguments.dart create mode 100644 lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart create mode 100644 lib/features/mailbox/presentation/model/mailbox_displayed.dart diff --git a/core/lib/presentation/utils/responsive_utils.dart b/core/lib/presentation/utils/responsive_utils.dart index ec7b75693..c3727ba04 100644 --- a/core/lib/presentation/utils/responsive_utils.dart +++ b/core/lib/presentation/utils/responsive_utils.dart @@ -9,6 +9,11 @@ class ResponsiveUtils { static const double _loginTextFieldWidthLargeScreen = 320.0; static const double _loginButtonWidth = 240.0; + static const double _tabletHorizontalMargin = 144.0; + static const double _tabletVerticalMargin = 234.0; + static const double _desktopVerticalMargin = 144.0; + static const double _desktopHorizontalMargin = 234.0; + double getSizeWidthScreen(BuildContext context) => MediaQuery.of(context).size.width; double getSizeHeightScreen(BuildContext context) => MediaQuery.of(context).size.height; @@ -22,4 +27,40 @@ class ResponsiveUtils { double getWidthLoginTextField(BuildContext context) => isMobile(context) ? _loginTextFieldWidthSmallScreen : _loginTextFieldWidthLargeScreen; double getWidthLoginButton() => _loginButtonWidth; + + EdgeInsets getMarginDestinationPicker(BuildContext context) { + if (isTablet(context)) { + return getSizeHeightScreen(context) <= _tabletVerticalMargin * 2 + ? EdgeInsets.symmetric( + horizontal: _tabletHorizontalMargin, + vertical: 0.0) + : EdgeInsets.symmetric( + horizontal: _tabletHorizontalMargin, + vertical: _tabletVerticalMargin); + } else if (isDesktop(context)) { + return getSizeHeightScreen(context) <= _desktopVerticalMargin * 2 + ? EdgeInsets.symmetric( + horizontal: _desktopHorizontalMargin, + vertical: 0.0) + : EdgeInsets.symmetric( + horizontal: _desktopHorizontalMargin, + vertical: _desktopVerticalMargin); + } else { + return EdgeInsets.zero; + } + } + + BorderRadius radiusDestinationPicker(BuildContext context, double radius) { + if (isTablet(context)) { + return getSizeHeightScreen(context) <= _tabletVerticalMargin * 2 + ? BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)) + : BorderRadius.circular(radius); + } else if (isDesktop(context)) { + return getSizeHeightScreen(context) <= _desktopVerticalMargin * 2 + ? BorderRadius.only(topLeft: Radius.circular(radius), topRight: Radius.circular(radius)) + : BorderRadius.circular(radius); + } else { + return BorderRadius.zero; + } + } } \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/destination_picker_bindings.dart b/lib/features/destination_picker/presentation/destination_picker_bindings.dart new file mode 100644 index 000000000..6a1beb130 --- /dev/null +++ b/lib/features/destination_picker/presentation/destination_picker_bindings.dart @@ -0,0 +1,24 @@ +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_controller.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart'; +import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; +import 'package:tmail_ui_user/features/mailbox/data/repository/mailbox_repository_impl.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; + +class DestinationPickerBindings extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => MailboxDataSourceImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => MailboxRepositoryImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => GetAllMailboxInteractor(Get.find())); + Get.lazyPut(() => TreeBuilder()); + Get.put(DestinationPickerController( + Get.find(), + Get.find())); + } +} \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/destination_picker_controller.dart b/lib/features/destination_picker/presentation/destination_picker_controller.dart new file mode 100644 index 000000000..19bd2e3d7 --- /dev/null +++ b/lib/features/destination_picker/presentation/destination_picker_controller.dart @@ -0,0 +1,72 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +class DestinationPickerController extends BaseController { + + final GetAllMailboxInteractor _getAllMailboxInteractor; + final TreeBuilder _treeBuilder; + + final folderMailboxTree = MailboxTree(MailboxNode.root()).obs; + + DestinationPickerArguments? destinationPickerArguments; + + DestinationPickerController( + this._getAllMailboxInteractor, + this._treeBuilder, + ); + + @override + void onReady() { + super.onReady(); + if (Get.arguments != null && Get.arguments is DestinationPickerArguments) { + destinationPickerArguments = Get.arguments; + getAllMailboxAction(); + } + } + + @override + void onData(Either newState) { + super.onData(newState); + newState.map((success) { + if (success is GetAllMailboxSuccess) { + _buildTree(success.folderMailboxList); + } + }); + } + + @override + void onDone() {} + + @override + void onError(error) {} + + void getAllMailboxAction() { + final accountId = destinationPickerArguments?.accountId; + if (accountId != null) { + consumeState(_getAllMailboxInteractor.execute(accountId)); + } + } + + void _buildTree(List folderMailboxList) async { + folderMailboxTree.value = await _treeBuilder.generateMailboxTree(folderMailboxList); + } + + void moveEmailToMailboxAction(PresentationMailbox destinationMailbox) { + popBack(); + } + + void closeDestinationPicker() { + popBack(); + } +} \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/destination_picker_view.dart b/lib/features/destination_picker/presentation/destination_picker_view.dart new file mode 100644 index 000000000..956e7a736 --- /dev/null +++ b/lib/features/destination_picker/presentation/destination_picker_view.dart @@ -0,0 +1,182 @@ +import 'package:core/core.dart'; +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:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_controller.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_displayed.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_tile_builder.dart'; + +class DestinationPickerView extends GetWidget { + + final _imagePaths = Get.find(); + final _responsiveUtils = Get.find(); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => controller.closeDestinationPicker(), + child: Card( + margin: EdgeInsets.zero, + borderOnForeground: false, + color: Colors.transparent, + child: Container( + margin: _responsiveUtils.getMarginDestinationPicker(context), + child: ClipRRect( + borderRadius: _responsiveUtils.radiusDestinationPicker(context, 20), + child: GestureDetector( + onTap: () => {}, + child: Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: Colors.white, + body: SafeArea( + right: false, + left: false, + child: Column( + children: [ + _buildAppBar(context), + Expanded(child: _buildBodyDestinationPicker(context)) + ], + ) + ) + ) + ) + ) + ) + ) + ); + } + + Widget _buildAppBar(BuildContext context) { + return Padding( + padding: EdgeInsets.only(left: 24, right: 12), + child: (AppBarDestinationPickerBuilder(context, _imagePaths, _responsiveUtils) + ..addCloseActionClick(() => controller.closeDestinationPicker())) + .build() + ); + } + + Widget _buildBodyDestinationPicker(BuildContext context) { + return RefreshIndicator( + color: AppColor.primaryColor, + onRefresh: () async => controller.getAllMailboxAction(), + child: SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: Container( + alignment: Alignment.center, + color: AppColor.bgMailboxListMail, + padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildLoadingView(), + _buildListMailbox(context) + ] + ) + ) + ) + ); + } + + Widget _buildLoadingView() { + return Obx(() => controller.viewState.value.fold( + (failure) => SizedBox.shrink(), + (success) => success == UIState.loading + ? Center(child: Padding( + padding: EdgeInsets.only(top: 16), + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(color: AppColor.primaryColor)))) + : SizedBox.shrink())); + } + + Widget _buildListMailbox(BuildContext context) { + return ListView( + primary: false, + shrinkWrap: true, + children: [ + _buildDefaultMailbox(context), + SizedBox(height: 20), + _buildFolderMailbox(context), + ] + ); + } + + Widget _buildDefaultMailbox(BuildContext context) { + return Obx(() => controller.viewState.value.fold( + (failure) => SizedBox.shrink(), + (success) { + if (success is GetAllMailboxSuccess) { + final defaultMailboxList = success.defaultMailboxList; + return ListView.builder( + padding: EdgeInsets.only(top: 16, right: 10), + key: Key('default_mailbox_list'), + itemCount: defaultMailboxList.length, + shrinkWrap: true, + primary: false, + itemBuilder: (context, index) => (MailboxTileBuilder( + context, + _imagePaths, + _responsiveUtils, + defaultMailboxList[index], + mailboxDisplayed: MailboxDisplayed.destinationPicker) + ..onOpenMailboxAction((mailbox) => controller.moveEmailToMailboxAction(mailbox))) + .build()); + } else { + return SizedBox.shrink(); + } + }) + ); + } + + Widget _buildFolderMailbox(BuildContext context) { + return Obx(() => controller.folderMailboxTree.value.root.hasChildren() + ? Transform( + transform: Matrix4.translationValues(-12.0, 0.0, 0.0), + child: Padding( + padding: EdgeInsets.zero, + child: TreeView( + startExpanded: false, + key: Key('folder_mailbox_list'), + children: _buildListChildTileWidget(context, controller.folderMailboxTree.value.root.childrenItems!)))) + : SizedBox.shrink() + ); + } + + List _buildListChildTileWidget(BuildContext context, List listMailboxNode) { + return listMailboxNode + .map((mailboxNode) => mailboxNode.hasChildren() + ? Padding( + padding: EdgeInsets.only(left: 20), + child: TreeViewChild( + key: Key('children_tree_mailbox_child'), + parent: MailBoxFolderTileBuilder( + context, + _imagePaths, + _responsiveUtils, + mailboxNode, + mailboxDisplayed: MailboxDisplayed.destinationPicker) + .build(), + children: _buildListChildTileWidget(context, mailboxNode.childrenItems!))) + : Padding( + padding: EdgeInsets.only(left: 20), + child: GestureDetector( + onTap: () => controller.moveEmailToMailboxAction(mailboxNode.item), + child: MailBoxFolderTileBuilder( + context, + _imagePaths, + _responsiveUtils, + mailboxNode, + mailboxDisplayed: MailboxDisplayed.destinationPicker) + .build(), + ))) + .toList(); + } +} \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/model/destination_picker_arguments.dart b/lib/features/destination_picker/presentation/model/destination_picker_arguments.dart new file mode 100644 index 000000000..c666d445f --- /dev/null +++ b/lib/features/destination_picker/presentation/model/destination_picker_arguments.dart @@ -0,0 +1,16 @@ + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; + +class DestinationPickerArguments with EquatableMixin{ + final AccountId accountId; + final List emailIds; + final MailboxId currentMailboxId; + + DestinationPickerArguments(this.accountId, this.emailIds, this.currentMailboxId); + + @override + List get props => [accountId, emailIds, currentMailboxId]; +} \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart b/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart new file mode 100644 index 000000000..4b3695224 --- /dev/null +++ b/lib/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart @@ -0,0 +1,65 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnCloseActionClick = void Function(); + +class AppBarDestinationPickerBuilder { + OnCloseActionClick? _onCloseActionClick; + + final BuildContext _context; + final ImagePaths _imagePaths; + final ResponsiveUtils _responsiveUtils; + + AppBarDestinationPickerBuilder(this._context, this._imagePaths, this._responsiveUtils); + + void addCloseActionClick(OnCloseActionClick onCloseActionClick) { + _onCloseActionClick = onCloseActionClick; + } + + Widget build() { + return Container( + key: Key('app_bar_destination_picker'), + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 8), + color: Colors.white, + child: MediaQuery( + data: MediaQueryData(padding: EdgeInsets.zero), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildBackButton(), + Expanded(child: _buildCountItemSelected()) + ] + ) + ) + ); + } + + Widget _buildBackButton() { + return ButtonBuilder(_imagePaths.icComposerClose) + .padding(5) + .size(30) + .onPressActionClick(() { + if (_onCloseActionClick != null) { + _onCloseActionClick!(); + }}) + .build(); + } + + Widget _buildCountItemSelected() { + return Padding( + padding: EdgeInsets.only( + left: 12, + right: _responsiveUtils.isMobile(_context) ? 0 : 47), + child: Text( + AppLocalizations.of(_context).move_to_mailbox, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: _responsiveUtils.isMobile(_context) ? TextAlign.start : TextAlign.center, + style: TextStyle(fontSize: 18, color: AppColor.nameUserColor, fontWeight: FontWeight.w500))); + } +} \ No newline at end of file diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index 42403aef6..e07a390fe 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -11,6 +11,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; import 'package:tmail_ui_user/features/email/domain/state/download_attachments_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/export_attachment_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; @@ -230,6 +231,18 @@ class EmailController extends BaseController { } } + void openDestinationPickerView(PresentationEmail email) { + final currentMailboxId = mailboxDashBoardController.selectedMailbox.value?.id; + final accountId = mailboxDashBoardController.accountId.value; + + if (currentMailboxId != null && accountId != null) { + push( + AppRoutes.DESTINATION_PICKER, + arguments: DestinationPickerArguments(accountId, [email.id], currentMailboxId) + ); + } + } + bool canComposeEmail() => mailboxDashBoardController.sessionCurrent != null && mailboxDashBoardController.userProfile.value != null && mailboxDashBoardController.mapMailboxId.containsKey(PresentationMailbox.roleOutbox) diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index f57c1ff3c..d27d629f0 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -50,13 +50,15 @@ class EmailView extends GetWidget { return Obx(() => Padding( padding: EdgeInsets.only(left: 6, top: 6, right: 6, bottom: 6), child: (AppBarMailWidgetBuilder( - context, - imagePaths, - responsiveUtils, - emailController.mailboxDashBoardController.selectedEmail.value) + context, + imagePaths, + responsiveUtils, + emailController.mailboxDashBoardController.selectedEmail.value) ..onBackActionClick(() => emailController.backToThreadView()) ..onUnreadEmailActionClick((presentationEmail) => - emailController.markAsEmailRead(presentationEmail, ReadActions.markAsUnread))) + emailController.markAsEmailRead(presentationEmail, ReadActions.markAsUnread)) + ..addOnMoveToMailboxActionClick((presentationEmail) => + emailController.openDestinationPickerView(presentationEmail))) .build())); } diff --git a/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart b/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart index c74eb5f2e..3ab6e9dbe 100644 --- a/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart +++ b/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart @@ -8,10 +8,12 @@ import 'package:tmail_ui_user/main/routes/app_routes.dart'; typedef OnBackActionClick = void Function(); typedef OnUnreadEmailActionClick = void Function(PresentationEmail presentationEmail); +typedef OnMoveToMailboxActionClick = void Function(PresentationEmail presentationEmail); class AppBarMailWidgetBuilder { OnBackActionClick? _onBackActionClick; OnUnreadEmailActionClick? _onUnreadEmailActionClick; + OnMoveToMailboxActionClick? _onMoveToMailboxActionClick; final BuildContext _context; final ImagePaths _imagePaths; @@ -33,6 +35,10 @@ class AppBarMailWidgetBuilder { _onUnreadEmailActionClick = onUnreadEmailActionClick; } + void addOnMoveToMailboxActionClick(OnMoveToMailboxActionClick onMoveToMailboxActionClick) { + _onMoveToMailboxActionClick = onMoveToMailboxActionClick; + } + Widget build() { return Container( key: Key('app_bar_messenger_widget'), @@ -88,7 +94,13 @@ class AppBarMailWidgetBuilder { .key(Key('button_mark_as_flag_email')) .build(), SizedBox(width: 10), - ButtonBuilder(_imagePaths.icFolder).key(Key('button_move_to_mailbox_email')).build(), + ButtonBuilder(_imagePaths.icFolder) + .key(Key('button_move_to_mailbox_email')) + .onPressActionClick(() { + if (_onMoveToMailboxActionClick != null && _presentationEmail != null) { + _onMoveToMailboxActionClick!(_presentationEmail!); + }}) + .build(), ] ); } diff --git a/lib/features/mailbox/presentation/mailbox_view.dart b/lib/features/mailbox/presentation/mailbox_view.dart index 3bf82f1b7..24e24650a 100644 --- a/lib/features/mailbox/presentation/mailbox_view.dart +++ b/lib/features/mailbox/presentation/mailbox_view.dart @@ -161,14 +161,16 @@ class MailboxView extends GetWidget { shrinkWrap: true, primary: false, itemBuilder: (context, index) => - Obx(() => MailboxTileBuilder( - context, - imagePaths, - responsiveUtils, - defaultMailbox[index], - controller.getSelectMode(defaultMailbox[index], controller.mailboxDashBoardController.selectedMailbox.value)) - .onOpenMailboxAction(() => controller.selectMailbox(context, defaultMailbox[index])) - .build())); + Obx(() => (MailboxTileBuilder( + context, + imagePaths, + responsiveUtils, + defaultMailbox[index], + selectMode: controller.getSelectMode( + defaultMailbox[index], + controller.mailboxDashBoardController.selectedMailbox.value)) + ..onOpenMailboxAction((mailbox) => controller.selectMailbox(context, mailbox))) + .build())); } Widget _buildFolderMailbox(BuildContext context) { @@ -189,30 +191,34 @@ class MailboxView extends GetWidget { List _buildListChildTileWidget(BuildContext context, List listMailboxNode) { return listMailboxNode .map((mailboxNode) => mailboxNode.hasChildren() - ? Padding( - padding: EdgeInsets.only(left: 16), - child: TreeViewChild( - key: Key('children_tree_mailbox_child'), - parent: Obx(() => MailBoxFolderTileBuilder( - context, - imagePaths, - responsiveUtils, - mailboxNode, - controller.getSelectMode(mailboxNode.item, - controller.mailboxDashBoardController.selectedMailbox.value)) - .build(context)), - children: _buildListChildTileWidget(context, mailboxNode.childrenItems!))) - : Padding( - padding: EdgeInsets.only(left: 16), - child: GestureDetector( - onTap: () => controller.selectMailbox(context, mailboxNode.item), - child: Obx(() => MailBoxFolderTileBuilder( - context, - imagePaths, - responsiveUtils, - mailboxNode, - controller.getSelectMode(mailboxNode.item, controller.mailboxDashBoardController.selectedMailbox.value)) - .build(context))))) + ? Padding( + padding: EdgeInsets.only(left: 16), + child: TreeViewChild( + key: Key('children_tree_mailbox_child'), + parent: Obx(() => MailBoxFolderTileBuilder( + context, + imagePaths, + responsiveUtils, + mailboxNode, + selectMode: controller.getSelectMode( + mailboxNode.item, + controller.mailboxDashBoardController.selectedMailbox.value)) + .build()), + children: _buildListChildTileWidget(context, mailboxNode.childrenItems!))) + : Padding( + padding: EdgeInsets.only(left: 16), + child: Obx(() => GestureDetector( + onTap: () => controller.selectMailbox(context, mailboxNode.item), + child: MailBoxFolderTileBuilder( + context, + imagePaths, + responsiveUtils, + mailboxNode, + selectMode: controller.getSelectMode( + mailboxNode.item, + controller.mailboxDashBoardController.selectedMailbox.value)) + .build(), + )))) .toList(); } diff --git a/lib/features/mailbox/presentation/model/mailbox_displayed.dart b/lib/features/mailbox/presentation/model/mailbox_displayed.dart new file mode 100644 index 000000000..ec77b663c --- /dev/null +++ b/lib/features/mailbox/presentation/model/mailbox_displayed.dart @@ -0,0 +1,5 @@ + +enum MailboxDisplayed { + mailbox, + destinationPicker +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart b/lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart index 0f535d2a4..49044483a 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_folder_tile_builder.dart @@ -3,53 +3,52 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:model/mailbox/select_mode.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_displayed.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; -import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart'; -typedef OnOpenMailBoxFolderActionClick = void Function(MailboxTree mailboxTree); - -class MailBoxFolderTileBuilder extends StatelessWidget { +class MailBoxFolderTileBuilder { final MailboxNode _mailboxNode; final BuildContext _context; final ImagePaths _imagePaths; final ResponsiveUtils _responsiveUtils; - final SelectMode _selectMode; - - final OnOpenMailBoxFolderActionClick? onOpenMailBoxFolderActionClick; + final SelectMode selectMode; + final MailboxDisplayed mailboxDisplayed; MailBoxFolderTileBuilder( this._context, this._imagePaths, this._responsiveUtils, this._mailboxNode, - this._selectMode, { - this.onOpenMailBoxFolderActionClick + this.selectMode = SelectMode.INACTIVE, + this.mailboxDisplayed = MailboxDisplayed.mailbox, } ); - @override - Widget build(BuildContext context) { + Widget build() { return Container( key: Key('mailbox_folder_tile'), alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: _selectMode== SelectMode.ACTIVE - ? AppColor.mailboxSelectedBackgroundColor - : AppColor.mailboxBackgroundColor), + borderRadius: BorderRadius.circular( mailboxDisplayed == MailboxDisplayed.mailbox ? 16 : 0), + color: mailboxDisplayed == MailboxDisplayed.mailbox + ? selectMode == SelectMode.ACTIVE ? AppColor.mailboxSelectedBackgroundColor : AppColor.mailboxBackgroundColor + : AppColor.bgMailboxListMail + ), child: MediaQuery( data: MediaQueryData(padding: EdgeInsets.zero), child: ListTile( contentPadding: EdgeInsets.zero, leading: Padding( - padding: EdgeInsets.only(left: _responsiveUtils.isMobile(_context) ? 40 : 34), + padding: EdgeInsets.only(left: mailboxDisplayed == MailboxDisplayed.mailbox + ? _responsiveUtils.isMobile(_context) ? 40 : 34 + : 34), child: SvgPicture.asset( _imagePaths.icMailboxFolder, width: 24, height: 24, - color: _selectMode == SelectMode.ACTIVE + color: selectMode == SelectMode.ACTIVE ? AppColor.mailboxSelectedIconColor : AppColor.mailboxIconColor, fit: BoxFit.fill)), @@ -61,19 +60,23 @@ class MailBoxFolderTileBuilder extends StatelessWidget { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 15, - color: _selectMode == SelectMode.ACTIVE + color: selectMode == SelectMode.ACTIVE ? AppColor.mailboxSelectedTextColor : AppColor.mailboxTextColor, - fontWeight: FontWeight.bold), + fontWeight: mailboxDisplayed == MailboxDisplayed.mailbox ? FontWeight.bold : FontWeight.w500), )), trailing: Padding( - padding: EdgeInsets.only(right: _responsiveUtils.isMobile(_context) ? 36 : 8, left: 16), + padding: EdgeInsets.only( + right: mailboxDisplayed == MailboxDisplayed.mailbox + ? _responsiveUtils.isMobile(_context) ? 36 : 8 + : 36, + left: mailboxDisplayed == MailboxDisplayed.mailbox ? 16 : 16), child: _mailboxNode.hasChildren() ? SvgPicture.asset( _imagePaths.icFolderArrow, width: 12, height: 12, - color: _selectMode == SelectMode.ACTIVE + color: selectMode == SelectMode.ACTIVE ? AppColor.mailboxSelectedIconColor : AppColor.mailboxIconColor, fit: BoxFit.fill) diff --git a/lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart b/lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart index 2776a90b8..a840e6b09 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_tile_builder.dart @@ -6,16 +6,18 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/mailbox/domain/extensions/presentation_mailbox_extension.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_displayed.dart'; -typedef OnOpenMailboxActionClick = void Function(); +typedef OnOpenMailboxActionClick = void Function(PresentationMailbox mailbox); class MailboxTileBuilder { final PresentationMailbox _presentationMailbox; - final SelectMode _selectMode; + final SelectMode selectMode; final BuildContext _context; final ImagePaths _imagePaths; final ResponsiveUtils _responsiveUtils; + final MailboxDisplayed mailboxDisplayed; OnOpenMailboxActionClick? _onOpenMailboxActionClick; @@ -24,12 +26,14 @@ class MailboxTileBuilder { this._imagePaths, this._responsiveUtils, this._presentationMailbox, - this._selectMode + { + this.selectMode = SelectMode.INACTIVE, + this.mailboxDisplayed = MailboxDisplayed.mailbox + } ); - MailboxTileBuilder onOpenMailboxAction(OnOpenMailboxActionClick onOpenMailboxActionClick) { + void onOpenMailboxAction(OnOpenMailboxActionClick onOpenMailboxActionClick) { _onOpenMailboxActionClick = onOpenMailboxActionClick; - return this; } Widget build() { @@ -41,10 +45,11 @@ class MailboxTileBuilder { key: Key('mailbox_list_tile'), alignment: Alignment.center, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: _selectMode== SelectMode.ACTIVE - ? AppColor.mailboxSelectedBackgroundColor - : AppColor.mailboxBackgroundColor), + borderRadius: BorderRadius.circular(mailboxDisplayed == MailboxDisplayed.mailbox ? 16 : 0), + color: mailboxDisplayed == MailboxDisplayed.mailbox + ? selectMode == SelectMode.ACTIVE ? AppColor.mailboxSelectedBackgroundColor : AppColor.mailboxBackgroundColor + : AppColor.bgMailboxListMail + ), child: MediaQuery( data: MediaQueryData(padding: EdgeInsets.zero), child: ListTile( @@ -53,16 +58,19 @@ class MailboxTileBuilder { contentPadding: EdgeInsets.zero, onTap: () => { if (_onOpenMailboxActionClick != null) { - _onOpenMailboxActionClick!() + _onOpenMailboxActionClick!(_presentationMailbox) } }, leading: Padding( - padding: EdgeInsets.only(left: _responsiveUtils.isMobile(_context) ? 40 : 20), + padding: EdgeInsets.only(left: mailboxDisplayed == MailboxDisplayed.mailbox + ? _responsiveUtils.isMobile(_context) ? 40 : 20 + : 40 + ), child: SvgPicture.asset( '${_presentationMailbox.getMailboxIcon(_imagePaths)}', width: 24, height: 24, - color: _selectMode == SelectMode.ACTIVE + color: selectMode == SelectMode.ACTIVE ? AppColor.mailboxSelectedIconColor : AppColor.mailboxIconColor, fit: BoxFit.fill)), @@ -74,23 +82,25 @@ class MailboxTileBuilder { overflow:TextOverflow.ellipsis, style: TextStyle( fontSize: 15, - color: _selectMode == SelectMode.ACTIVE + color: selectMode == SelectMode.ACTIVE ? AppColor.mailboxSelectedTextColor : AppColor.mailboxTextColor, - fontWeight: FontWeight.bold), + fontWeight: mailboxDisplayed == MailboxDisplayed.mailbox ? FontWeight.bold : FontWeight.w500), )), - trailing: Padding( - padding: EdgeInsets.only(right: _responsiveUtils.isMobile(_context) ? 36 : 24, left: 16), - child: Text( - '${_presentationMailbox.getCountUnReadEmails()}', - maxLines: 1, - style: TextStyle( - fontSize: 15, - color: _selectMode == SelectMode.ACTIVE - ? AppColor.mailboxSelectedTextNumberColor - : AppColor.mailboxTextNumberColor, - fontWeight: FontWeight.bold)) - )), + trailing: mailboxDisplayed == MailboxDisplayed.mailbox + ? Padding( + padding: EdgeInsets.only(right: _responsiveUtils.isMobile(_context) ? 36 : 24, left: 16), + child: Text( + '${_presentationMailbox.getCountUnReadEmails()}', + maxLines: 1, + style: TextStyle( + fontSize: 15, + color: selectMode == SelectMode.ACTIVE + ? AppColor.mailboxSelectedTextNumberColor + : AppColor.mailboxTextNumberColor, + fontWeight: FontWeight.bold))) + : SizedBox.shrink() + ), ) ) ); diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index ded856c58..08eecc300 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -15,6 +15,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; @@ -318,6 +319,19 @@ class ThreadController extends BaseController { .build(); } + void moveSelectedMultipleEmailToMailboxAction(List listEmail) { + final accountId = mailboxDashBoardController.accountId.value; + if (_currentMailboxId != null && accountId != null) { + popBack(); + + final listEmailIds = listEmail.map((email) => email.id).toList(); + push( + AppRoutes.DESTINATION_PICKER, + arguments: DestinationPickerArguments(accountId, listEmailIds, _currentMailboxId!) + ); + } + } + bool canComposeEmail() => mailboxDashBoardController.sessionCurrent != null && mailboxDashBoardController.userProfile.value != null && mailboxDashBoardController.mapMailboxId.containsKey(PresentationMailbox.roleOutbox); diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 2a8001a1f..4788d0489 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -142,6 +142,69 @@ class ThreadView extends GetWidget { .build(); } + List _contextMenuActionList(BuildContext context, List listEmail) { + return [ + _moveToTrashAction(context, listEmail), + _moveToMailboxAction(context, listEmail), + _markAsReadAction(context, listEmail), + _markAsFlagAction(context, listEmail), + _moveToSpamAction(context, listEmail), + SizedBox(height: 30), + ]; + } + + Widget _markAsReadAction(BuildContext context, List listEmail) { + return (EmailContextMenuActionBuilder( + Key('mark_as_read_context_menu_action'), + SvgPicture.asset(imagePaths.icEyeDisable, width: 24, height: 24, fit: BoxFit.fill), + controller.isEmailAllRead(listEmail) + ? AppLocalizations.of(context).mark_as_unread + : AppLocalizations.of(context).mark_as_read, + listEmail) + ..onActionClick((data) => controller.markAsSelectedEmailRead(data, fromContextMenuAction: true))) + .build(); + } + + Widget _moveToTrashAction(BuildContext context, List listEmail) { + return (EmailContextMenuActionBuilder( + Key('move_to_trash_context_menu_action'), + SvgPicture.asset(imagePaths.icTrash, width: 24, height: 24, fit: BoxFit.fill), + AppLocalizations.of(context).move_to_trash, + listEmail) + ..onActionClick((data) => {})) + .build(); + } + + Widget _moveToMailboxAction(BuildContext context, List listEmail) { + return (EmailContextMenuActionBuilder( + Key('move_to_mailbox_context_menu_action'), + SvgPicture.asset(imagePaths.icFolder, width: 24, height: 24, fit: BoxFit.fill), + AppLocalizations.of(context).move_to_mailbox, + listEmail) + ..onActionClick((emails) => controller.moveSelectedMultipleEmailToMailboxAction(emails))) + .build(); + } + + Widget _markAsFlagAction(BuildContext context, List listEmail) { + return (EmailContextMenuActionBuilder( + Key('mark_as_flag_context_menu_action'), + SvgPicture.asset(imagePaths.icFlag, width: 24, height: 24, fit: BoxFit.fill), + AppLocalizations.of(context).mark_as_flag, + listEmail) + ..onActionClick((data) => {})) + .build(); + } + + Widget _moveToSpamAction(BuildContext context, List listEmail) { + return (EmailContextMenuActionBuilder( + Key('move_to_spam_context_menu_action'), + SvgPicture.asset(imagePaths.icMailboxSpam, width: 24, height: 24, fit: BoxFit.fill), + AppLocalizations.of(context).move_to_spam, + listEmail) + ..onActionClick((data) => {})) + .build(); + } + Widget _buildLoadingView() { return Obx(() => controller.viewState.value.fold( (failure) => SizedBox.shrink(), diff --git a/lib/main/pages/app_pages.dart b/lib/main/pages/app_pages.dart index 83743f655..2ff3ab6ad 100644 --- a/lib/main/pages/app_pages.dart +++ b/lib/main/pages/app_pages.dart @@ -1,6 +1,8 @@ import 'package:get/get_navigation/src/routes/get_route.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_bindings.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_bindings.dart'; +import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_view.dart'; import 'package:tmail_ui_user/features/home/presentation/home_bindings.dart'; import 'package:tmail_ui_user/features/home/presentation/home_view.dart'; import 'package:tmail_ui_user/features/login/presentation/login_bindings.dart'; @@ -45,5 +47,10 @@ class AppPages { name: AppRoutes.COMPOSER, page: () => ComposerView(), binding: ComposerBindings()), + GetPage( + name: AppRoutes.DESTINATION_PICKER, + opaque: false, + page: () => DestinationPickerView(), + binding: DestinationPickerBindings()), ]; } diff --git a/lib/main/routes/app_routes.dart b/lib/main/routes/app_routes.dart index f4071c00c..a94653ab0 100644 --- a/lib/main/routes/app_routes.dart +++ b/lib/main/routes/app_routes.dart @@ -7,4 +7,5 @@ abstract class AppRoutes { static const EMAIL = '$THREAD/email'; static const SESSION = '/session'; static const COMPOSER = '/composer'; + static const DESTINATION_PICKER = '/destinationPicker'; } \ No newline at end of file