From 870095936262cdb2abb6d271ae465fb96d710530 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 24 Mar 2022 09:08:25 +0700 Subject: [PATCH] TF-390 Apply new ui composer for browser --- assets/images/ic_add_email_address.svg | 3 + assets/images/ic_chevron_up.svg | 3 + assets/images/ic_close.svg | 3 + assets/images/ic_empty.svg | 3 + assets/images/ic_fullscreen_exit.svg | 4 + assets/images/ic_minimize.svg | 3 + assets/images/ic_minimize_composer.svg | 3 - assets/images/ic_send_disable.svg | 3 + assets/images/ic_send_mobile_disable.svg | 4 - assets/images/ic_send_toast_error.svg | 4 + .../extensions/color_extension.dart | 5 +- .../presentation/resources/image_paths.dart | 10 +- .../views/button/icon_button_web.dart | 48 +- .../dialog/confirmation_dialog_builder.dart | 56 +- .../presentation/composer_bindings.dart | 3 - .../presentation/composer_controller.dart | 189 ++++-- .../composer/presentation/composer_view.dart | 15 +- .../presentation/composer_view_web.dart | 572 ++++++++++-------- .../widgets/email_address_input_builder.dart | 267 ++++---- .../widgets/suggestions_box_controller.dart | 35 -- .../presentation/mailbox_controller.dart | 5 +- lib/main/localizations/app_localizations.dart | 76 ++- 22 files changed, 820 insertions(+), 494 deletions(-) create mode 100644 assets/images/ic_add_email_address.svg create mode 100644 assets/images/ic_chevron_up.svg create mode 100644 assets/images/ic_close.svg create mode 100644 assets/images/ic_empty.svg create mode 100644 assets/images/ic_fullscreen_exit.svg create mode 100644 assets/images/ic_minimize.svg delete mode 100644 assets/images/ic_minimize_composer.svg create mode 100644 assets/images/ic_send_disable.svg delete mode 100644 assets/images/ic_send_mobile_disable.svg create mode 100644 assets/images/ic_send_toast_error.svg delete mode 100644 lib/features/composer/presentation/widgets/suggestions_box_controller.dart diff --git a/assets/images/ic_add_email_address.svg b/assets/images/ic_add_email_address.svg new file mode 100644 index 000000000..c3026c8c2 --- /dev/null +++ b/assets/images/ic_add_email_address.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_chevron_up.svg b/assets/images/ic_chevron_up.svg new file mode 100644 index 000000000..fcc429730 --- /dev/null +++ b/assets/images/ic_chevron_up.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_close.svg b/assets/images/ic_close.svg new file mode 100644 index 000000000..cb1b7c340 --- /dev/null +++ b/assets/images/ic_close.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_empty.svg b/assets/images/ic_empty.svg new file mode 100644 index 000000000..17ec58775 --- /dev/null +++ b/assets/images/ic_empty.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_fullscreen_exit.svg b/assets/images/ic_fullscreen_exit.svg new file mode 100644 index 000000000..509276675 --- /dev/null +++ b/assets/images/ic_fullscreen_exit.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/ic_minimize.svg b/assets/images/ic_minimize.svg new file mode 100644 index 000000000..999a094a3 --- /dev/null +++ b/assets/images/ic_minimize.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_minimize_composer.svg b/assets/images/ic_minimize_composer.svg deleted file mode 100644 index ac0e2f5f4..000000000 --- a/assets/images/ic_minimize_composer.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/images/ic_send_disable.svg b/assets/images/ic_send_disable.svg new file mode 100644 index 000000000..d17fb2804 --- /dev/null +++ b/assets/images/ic_send_disable.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_send_mobile_disable.svg b/assets/images/ic_send_mobile_disable.svg deleted file mode 100644 index 64f85c188..000000000 --- a/assets/images/ic_send_mobile_disable.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/images/ic_send_toast_error.svg b/assets/images/ic_send_toast_error.svg new file mode 100644 index 000000000..129c34279 --- /dev/null +++ b/assets/images/ic_send_toast_error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 3646c1b42..3c44a6be3 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -43,7 +43,7 @@ extension AppColor on Color { static const attachmentFileBorderColor = Color(0xFFEAEAEA); static const attachmentFileNameColor = Color(0xFF182952); static const attachmentFileSizeColor = Color(0xFF7E869B); - static const avatarColor = Color(0xFFE6E5FF); + static const avatarColor = Color(0xFFF8F8F8); static const avatarTextColor = Color(0xFF3840F7); static const sentTimeTextColorUnRead = Color(0xFF182952); static const subjectEmailTextColorUnRead = Color(0xFF3840F7); @@ -103,6 +103,9 @@ extension AppColor on Color { static const colorBorderBodyThread = Color(0x5CB8C1CC); static const colorBgDesktop = Color(0xFFF6F6F6); static const colorItemEmailSelectedDesktop = Color(0x0F007AFF); + static const colorAvatar = Color(0xFFDE5E5E); + static const colorFocusButton = Color(0x14818C99); + static const colorBorderEmailAddressInvalid = Color(0xFFFF3347); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 30ddadeeb..73ca23672 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -68,18 +68,24 @@ class ImagePaths { String get icAttachmentsComposer => _getImagePath('ic_attachments_composer.svg'); String get icCloseComposer => _getImagePath('ic_close_composer.svg'); String get icFullScreenComposer => _getImagePath('ic_fullscreen_composer.svg'); - String get icMinimizeComposer => _getImagePath('ic_minimize_composer.svg'); + String get icMinimize => _getImagePath('ic_minimize.svg'); String get icSendComposer => _getImagePath('ic_send_composer.svg'); String get icDeleteComposer => _getImagePath('ic_delete_composer.svg'); String get icFileAttachment => _getImagePath('ic_file_attachment.svg'); String get icDeleteAttachment => _getImagePath('ic_delete_attachment.svg'); String get icSendMobile => _getImagePath('ic_send_mobile.svg'); - String get icSendMobileDisable => _getImagePath('ic_send_mobile_disable.svg'); + String get icSendDisable => _getImagePath('ic_send_disable.svg'); String get icArrowDown => _getImagePath('ic_arrow_down.svg'); String get icFilterWeb => _getImagePath('ic_filter_web.svg'); String get icMarkAllAsRead => _getImagePath('ic_mark_all_as_read.svg'); String get icRefresh => _getImagePath('ic_refresh.svg'); String get icSelectAll => _getImagePath('ic_select_all.svg'); + String get icFullScreenExit => _getImagePath('ic_fullscreen_exit.svg'); + String get icChevronUp => _getImagePath('ic_chevron_up.svg'); + String get icAddEmailAddress => _getImagePath('ic_add_email_address.svg'); + String get icClose => _getImagePath('ic_close.svg'); + String get icSendToastError => _getImagePath('ic_send_toast_error.svg'); + String get icEmpty => _getImagePath('ic_empty.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/views/button/icon_button_web.dart b/core/lib/presentation/views/button/icon_button_web.dart index d01a36db1..a83456b83 100644 --- a/core/lib/presentation/views/button/icon_button_web.dart +++ b/core/lib/presentation/views/button/icon_button_web.dart @@ -1,4 +1,5 @@ +import 'package:core/core.dart'; import 'package:flutter/material.dart'; typedef IconWebCallback = void Function(); @@ -20,7 +21,7 @@ Widget buildIconWeb({ iconSize: iconSize, padding: iconPadding ?? EdgeInsets.all(8.0), splashRadius: splashRadius ?? 20, - tooltip: tooltip, + tooltip: tooltip ?? '', onPressed: onTap) ); } @@ -50,4 +51,47 @@ Widget buildIconWebHasPosition(BuildContext context, { child: icon, ) ); -} \ No newline at end of file +} + +Widget buildTextCircleButton(String text, { + TextStyle? textStyle, + IconWebCallback? onTap, +}) { + return Material( + shape: CircleBorder(), + color: Colors.transparent, + child: TextButton( + child: Text( + text, + style: textStyle ?? TextStyle(fontWeight: FontWeight.normal, fontSize: 15, color: AppColor.lineItemListColor)), + style: ButtonStyle( + overlayColor: MaterialStateProperty.resolveWith((Set states) => AppColor.colorFocusButton), + shape: MaterialStateProperty.all(CircleBorder()), + padding: MaterialStateProperty.resolveWith((Set states) => EdgeInsets.all(5)), + elevation: MaterialStateProperty.resolveWith((Set states) => 0)), + onPressed: () => onTap?.call() + ) + ); +} + +Widget buildTextButton(String text, { + TextStyle? textStyle, + double? width, + double? height, + Color? backgroundColor, + double? radius, + IconWebCallback? onTap, +}) { + return SizedBox( + width: width ?? double.infinity, + height: height ?? 40, + child: ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith((states) => backgroundColor ?? AppColor.colorTextButton), + elevation: MaterialStateProperty.resolveWith((states) => 0), + shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(radius ?? 0)))), + child: Text(text, style: textStyle ?? TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.w500)), + onPressed: () => onTap?.call() + ), + ); +} diff --git a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart index ac19397f1..49a5b60c7 100644 --- a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart +++ b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart @@ -23,6 +23,10 @@ class ConfirmDialogBuilder { TextStyle? _styleTitle; TextStyle? _styleContent; double? _radiusButton; + EdgeInsets? _paddingTitle; + EdgeInsets? _paddingContent; + EdgeInsets? _paddingButton; + EdgeInsets? _marginIcon; OnConfirmButtonAction? _onConfirmButtonAction; OnCancelButtonAction? _onCancelButtonAction; @@ -74,6 +78,22 @@ class ConfirmDialogBuilder { _radiusButton = radius; } + void paddingTitle(EdgeInsets? value) { + _paddingTitle = value; + } + + void paddingContent(EdgeInsets? value) { + _paddingContent = value; + } + + void paddingButton(EdgeInsets? value) { + _paddingButton = value; + } + + void marginIcon(EdgeInsets? value) { + _marginIcon = value; + } + void onConfirmButtonAction(String confirmText, OnConfirmButtonAction? onConfirmButtonAction) { _confirmText = confirmText; _onConfirmButtonAction = onConfirmButtonAction; @@ -104,19 +124,21 @@ class ConfirmDialogBuilder { if (_onCloseButtonAction != null) Align( alignment: Alignment.centerRight, - child: IconButton( - padding: EdgeInsets.only(top: 16, right: 16), - onPressed: () => _onCloseButtonAction?.call(), - icon: SvgPicture.asset(_imagePath.icCloseMailbox, width: 30, height: 30, fit: BoxFit.fill))), + child: Padding( + padding: EdgeInsets.only(top: 8, right: 8), + child: buildIconWeb( + icon: SvgPicture.asset(_imagePath.icCloseMailbox, fit: BoxFit.fill), + onTap: () => _onCloseButtonAction?.call()) + )), if (_iconWidget != null) Container( - margin: EdgeInsets.only(top: 24), + margin: _marginIcon ?? EdgeInsets.only(top: 24), alignment: Alignment.center, child: _iconWidget, ), if (_title.isNotEmpty) Padding( - padding: EdgeInsets.only(top: 12), + padding: _paddingTitle ?? EdgeInsets.only(top: 12), child: Center( child: Text( _title, @@ -127,7 +149,7 @@ class ConfirmDialogBuilder { ), if (_content.isNotEmpty) Padding( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24), + padding: _paddingContent ?? EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Center( child: Text(_content, textAlign: TextAlign.center, @@ -136,22 +158,24 @@ class ConfirmDialogBuilder { ), ), Padding( - padding: EdgeInsets.only(bottom: 16, left: 16, right: 16), + padding: _paddingButton ?? EdgeInsets.only(bottom: 16, left: 16, right: 16), child: Row( children: [ - Expanded(child: _buildButton( + if (_cancelText.isNotEmpty) + Expanded(child: _buildButton( name: _cancelText, bgColor: _colorCancelButton, radius: _radiusButton, textStyle: _styleTextCancelButton, action: _onCancelButtonAction)), - SizedBox(width: 16), - Expanded(child: _buildButton( - name: _confirmText, - bgColor: _colorConfirmButton, - radius: _radiusButton, - textStyle: _styleTextConfirmButton, - action: _onConfirmButtonAction)) + if (_confirmText.isNotEmpty && _cancelText.isNotEmpty) SizedBox(width: 16), + if (_confirmText.isNotEmpty) + Expanded(child: _buildButton( + name: _confirmText, + bgColor: _colorConfirmButton, + radius: _radiusButton, + textStyle: _styleTextConfirmButton, + action: _onConfirmButtonAction)) ] )) ]) diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index 0ddc7dbce..139784b2c 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -125,9 +125,6 @@ class ComposerBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), - Get.find(), - Get.find(), - Get.find(), Get.find(), Get.find(), Get.find(), diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 9bcd73e59..461db1c84 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -9,6 +9,7 @@ import 'package:file_picker/file_picker.dart'; import 'package:fk_user_agent/fk_user_agent.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:html_editor_enhanced/html_editor.dart' as HtmlEditorBrowser; import 'package:http_parser/http_parser.dart'; @@ -52,7 +53,10 @@ import 'package:uuid/uuid.dart'; class ComposerController extends BaseController { final mailboxDashBoardController = Get.find(); - + final _appToast = Get.find(); + final _imagePaths = Get.find(); + final _responsiveUtils = Get.find(); + final expandMode = ExpandMode.COLLAPSE.obs; final expandModeMobile = ExpandMode.EXPAND.obs; final expandModeAttachments = ExpandMode.EXPAND.obs; @@ -61,16 +65,17 @@ class ComposerController extends BaseController { final isInitialRecipient = false.obs; final attachments = [].obs; final emailContents = Rxn>(); + final listEmailAddressType = [].obs; final subjectEmail = Rxn(); final screenDisplayMode = ScreenDisplayMode.normal.obs; + final toAddressExpandMode = ExpandMode.EXPAND.obs; + final ccAddressExpandMode = ExpandMode.EXPAND.obs; + final bccAddressExpandMode = ExpandMode.EXPAND.obs; final SendEmailInteractor _sendEmailInteractor; final SaveEmailAddressesInteractor _saveEmailAddressInteractor; final GetAutoCompleteInteractor _getAutoCompleteInteractor; final GetAutoCompleteWithDeviceContactInteractor _getAutoCompleteWithDeviceContactInteractor; - final AppToast _appToast; - final ImagePaths _imagePaths; - final ResponsiveUtils responsiveUtils; final Uuid _uuid; final LocalFilePickerInteractor _localFilePickerInteractor; final UploadMultipleAttachmentInteractor _uploadMultipleAttachmentInteractor; @@ -90,6 +95,7 @@ class ComposerController extends BaseController { final toEmailAddressController = TextEditingController(); final ccEmailAddressController = TextEditingController(); final bccEmailAddressController = TextEditingController(); + final toEmailAddressFocusNode = FocusNode(); List initialAttachments = []; String? _textEditorWeb; @@ -117,9 +123,6 @@ class ComposerController extends BaseController { this._saveEmailAddressInteractor, this._getAutoCompleteInteractor, this._getAutoCompleteWithDeviceContactInteractor, - this._appToast, - this._imagePaths, - this.responsiveUtils, this._uuid, this._deviceInfoPlugin, this._localFilePickerInteractor, @@ -151,6 +154,7 @@ class ComposerController extends BaseController { toEmailAddressController.dispose(); ccEmailAddressController.dispose(); bccEmailAddressController.dispose(); + toEmailAddressFocusNode.dispose(); super.onClose(); } @@ -241,11 +245,6 @@ class ComposerController extends BaseController { return null; } - void expandEmailAddressAction() { - final newExpandMode = expandMode.value == ExpandMode.COLLAPSE ? ExpandMode.EXPAND : ExpandMode.COLLAPSE; - expandMode.value = newExpandMode; - } - void _initToEmailAddress(ComposerArguments arguments) { final userProfile = mailboxDashBoardController.userProfile.value; if (arguments.presentationEmail != null && userProfile != null) { @@ -266,6 +265,14 @@ class ComposerController extends BaseController { if (listToEmailAddress.isNotEmpty || listCcEmailAddress.isNotEmpty || listBccEmailAddress.isNotEmpty) { isInitialRecipient.value = true; } + + if (listCcEmailAddress.isNotEmpty) { + listEmailAddressType.add(PrefixEmailAddress.cc); + } + + if (listBccEmailAddress.isNotEmpty) { + listEmailAddressType.add(PrefixEmailAddress.bcc); + } } else if (arguments.emailAddress != null) { listToEmailAddress.add(arguments.emailAddress!); isInitialRecipient.value = true; @@ -377,15 +384,45 @@ class ComposerController extends BaseController { void sendEmailAction(BuildContext context) async { clearFocusEditor(context); - if (isEnableEmailSendButton.value) { - if (subjectEmail.value?.isNotEmpty == true) { - _handleSendMessages(context); - } else { - _showConfirmDialogSendEmail(context); - } - } else { - _appToast.showErrorToast(AppLocalizations.of(context).your_email_should_have_at_least_one_recipient); + if (!isEnableEmailSendButton.value) { + _showConfirmDialogSendEmailFailed( + context, + AppLocalizations.of(context).message_dialog_send_email_without_recipient, + AppLocalizations.of(context).add_recipients, + () => toEmailAddressFocusNode.requestFocus(), + hasCancelButton: false); + return; } + + final allListEmailAddress = listToEmailAddress + listCcEmailAddress + listBccEmailAddress; + final listEmailAddressInvalid = allListEmailAddress + .where((emailAddress) => !GetUtils.isEmail(emailAddress.emailAddress)) + .toList(); + + if (listEmailAddressInvalid.isNotEmpty) { + _showConfirmDialogSendEmailFailed( + context, + AppLocalizations.of(context).message_dialog_send_email_with_email_address_invalid, + AppLocalizations.of(context).fix_email_addresses, + () { + toAddressExpandMode.value = ExpandMode.EXPAND; + ccAddressExpandMode.value = ExpandMode.EXPAND; + bccAddressExpandMode.value = ExpandMode.EXPAND; + }, + hasCancelButton: false); + return; + } + + if (subjectEmail.value == null || subjectEmail.isEmpty == true) { + _showConfirmDialogSendEmailFailed( + context, + AppLocalizations.of(context).message_dialog_send_email_without_a_subject, + AppLocalizations.of(context).send_anyway, + () => _handleSendMessages(context)); + return; + } + + _handleSendMessages(context); } void _handleSendMessages(BuildContext context) async { @@ -646,23 +683,20 @@ class ComposerController extends BaseController { if (!kIsWeb) { htmlEditorApi?.unfocus(context); htmlControllerBrowser.clearFocus(); + } else { + FocusManager.instance.primaryFocus?.unfocus(); } } void closeComposerWeb() { + FocusManager.instance.primaryFocus?.unfocus(); mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.none); } - void minimizeComposer() { + void displayScreenTypeComposerAction(ScreenDisplayMode displayMode) { + FocusManager.instance.primaryFocus?.unfocus(); _updateTextForEditor(); - final newDisplayMode = screenDisplayMode.value == ScreenDisplayMode.minimize ? ScreenDisplayMode.normal : ScreenDisplayMode.minimize; - screenDisplayMode.value = newDisplayMode; - } - - void setFullScreenComposer() { - _updateTextForEditor(); - final newDisplayMode = screenDisplayMode.value == ScreenDisplayMode.fullScreen ? ScreenDisplayMode.normal : ScreenDisplayMode.fullScreen; - screenDisplayMode.value = newDisplayMode; + screenDisplayMode.value = displayMode; } void _updateTextForEditor() async { @@ -671,6 +705,7 @@ class ComposerController extends BaseController { } void deleteComposer() { + FocusManager.instance.primaryFocus?.unfocus(); mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.none); } @@ -679,15 +714,16 @@ class ComposerController extends BaseController { expandModeAttachments.value = newExpandMode; } - void _showConfirmDialogSendEmail(BuildContext context) { - if (responsiveUtils.isMobileDevice(context)) { + void _showConfirmDialogSendEmailFailed(BuildContext context, String message, + String actionName, Function? onConfirmAction, {bool hasCancelButton = true}) { + if (_responsiveUtils.isMobile(context)) { (ConfirmationDialogActionSheetBuilder(context) - ..messageText(AppLocalizations.of(context).message_confirm_send_email) + ..messageText(message) ..styleConfirmButton(TextStyle(fontSize: 20, fontWeight: FontWeight.normal, color: Colors.black)) ..onCancelAction(AppLocalizations.of(context).cancel, () => popBack()) - ..onConfirmAction(AppLocalizations.of(context).send, () { + ..onConfirmAction(actionName, () { popBack(); - _handleSendMessages(context); + onConfirmAction?.call(); })).show(); } else { showDialog( @@ -695,17 +731,25 @@ class ComposerController extends BaseController { barrierColor: AppColor.colorDefaultCupertinoActionSheet, builder: (BuildContext context) => PointerInterceptor(child: (ConfirmDialogBuilder(_imagePaths) ..key(Key('confirm_dialog_send_message')) - ..content(AppLocalizations.of(context).message_confirm_send_email) + ..title(AppLocalizations.of(context).sending_failed) + ..content(message) + ..addIcon(SvgPicture.asset(_imagePaths.icSendToastError, fit: BoxFit.fill)) ..colorConfirmButton(AppColor.colorTextButton) ..colorCancelButton(AppColor.colorCancelButton) - ..styleContent(TextStyle(fontSize: 15, fontWeight: FontWeight.normal, color: AppColor.colorMessageDialog)) + ..paddingTitle(EdgeInsets.only(top: 34)) + ..marginIcon(EdgeInsets.zero) + ..paddingContent(EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 8)) + ..paddingButton(hasCancelButton ? null : EdgeInsets.only(bottom: 16, left: 44, right: 44)) + ..styleTitle(TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black)) + ..styleContent(TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail)) ..styleTextCancelButton(TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton)) ..styleTextConfirmButton(TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Colors.white)) - ..onConfirmButtonAction(AppLocalizations.of(context).send, () { + ..onConfirmButtonAction(actionName, () { popBack(); - _handleSendMessages(context); + onConfirmAction?.call(); }) - ..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack())) + ..onCancelButtonAction(hasCancelButton ? AppLocalizations.of(context).cancel : '', () => popBack()) + ..onCloseButtonAction(() => popBack())) .build())); } } @@ -714,6 +758,73 @@ class ComposerController extends BaseController { expandModeMobile.value = expandMode; } + void addEmailAddressType(PrefixEmailAddress prefixEmailAddress) { + listEmailAddressType.add(prefixEmailAddress); + } + + void deleteEmailAddressType(PrefixEmailAddress prefixEmailAddress) { + listEmailAddressType.remove(prefixEmailAddress); + updateListEmailAddress(prefixEmailAddress, List.empty()); + } + + void onSubjectEmailFocusChange(bool isFocus) { + log('ComposerController::onSubjectEmailFocusChange(): Focus: $isFocus'); + if (isFocus) { + toAddressExpandMode.value = ExpandMode.COLLAPSE; + ccAddressExpandMode.value = ExpandMode.COLLAPSE; + bccAddressExpandMode.value = ExpandMode.COLLAPSE; + } + } + + void onEditorFocusChange(bool isFocus) { + log('ComposerController::onEditorFocusChange(): Focus: $isFocus'); + if (isFocus) { + toAddressExpandMode.value = ExpandMode.COLLAPSE; + ccAddressExpandMode.value = ExpandMode.COLLAPSE; + bccAddressExpandMode.value = ExpandMode.COLLAPSE; + } + } + + void showFullEmailAddress(PrefixEmailAddress prefixEmailAddress) { + switch(prefixEmailAddress) { + case PrefixEmailAddress.to: + toAddressExpandMode.value = ExpandMode.EXPAND; + break; + case PrefixEmailAddress.cc: + ccAddressExpandMode.value = ExpandMode.EXPAND; + break; + case PrefixEmailAddress.bcc: + bccAddressExpandMode.value = ExpandMode.EXPAND; + break; + default: + break; + } + } + + void onEmailAddressFocusChange(PrefixEmailAddress prefixEmailAddress, bool isFocus) { + if (isFocus) { + switch(prefixEmailAddress) { + case PrefixEmailAddress.to: + toAddressExpandMode.value = ExpandMode.EXPAND; + ccAddressExpandMode.value = ExpandMode.COLLAPSE; + bccAddressExpandMode.value = ExpandMode.COLLAPSE; + break; + case PrefixEmailAddress.cc: + ccAddressExpandMode.value = ExpandMode.EXPAND; + toAddressExpandMode.value = ExpandMode.COLLAPSE; + bccAddressExpandMode.value = ExpandMode.COLLAPSE; + break; + case PrefixEmailAddress.bcc: + bccAddressExpandMode.value = ExpandMode.EXPAND; + toAddressExpandMode.value = ExpandMode.COLLAPSE; + ccAddressExpandMode.value = ExpandMode.COLLAPSE; + break; + default: + break; + } + } + } + void closeComposer(BuildContext context) { popBack(); } diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 43f5ee53b..937848c26 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -18,7 +18,6 @@ class ComposerView extends GetWidget { final responsiveUtils = Get.find(); final imagePaths = Get.find(); - final _appToast = Get.find(); final keyboardUtils = Get.find(); @override @@ -128,7 +127,7 @@ class ComposerView extends GetWidget { onTap: () => controller.openPickAttachmentMenu(context, _pickAttachmentsActionTiles(context))), if (expandModeMobile == ExpandMode.COLLAPSE) buildIconWeb( - icon: SvgPicture.asset(isEnableSendButton ? imagePaths.icSendMobile : imagePaths.icSendMobileDisable, fit: BoxFit.fill), + icon: SvgPicture.asset(isEnableSendButton ? imagePaths.icSendMobile : imagePaths.icSendDisable, fit: BoxFit.fill), tooltip: AppLocalizations.of(context).send, onTap: () => controller.sendEmailAction(context)), ]); @@ -145,7 +144,7 @@ class ComposerView extends GetWidget { buildIconWeb( iconSize: 36, iconPadding: EdgeInsets.zero, - icon: SvgPicture.asset(isEnableSendButton ? imagePaths.icSendMobile : imagePaths.icSendMobileDisable, width: 36, height: 36, fit: BoxFit.fill), + icon: SvgPicture.asset(isEnableSendButton ? imagePaths.icSendMobile : imagePaths.icSendDisable, width: 36, height: 36, fit: BoxFit.fill), tooltip: AppLocalizations.of(context).send, onTap: () => controller.sendEmailAction(context)), ]); @@ -182,13 +181,11 @@ class ComposerView extends GetWidget { Obx(() => (EmailAddressInputBuilder( context, imagePaths, - _appToast, PrefixEmailAddress.to, controller.listToEmailAddress, + controller.listEmailAddressType, controller: controller.toEmailAddressController, - isInitial: controller.isInitialRecipient.value, - expandMode: controller.expandMode.value) - ..addExpandAddressActionClick(() => controller.expandEmailAddressAction()) + isInitial: controller.isInitialRecipient.value) ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) ..addOnSuggestionEmailAddress((word) => controller.getAutoCompleteSuggestion(word))) .build() @@ -200,9 +197,9 @@ class ComposerView extends GetWidget { ? (EmailAddressInputBuilder( context, imagePaths, - _appToast, PrefixEmailAddress.cc, controller.listCcEmailAddress, + controller.listEmailAddressType, controller: controller.ccEmailAddressController, isInitial: controller.isInitialRecipient.value,) ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) @@ -217,9 +214,9 @@ class ComposerView extends GetWidget { ? (EmailAddressInputBuilder( context, imagePaths, - _appToast, PrefixEmailAddress.bcc, controller.listBccEmailAddress, + controller.listEmailAddressType, controller: controller.bccEmailAddressController, isInitial: controller.isInitialRecipient.value,) ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 68d385d3e..e943b7cb3 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -2,6 +2,7 @@ import 'package:core/core.dart'; import 'package:file_picker/file_picker.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; @@ -28,87 +29,78 @@ class ComposerView extends GetWidget { responsiveUtils: responsiveUtils, mobile: Scaffold( backgroundColor: Colors.white, - body: Align(alignment: Alignment.center, child: Card( - color: Colors.transparent, - child: Container( - color: Colors.white, - child: PointerInterceptor(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Obx(() => _buildAppBarForMobile(context, controller.isEnableEmailSendButton.value)), - SizedBox(height: 16), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildEmailAddress(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildSubjectEmail(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Expanded(child: Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: _buildEditorAndAttachments(context))), - ] - )) - ) - )) + body: Container( + color: Colors.white, + child: LayoutBuilder(builder: (context, constraints) => PointerInterceptor(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Obx(() => _buildAppBarForMobile(context, controller.isEnableEmailSendButton.value)), + Divider(color: AppColor.colorDividerComposer, height: 1), + _buildEmailAddress(context, constraints), + Divider(color: AppColor.colorDividerComposer, height: 1), + Padding(padding: EdgeInsets.symmetric(horizontal: 16), child: _buildSubjectEmail(context)), + Divider(color: AppColor.colorDividerComposer, height: 1), + Padding(padding: EdgeInsets.symmetric(horizontal: 16), child: _buildListButton(context)), + Divider(color: AppColor.colorDividerComposer, height: 1), + Expanded(child: _buildEditorAndAttachments(context)), + ] + ))) + ) ), desktop: Obx(() { return Stack(children: [ if (controller.screenDisplayMode == ScreenDisplayMode.normal) - Positioned(right: 10, bottom: 10, child: Card( + Positioned(right: 5, bottom: 5, child: Card( elevation: 20, color: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))), child: Container( - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(24))), width: responsiveUtils.getSizeScreenWidth(context) * 0.55, - height: responsiveUtils.getSizeScreenHeight(context) * 0.75, - child: PointerInterceptor(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 5), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildAppBar(context)), - SizedBox(height: 10), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildTitleComposer(context)), - SizedBox(height: 16), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildEmailAddress(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildSubjectEmail(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Expanded(child: Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: _buildEditorAndAttachments(context))), - Obx(() => _buildBottomBar(context, controller.isEnableEmailSendButton.value)), - ] - ) + height: responsiveUtils.getSizeScreenHeight(context) * 0.7, + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(24)), + child: LayoutBuilder(builder: (context, constraints) => + PointerInterceptor(child: _buildBodyForDesktop(context, constraints))) ) ) )), if (controller.screenDisplayMode == ScreenDisplayMode.minimize) - Positioned(right: 10, bottom: 10, child: Card( + Positioned(right: 5, bottom: 5, child: Card( elevation: 20, color: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), child: Container( - decoration: BoxDecoration(color: AppColor.colorTextButton, borderRadius: BorderRadius.all(Radius.circular(20))), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(16))), width: 500, height: 50, - child: PointerInterceptor(child: Row( - children: [ - Expanded(child: Padding( - padding: EdgeInsets.only(left: 20, right: 16), - child: _buildTitleComposer(context), - )), - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icMinimizeComposer, color: Colors.white, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).minimize, - onTap: () => controller.minimizeComposer()), - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icFullScreenComposer, color: Colors.white, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).fullscreen, - onTap: () => controller.setFullScreenComposer()), - Padding( - padding: EdgeInsets.only(right: 8), - child: buildIconWeb( - icon: SvgPicture.asset(imagePaths.icCloseComposer, color: Colors.white, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).close, - onTap: () { - controller.saveEmailAsDrafts(context); - controller.closeComposerWeb(); - })), - ] - )) + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(16)), + child: PointerInterceptor(child: Row(children: [ + Padding( + padding: EdgeInsets.only(left: 10), + child: buildIconWeb( + icon: SvgPicture.asset(imagePaths.icCloseMailbox, fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).close, + onTap: () { + controller.saveEmailAsDrafts(context); + controller.closeComposerWeb(); + } + )), + buildIconWeb( + icon: SvgPicture.asset(imagePaths.icFullScreenComposer, fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).fullscreen, + onTap: () => controller.displayScreenTypeComposerAction(ScreenDisplayMode.fullScreen)), + buildIconWeb( + icon: SvgPicture.asset(imagePaths.icChevronUp, fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).show, + onTap: () => controller.displayScreenTypeComposerAction(ScreenDisplayMode.normal)), + Expanded(child: Padding( + padding: EdgeInsets.only(left: 16, right: 80), + child: _buildTitleComposer(context), + )), + ])) + ) ) )), if (controller.screenDisplayMode == ScreenDisplayMode.fullScreen) @@ -116,29 +108,15 @@ class ComposerView extends GetWidget { backgroundColor: Colors.black38, body: Align(alignment: Alignment.center, child: Card( color: Colors.transparent, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))), child: Container( - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(24))), width: responsiveUtils.getSizeScreenWidth(context) * 0.85, height: responsiveUtils.getSizeScreenHeight(context) * 0.9, child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(20)), - child: PointerInterceptor(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 5), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildAppBar(context)), - SizedBox(height: 10), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildTitleComposer(context)), - SizedBox(height: 16), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildEmailAddress(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildSubjectEmail(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Expanded(child: Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: _buildEditorAndAttachments(context))), - Obx(() => _buildBottomBar(context, controller.isEnableEmailSendButton.value)), - ] - )) + borderRadius: BorderRadius.all(Radius.circular(24)), + child: LayoutBuilder(builder: (context, constraints) => + PointerInterceptor(child: _buildBodyForDesktop(context, constraints))) ) ) ) @@ -150,27 +128,30 @@ class ComposerView extends GetWidget { backgroundColor: Colors.black38, body: Align(alignment: Alignment.center, child: Card( color: Colors.transparent, + shadowColor: Colors.transparent, child: Container( - decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))), + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(24))), margin: EdgeInsets.symmetric(horizontal: 16, vertical: 20), child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(20)), - child: PointerInterceptor(child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 5), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildAppBar(context)), - SizedBox(height: 10), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildTitleComposer(context)), - SizedBox(height: 16), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildEmailAddress(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: _buildSubjectEmail(context)), - Padding(padding: EdgeInsets.symmetric(horizontal: 20), child: Divider(color: AppColor.colorDividerComposer, height: 1)), - Expanded(child: Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: _buildEditorAndAttachments(context))), - Obx(() => _buildBottomBar(context, controller.isEnableEmailSendButton.value)), - ] - )) + borderRadius: BorderRadius.all(Radius.circular(24)), + child: LayoutBuilder(builder: (context, constraints) => + PointerInterceptor(child: _buildBodyForDesktop(context, constraints))) + ) + ) + )) + ), + tabletLarge: Scaffold( + backgroundColor: Colors.black38, + body: Align(alignment: Alignment.center, child: Card( + color: Colors.transparent, + shadowColor: Colors.transparent, + child: Container( + decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(24))), + margin: EdgeInsets.symmetric(horizontal: 16, vertical: 20), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(24)), + child: LayoutBuilder(builder: (context, constraints) => + PointerInterceptor(child: _buildBodyForDesktop(context, constraints))) ) ) )) @@ -179,55 +160,55 @@ class ComposerView extends GetWidget { } Widget _buildAppBar(BuildContext context) { - return Transform( - transform: Matrix4.translationValues(15.0, 0.0, 0.0), - child: Row( - children: [ - Spacer(), - if (responsiveUtils.isDesktop(context)) - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icMinimizeComposer, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).minimize, - onTap: () => controller.minimizeComposer()), - if (responsiveUtils.isDesktop(context)) - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icFullScreenComposer, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).fullscreen, - onTap: () => controller.setFullScreenComposer()), - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icCloseComposer, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).close, - onTap: () { - controller.saveEmailAsDrafts(context); - controller.closeComposerWeb(); - }), - ] - ), + return Row( + children: [ + buildIconWeb( + icon: SvgPicture.asset(imagePaths.icCloseMailbox, fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).close, + onTap: () { + controller.saveEmailAsDrafts(context); + controller.closeComposerWeb(); + }), + if (responsiveUtils.isDesktop(context)) + Obx(() => buildIconWeb( + icon: SvgPicture.asset( + controller.screenDisplayMode == ScreenDisplayMode.fullScreen + ? imagePaths.icFullScreenExit + : imagePaths.icFullScreenComposer, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).fullscreen, + onTap: () => controller.displayScreenTypeComposerAction(controller.screenDisplayMode == ScreenDisplayMode.fullScreen + ? ScreenDisplayMode.normal + : ScreenDisplayMode.fullScreen))), + if (responsiveUtils.isDesktop(context)) + buildIconWeb( + icon: SvgPicture.asset(imagePaths.icMinimize, fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).minimize, + onTap: () => controller.displayScreenTypeComposerAction(ScreenDisplayMode.minimize)), + Expanded(child: _buildTitleComposer(context)), + SizedBox(width: 100), + ] ); } Widget _buildAppBarForMobile(BuildContext context, bool isEnableSendButton) { return Container( - padding: EdgeInsets.only(left: 10, right: 10, top: 10), + padding: EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 10), color: Colors.white, child: Row( children: [ buildIconWeb( - icon: SvgPicture.asset(imagePaths.icCloseComposer, fit: BoxFit.fill), + icon: SvgPicture.asset(imagePaths.icClose, width: 30, height: 30, fit: BoxFit.fill), tooltip: AppLocalizations.of(context).close, + iconPadding: EdgeInsets.zero, onTap: () { controller.saveEmailAsDrafts(context); controller.closeComposerWeb(); }), - Spacer(), - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icAttachmentsComposer, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).pick_attachments, - onTap: () => controller.openFilePickerByType(context, FileType.any)), + Expanded(child: _buildTitleComposer(context)), buildIconWeb( icon: SvgPicture.asset( - imagePaths.icSendComposer, - color: isEnableSendButton ? AppColor.colorTextButton : AppColor.colorDisableMailboxCreateButton, + isEnableSendButton ? imagePaths.icSendMobile : imagePaths.icSendDisable, fit: BoxFit.fill), tooltip: AppLocalizations.of(context).send, onTap: () => controller.sendEmailAction(context)), @@ -238,133 +219,157 @@ class ComposerView extends GetWidget { Widget _buildBottomBar(BuildContext context, bool isEnableSendButton) { return Container( - padding: EdgeInsets.only(left: 10, right: 20, top: 18, bottom: 18), - color: AppColor.colorBottomBarComposer, - child: Row( + padding: EdgeInsets.symmetric(vertical: 16), + color: Colors.white, + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icDeleteComposer, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).delete, - onTap: () => controller.deleteComposer()), - Spacer(), - buildIconWeb( - icon: SvgPicture.asset(imagePaths.icAttachmentsComposer, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).pick_attachments, - onTap: () => controller.openFilePickerByType(context, FileType.any)), - SizedBox(width: 16), - SizedBox( - width: 102, - height: 36, - child: ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith( - (states) => isEnableSendButton ? AppColor.colorTextButton : AppColor.colorDisableMailboxCreateButton), - padding: MaterialStateProperty.resolveWith((states) => EdgeInsets.symmetric(horizontal: 16, vertical: 10)), - elevation: MaterialStateProperty.resolveWith((states) => 0), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide(width: 0, color: isEnableSendButton ? AppColor.colorTextButton : AppColor.colorDisableMailboxCreateButton)))), - child: Row(mainAxisSize: MainAxisSize.min, children: [ - Text(AppLocalizations.of(context).send.toUpperCase(), style: TextStyle(fontSize: 17, color: Colors.white, fontWeight: FontWeight.w500)), - SizedBox(width: 5), - SvgPicture.asset(imagePaths.icSendComposer, width: 18, height: 18, fit: BoxFit.fill), - ]), - onPressed: () => controller.sendEmailAction(context) - ), - ) + buildTextButton( + AppLocalizations.of(context).cancel, + textStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: 17, color: AppColor.lineItemListColor), + backgroundColor: AppColor.emailAddressChipColor, + width: 150, + height: 44, + radius: 10, + onTap: () => controller.closeComposerWeb()), + SizedBox(width: 12), + buildTextButton( + AppLocalizations.of(context).save_to_drafts, + textStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: 17, color: AppColor.colorTextButton), + backgroundColor: AppColor.emailAddressChipColor, + width: 150, + height: 44, + radius: 10, + onTap: () { + controller.saveEmailAsDrafts(context); + controller.closeComposerWeb(); + }), + SizedBox(width: 12), + buildTextButton( + AppLocalizations.of(context).send, + width: 150, + height: 44, + radius: 10, + onTap: () => controller.sendEmailAction(context)), ] ), ); } + Widget _buildBodyForDesktop(BuildContext context, BoxConstraints constraints) { + return Column(children: [ + Padding(padding: EdgeInsets.only(left: 20, right: 20, top: 8), child: _buildAppBar(context)), + Padding(padding: EdgeInsets.only(top: 8), child: Divider(color: AppColor.colorDividerComposer, height: 1)), + Padding( + padding: EdgeInsets.only(left: 16), + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Padding(padding: EdgeInsets.only(top: 20), + child: (AvatarBuilder() + ..text('${controller.mailboxDashBoardController.userProfile.value?.getAvatarText() ?? ''}') + ..size(56) + ..addTextStyle(TextStyle(fontWeight: FontWeight.w600, fontSize: 28, color: Colors.white)) + ..backgroundColor(AppColor.colorAvatar)) + .build()), + Expanded(child: Padding( + padding: EdgeInsets.only(left: 12), + child: Column(children: [ + _buildEmailAddress(context, constraints), + Divider(color: AppColor.colorDividerComposer, height: 1), + Padding(padding: EdgeInsets.only(right: 16), child: _buildSubjectEmail(context)), + Divider(color: AppColor.colorDividerComposer, height: 1), + _buildListButton(context), + ]), + )) + ])), + Divider(color: AppColor.colorDividerComposer, height: 1), + Expanded(child: Padding( + padding: EdgeInsets.only(left: responsiveUtils.isMobile(context) ? 16 : 60, right: responsiveUtils.isMobile(context) ? 16 : 25), + child: _buildEditorAndAttachments(context))), + Divider(color: AppColor.colorDividerComposer, height: 1), + Obx(() => _buildBottomBar(context, controller.isEnableEmailSendButton.value)), + ]); + } + Widget _buildTitleComposer(BuildContext context) { return Obx(() => Text( controller.subjectEmail.isNotEmpty == true ? controller.subjectEmail.value ?? '' : AppLocalizations.of(context).new_message.capitalizeFirstEach, - overflow: TextOverflow.ellipsis, + overflow: kIsWeb ? null : TextOverflow.ellipsis, maxLines: 1, - style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500, - color: controller.screenDisplayMode == ScreenDisplayMode.minimize ? Colors.white : Colors.black), + textAlign: TextAlign.center, + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black), )); } - Widget _buildEmailAddress(BuildContext context) { - return Column( - children: [ - Padding( - padding: EdgeInsets.only(bottom: 12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, + Widget _buildEmailAddress(BuildContext context, BoxConstraints constraints) { + log('ComposerView::_buildEmailAddress(): height: ${constraints.maxHeight}'); + return ConstrainedBox( + constraints: BoxConstraints(maxHeight: _getMaxHeightEmailAddressWidget(context, constraints)), + child: SingleChildScrollView( + child: Column( children: [ - Padding( - padding: EdgeInsets.only(right: 8), - child: Text( - '${AppLocalizations.of(context).from_email_address_prefix}:', - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: TextStyle(fontSize: 15, color: AppColor.colorHintEmailAddressInput))), - Expanded( - child: Obx(() => controller.composerArguments.value != null - ? Text( - '<${controller.getEmailAddressSender()}>', - style: TextStyle(fontSize: 16, color: AppColor.colorContentEmail, fontWeight: FontWeight.normal)) - : SizedBox.shrink() - ) - ) - ] - ) - ), - Divider(color: AppColor.colorDividerComposer, height: 1), - Obx(() => (EmailAddressInputBuilder( - context, - imagePaths, - appToast, - PrefixEmailAddress.to, - controller.listToEmailAddress, - controller: controller.toEmailAddressController, - isInitial: controller.isInitialRecipient.value, - expandMode: controller.expandMode.value) - ..addExpandAddressActionClick(() => controller.expandEmailAddressAction()) - ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) - ..addOnSuggestionEmailAddress((word) => controller.getAutoCompleteSuggestion(word))) - .build() - ), - Obx(() => controller.expandMode.value == ExpandMode.EXPAND - ? Divider(color: AppColor.colorDividerComposer, height: 1) - : SizedBox.shrink()), - Obx(() => controller.expandMode.value == ExpandMode.EXPAND - ? (EmailAddressInputBuilder( - context, - imagePaths, - appToast, - PrefixEmailAddress.cc, - controller.listCcEmailAddress, - controller: controller.ccEmailAddressController, - isInitial: controller.isInitialRecipient.value,) - ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) - ..addOnSuggestionEmailAddress((word) => controller.getAutoCompleteSuggestion(word))) - .build() - : SizedBox.shrink() - ), - Obx(() => controller.expandMode.value == ExpandMode.EXPAND - ? Divider(color: AppColor.colorDividerComposer, height: 1) - : SizedBox.shrink()), - Obx(() => controller.expandMode.value == ExpandMode.EXPAND - ? (EmailAddressInputBuilder( - context, - imagePaths, - appToast, - PrefixEmailAddress.bcc, - controller.listBccEmailAddress, - controller: controller.bccEmailAddressController, - isInitial: controller.isInitialRecipient.value,) - ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) - ..addOnSuggestionEmailAddress((word) => controller.getAutoCompleteSuggestion(word))) - .build() - : SizedBox.shrink() - ), - ], + Obx(() => Padding( + padding: EdgeInsets.only(left: responsiveUtils.isMobile(context) ? 16 : 0), + child: (EmailAddressInputBuilder(context, imagePaths, + PrefixEmailAddress.to, + controller.listToEmailAddress, + controller.listEmailAddressType, + expandMode: controller.toAddressExpandMode.value, + controller: controller.toEmailAddressController, + focusNode: controller.toEmailAddressFocusNode, + isInitial: controller.isInitialRecipient.value) + ..addOnFocusEmailAddressChangeAction((prefixEmailAddress, focus) => controller.onEmailAddressFocusChange(prefixEmailAddress, focus)) + ..addOnShowFullListEmailAddressAction((prefixEmailAddress) => controller.showFullEmailAddress(prefixEmailAddress)) + ..addOnAddEmailAddressTypeAction((prefixEmailAddress) => controller.addEmailAddressType(prefixEmailAddress)) + ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) + ..addOnSuggestionEmailAddress((word) => controller.getAutoCompleteSuggestion(word))) + .build() + )), + Obx(() => controller.listEmailAddressType.contains(PrefixEmailAddress.cc) == true + ? Divider(color: AppColor.colorDividerComposer, height: 1) + : SizedBox.shrink()), + Obx(() => controller.listEmailAddressType.contains(PrefixEmailAddress.cc) == true + ? Padding( + padding: EdgeInsets.only(left: responsiveUtils.isMobile(context) ? 16 : 0), + child: (EmailAddressInputBuilder(context, imagePaths, + PrefixEmailAddress.cc, + controller.listCcEmailAddress, + controller.listEmailAddressType, + expandMode: controller.ccAddressExpandMode.value, + controller: controller.ccEmailAddressController, + isInitial: controller.isInitialRecipient.value,) + ..addOnFocusEmailAddressChangeAction((prefixEmailAddress, focus) => controller.onEmailAddressFocusChange(prefixEmailAddress, focus)) + ..addOnShowFullListEmailAddressAction((prefixEmailAddress) => controller.showFullEmailAddress(prefixEmailAddress)) + ..addOnDeleteEmailAddressTypeAction((prefixEmailAddress) => controller.deleteEmailAddressType(prefixEmailAddress)) + ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) + ..addOnSuggestionEmailAddress((word) => controller.getAutoCompleteSuggestion(word))) + .build()) + : SizedBox.shrink() + ), + Obx(() => controller.listEmailAddressType.contains(PrefixEmailAddress.bcc) == true + ? Divider(color: AppColor.colorDividerComposer, height: 1) + : SizedBox.shrink()), + Obx(() => controller.listEmailAddressType.contains(PrefixEmailAddress.bcc) == true + ? Padding( + padding: EdgeInsets.only(left: responsiveUtils.isMobile(context) ? 16 : 0), + child: (EmailAddressInputBuilder(context, imagePaths, + PrefixEmailAddress.bcc, + controller.listBccEmailAddress, + controller.listEmailAddressType, + expandMode: controller.bccAddressExpandMode.value, + controller: controller.bccEmailAddressController, + isInitial: controller.isInitialRecipient.value,) + ..addOnFocusEmailAddressChangeAction((prefixEmailAddress, focus) => controller.onEmailAddressFocusChange(prefixEmailAddress, focus)) + ..addOnShowFullListEmailAddressAction((prefixEmailAddress) => controller.showFullEmailAddress(prefixEmailAddress)) + ..addOnDeleteEmailAddressTypeAction((prefixEmailAddress) => controller.deleteEmailAddressType(prefixEmailAddress)) + ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress)) + ..addOnSuggestionEmailAddress((word) => controller.getAutoCompleteSuggestion(word))) + .build()) + : SizedBox.shrink() + ), + ], + ), + ) ); } @@ -377,61 +382,74 @@ class ComposerView extends GetWidget { child: Text( '${AppLocalizations.of(context).subject_email}:', style: TextStyle(fontSize: 15, color: AppColor.colorHintEmailAddressInput))), - Expanded(child: (TextFieldBuilder() - ..key(Key('subject_email_input')) - ..cursorColor(AppColor.colorTextButton) - ..onChange((value) => controller.setSubjectEmail(value)) - ..textStyle(TextStyle(color: AppColor.colorEmailAddress, fontSize: 15)) - ..textDecoration(InputDecoration(contentPadding: EdgeInsets.zero, border: InputBorder.none)) - ..addController(controller.subjectEmailInputController)) - .build() + Expanded( + child: FocusScope(child: Focus( + onFocusChange: (focus) => controller.onSubjectEmailFocusChange(focus), + child: (TextFieldBuilder() + ..key(Key('subject_email_input')) + ..cursorColor(AppColor.colorTextButton) + ..onChange((value) => controller.setSubjectEmail(value)) + ..textStyle(TextStyle(color: Colors.black, fontSize: 15, fontWeight: FontWeight.normal)) + ..textDecoration(InputDecoration(contentPadding: EdgeInsets.zero, border: InputBorder.none)) + ..addController(controller.subjectEmailInputController)) + .build(), + )) ) ] ); } + Widget _buildListButton(BuildContext context) { + return Transform( + transform: Matrix4.translationValues(-10.0, 0.0, 0.0), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 5), + child: Row(children: [ + buildIconWeb( + icon: SvgPicture.asset(imagePaths.icAttachmentsComposer, color: AppColor.colorTextButton, fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).attach_file, + onTap: () => controller.openFilePickerByType(context, FileType.any)), + ]) + ) + ); + } + Widget _buildEditorAndAttachments(BuildContext context) { return Column( children: [ Obx(() => controller.attachments.isNotEmpty ? Padding( - padding: EdgeInsets.symmetric(vertical: 4, horizontal: 10), + padding: EdgeInsets.only(top: 4, bottom: 4, left: responsiveUtils.isMobile(context) ? 16 : 20, right: responsiveUtils.isMobile(context) ? 16: 0), child: _buildAttachmentsTitle(context, controller.attachments, controller.expandModeAttachments.value)) : SizedBox.shrink()), Obx(() => controller.attachments.isEmpty ? _buildAttachmentsLoadingView() : SizedBox.shrink()), - Obx(() => controller.attachments.isNotEmpty && controller.expandModeAttachments.value == ExpandMode.COLLAPSE - ? Padding(padding: EdgeInsets.symmetric(horizontal: 10), child: Divider(color: AppColor.colorDividerComposer, height: 1)) - : SizedBox.shrink()), Obx(() => controller.attachments.isNotEmpty ? Padding( - padding: EdgeInsets.only(bottom: 8, left: 10, right: 10), + padding: EdgeInsets.only(bottom: 8, left: responsiveUtils.isMobile(context) ? 16 : 10, right: responsiveUtils.isMobile(context) ? 16 : 10), child: _buildAttachmentsList(context, controller.attachments, controller.expandModeAttachments.value)) : SizedBox.shrink()), Obx(() { if (controller.composerArguments.value != null) { if (controller.composerArguments.value?.emailActionType == EmailActionType.compose) { final initContent = controller.textEditorWeb ?? ''; - log('ComposerView::_buildEditorAndAttachments(): initContent: $initContent'); return Expanded(child: Padding( - padding: EdgeInsets.symmetric(horizontal: 10), + padding: EdgeInsets.symmetric(horizontal: responsiveUtils.isMobile(context) ? 8 : 10), child: _buildEditor(context, initContent))); } else if (controller.composerArguments.value?.emailActionType == EmailActionType.edit) { - final initContent = controller.getEmailContentDraftsAsHtml(); - log('ComposerView::_buildEditorAndAttachments(): initContent: $initContent'); + final initContent = controller.textEditorWeb ?? controller.getEmailContentDraftsAsHtml(); if (initContent != null) { return Expanded(child: Padding( - padding: EdgeInsets.symmetric(horizontal: 10), + padding: EdgeInsets.symmetric(horizontal: responsiveUtils.isMobile(context) ? 8 : 10), child: _buildEditor(context, initContent))); } else { return SizedBox.shrink(); } } else { - final initContent = controller.getEmailContentQuotedAsHtml(context, controller.composerArguments.value!); - log('ComposerView::_buildEditorAndAttachments(): initContent: $initContent'); + final initContent = controller.textEditorWeb ?? controller.getEmailContentQuotedAsHtml(context, controller.composerArguments.value!); return Expanded(child: Padding( - padding: EdgeInsets.symmetric(horizontal: 10), + padding: EdgeInsets.symmetric(horizontal: responsiveUtils.isMobile(context) ? 8 : 10), child: _buildEditor(context, initContent))); } } else { @@ -443,11 +461,13 @@ class ComposerView extends GetWidget { } Widget _buildEditor(BuildContext context, String initContent) { + log('ComposerView::_buildEditor(): initContent: $initContent'); + return HtmlEditorBrowser.HtmlEditor( key: Key('composer_editor_web'), controller: controller.htmlControllerBrowser, htmlEditorOptions: HtmlEditorBrowser.HtmlEditorOptions( - hint: '

', + hint: '${AppLocalizations.of(context).hint_compose_email}', initialText: initContent, darkMode: false, ), @@ -460,7 +480,7 @@ class ComposerView extends GetWidget { log('ComposerView::_buildComposerEditor(): onBeforeCommand'); controller.setTextEditorWeb(currentHtml); }, onChangeContent: (String? changed) { - log('ComposerView::_buildComposerEditor(): onChangeContent'); + log('ComposerView::_buildComposerEditor(): onChangeContent : $changed'); controller.setTextEditorWeb(changed); }, onInit: () { log('ComposerView::_buildComposerEditor(): onInit'); @@ -470,6 +490,10 @@ class ComposerView extends GetWidget { Future.delayed(const Duration(milliseconds: 500), () { controller.htmlControllerBrowser.setFocus(); }); + }, onBlur: () { + controller.onEditorFocusChange(false); + }, onMouseDown: () { + controller.onEditorFocusChange(true); } ), ); @@ -555,4 +579,16 @@ class ComposerView extends GetWidget { final currentWidth = constraints.maxWidth - 40; return currentWidth / _getMaxItemRowListAttachment(context, constraints); } + + double _getMaxHeightEmailAddressWidget(BuildContext context, BoxConstraints constraints) { + if (responsiveUtils.isDesktop(context)) { + if (controller.screenDisplayMode == ScreenDisplayMode.normal) { + return constraints.maxHeight > 0 ? constraints.maxHeight * 0.25 : 150.0; + } else { + return constraints.maxHeight > 0 ? constraints.maxHeight * 0.3 : 150.0; + } + } else { + return constraints.maxHeight > 0 ? constraints.maxHeight * 0.4 : 150.0; + } + } } \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/email_address_input_builder.dart b/lib/features/composer/presentation/widgets/email_address_input_builder.dart index 4226e99c2..bc939d32e 100644 --- a/lib/features/composer/presentation/widgets/email_address_input_builder.dart +++ b/lib/features/composer/presentation/widgets/email_address_input_builder.dart @@ -12,29 +12,32 @@ import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -typedef OnOpenExpandAddressActionClick = void Function(); typedef OnSuggestionEmailAddress = Future> Function(String word); typedef OnUpdateListEmailAddressAction = void Function(PrefixEmailAddress, List); +typedef OnAddEmailAddressTypeAction = void Function(PrefixEmailAddress); +typedef OnDeleteEmailAddressTypeAction = void Function(PrefixEmailAddress); +typedef OnShowFullListEmailAddressAction = void Function(PrefixEmailAddress); +typedef OnFocusEmailAddressChangeAction = void Function(PrefixEmailAddress, bool); class EmailAddressInputBuilder { final BuildContext _context; final ImagePaths _imagePaths; - final AppToast _appToast; final ExpandMode expandMode; final PrefixEmailAddress _prefixEmailAddress; - List _listEmailAddress; + final List _listEmailAddressType; final TextEditingController? controller; + final FocusNode? focusNode; final bool? isInitial; - final bool hasAvatar; - OnOpenExpandAddressActionClick? _onOpenExpandAddressActionClick; + List _listEmailAddress; + OnUpdateListEmailAddressAction? _onUpdateListEmailAddressAction; OnSuggestionEmailAddress? _onSuggestionEmailAddress; - - void addExpandAddressActionClick(OnOpenExpandAddressActionClick onOpenExpandAddressActionClick) { - _onOpenExpandAddressActionClick = onOpenExpandAddressActionClick; - } + OnAddEmailAddressTypeAction? _onAddEmailAddressTypeAction; + OnDeleteEmailAddressTypeAction? _onDeleteEmailAddressTypeAction; + OnShowFullListEmailAddressAction? _onShowFullListEmailAddressAction; + OnFocusEmailAddressChangeAction? _onFocusEmailAddressChangeAction; void addOnUpdateListEmailAddressAction(OnUpdateListEmailAddressAction onUpdateListEmailAddressAction) { _onUpdateListEmailAddressAction = onUpdateListEmailAddressAction; @@ -44,124 +47,174 @@ class EmailAddressInputBuilder { _onSuggestionEmailAddress = onSuggestionEmailAddress; } + void addOnAddEmailAddressTypeAction(OnAddEmailAddressTypeAction onAddEmailAddressTypeAction) { + _onAddEmailAddressTypeAction = onAddEmailAddressTypeAction; + } + + void addOnDeleteEmailAddressTypeAction(OnDeleteEmailAddressTypeAction onDeleteEmailAddressTypeAction) { + _onDeleteEmailAddressTypeAction = onDeleteEmailAddressTypeAction; + } + + void addOnShowFullListEmailAddressAction(OnShowFullListEmailAddressAction onShowFullListEmailAddressAction) { + _onShowFullListEmailAddressAction = onShowFullListEmailAddressAction; + } + + void addOnFocusEmailAddressChangeAction(OnFocusEmailAddressChangeAction onFocusEmailAddressChangeAction) { + _onFocusEmailAddressChangeAction = onFocusEmailAddressChangeAction; + } + EmailAddressInputBuilder( this._context, this._imagePaths, - this._appToast, this._prefixEmailAddress, this._listEmailAddress, + this._listEmailAddressType, { this.isInitial, - this.hasAvatar = false, this.controller, - this.expandMode = ExpandMode.COLLAPSE, + this.focusNode, + this.expandMode = ExpandMode.EXPAND, } ); Widget build() { return Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, children: [ - Padding( - padding: EdgeInsets.only(right: 8, top: 15), - child: Text( - '${_prefixEmailAddress.asName(_context)}:', - style: TextStyle(fontSize: 15, color: AppColor.colorHintEmailAddressInput))), - Expanded(child: _buildTagEditor()), + Text('${_prefixEmailAddress.asName(_context)}:', + style: TextStyle(fontSize: 15, color: AppColor.colorHintEmailAddressInput)), + buildIconWeb( + icon: SvgPicture.asset(_imagePaths.icAddEmailAddress, fit: BoxFit.fill), + tooltip: AppLocalizations.of(_context).email_address_suggestion, + onTap: () => {}), + Expanded(child: Padding( + padding: EdgeInsets.only(right: _listEmailAddressType.isNotEmpty ? 16 : 0), + child: _buildTagEditor())), if (_prefixEmailAddress == PrefixEmailAddress.to) - Padding( - padding: EdgeInsets.only(top: kIsWeb ? 8 : 0), - child: _buildButtonExpandAddress()) + Row(children: [ + if (!_listEmailAddressType.contains(PrefixEmailAddress.cc)) + Transform( + transform: Matrix4.translationValues( + _listEmailAddressType.contains(PrefixEmailAddress.bcc) ? 0.0 : 16.0, + 0.0, 0.0), + child: buildTextCircleButton(AppLocalizations.of(_context).cc_email_address_prefix, + textStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + decoration: TextDecoration.underline, + color: AppColor.lineItemListColor), + onTap: () => _onAddEmailAddressTypeAction?.call(PrefixEmailAddress.cc))), + if (!_listEmailAddressType.contains(PrefixEmailAddress.bcc)) + buildTextCircleButton(AppLocalizations.of(_context).bcc_email_address_prefix, + textStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.normal, + decoration: TextDecoration.underline, + color: AppColor.lineItemListColor), + onTap: () => _onAddEmailAddressTypeAction?.call(PrefixEmailAddress.bcc)), + ]), + if (_prefixEmailAddress != PrefixEmailAddress.to) + buildIconWeb( + icon: SvgPicture.asset(_imagePaths.icCloseComposer, fit: BoxFit.fill), + onTap: () => _onDeleteEmailAddressTypeAction?.call(_prefixEmailAddress)) ] ); } - Widget _buildButtonExpandAddress() { - return Material( - type: MaterialType.circle, - color: Colors.transparent, - child: TextButton( - child: Text( - expandMode == ExpandMode.EXPAND ? AppLocalizations.of(_context).hide : AppLocalizations.of(_context).details, - style: TextStyle(fontWeight: FontWeight.normal, fontSize: 16, color: AppColor.colorTextButton)), - onPressed: () => _onOpenExpandAddressActionClick?.call() - ) - ); - } - Widget _buildTagEditor() { return StatefulBuilder(builder: (BuildContext context, StateSetter setState) { - return TagEditor( - length: _listEmail.length, - controller: controller, - keyboardType: TextInputType.emailAddress, - textInputAction: TextInputAction.done, - hasAddButton: false, - tagSpacing: 8, - delimiters: [' '], - resetTextOnSubmitted: true, - textStyle: TextStyle(color: AppColor.colorEmailAddress, fontSize: 14, fontWeight: FontWeight.w500), - onSubmitted: (value) { - if (GetUtils.isEmail(value)) { - setState(() => _listEmailAddress.add(EmailAddress(value, value))); - _onUpdateListEmailAddressAction?.call(_prefixEmailAddress, _listEmailAddress); - } else { - _appToast.showErrorToast(AppLocalizations.of(context).email_address_is_not_in_the_correct_format); - } - }, - inputDecoration: InputDecoration( - border: InputBorder.none, - hintText: AppLocalizations.of(_context).hint_text_email_address, - hintMaxLines: 1, - hintStyle: TextStyle(color: AppColor.colorHintEmailAddressInput, fontSize: 14, fontWeight: FontWeight.w500) - ), - tagBuilder: (context, index) => Padding( - padding: EdgeInsets.only(top: kIsWeb ? 8 : 0), - child: Chip( - labelPadding: EdgeInsets.only(left: 12, right: 12, bottom: 2), - label: Text(_listEmail[index], maxLines: 1, overflow: TextOverflow.ellipsis), - deleteIcon: SvgPicture.asset(_imagePaths.icDeleteEmailAddress, fit: BoxFit.fill), - labelStyle: TextStyle(color: AppColor.colorEmailAddress, fontSize: 14, fontWeight: FontWeight.w500), - backgroundColor: AppColor.emailAddressChipColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - side: BorderSide(width: 0, color: AppColor.emailAddressChipColor), + final newListEmailAddress = _isCollapse ? _listEmailAddress.sublist(0, 1) : _listEmailAddress; + return FocusScope(child: Focus( + onFocusChange: (focus) => _onFocusEmailAddressChangeAction?.call(_prefixEmailAddress, focus), + child: TagEditor( + length: newListEmailAddress.length, + controller: controller, + focusNode: focusNode, + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.done, + hasAddButton: false, + tagSpacing: 8, + delimiters: [' '], + minTextFieldWidth: 20, + resetTextOnSubmitted: true, + textStyle: TextStyle(color: AppColor.colorEmailAddress, fontSize: 14, fontWeight: FontWeight.w500), + onSubmitted: (value) { + setState(() => _listEmailAddress.add(EmailAddress(null, value))); + _onUpdateListEmailAddressAction?.call(_prefixEmailAddress, _listEmailAddress); + }, + inputDecoration: InputDecoration(border: InputBorder.none), + tagBuilder: (context, index) => Stack(alignment: Alignment.centerRight, children: [ + Padding( + padding: EdgeInsets.only(top: kIsWeb ? 8 : 0, right: _isCollapse ? 50 : 0), + child: InkWell( + onTap: () => _isCollapse ? _onShowFullListEmailAddressAction?.call(_prefixEmailAddress) : null, + child: Chip( + labelPadding: EdgeInsets.only(left: 12, right: 12, bottom: 2), + label: Text(newListEmailAddress[index].asString(), maxLines: 1, overflow: kIsWeb ? null : TextOverflow.ellipsis), + deleteIcon: SvgPicture.asset(_imagePaths.icClose, fit: BoxFit.fill), + labelStyle: TextStyle(color: Colors.black, fontSize: 17, fontWeight: FontWeight.normal), + backgroundColor: _isEmailAddressValid(_listEmailAddress[index].emailAddress) ? AppColor.colorEmailAddressTag : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + width: _isEmailAddressValid(_listEmailAddress[index].emailAddress) ? 0 : 1, + color: _isEmailAddressValid(_listEmailAddress[index].emailAddress) ? AppColor.colorEmailAddressTag : AppColor.colorBorderEmailAddressInvalid), + ), + avatar: newListEmailAddress[index].displayName.isNotEmpty + ? CircleAvatar( + backgroundColor: AppColor.colorTextButton, + child: Text( + _listEmailAddress[index].displayName[0].toUpperCase(), + style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500))) + : null, + onDeleted: () { + setState(() => _listEmailAddress.removeAt(index)); + _onUpdateListEmailAddressAction?.call(_prefixEmailAddress, _listEmailAddress); + }, + ) + ) ), - avatar: hasAvatar == true - ? CircleAvatar( - backgroundColor: AppColor.colorTextButton, - child: Text( - _listEmail[index].isNotEmpty ? _listEmail[index][0].toUpperCase() : '', - style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500))) - : null, - onDeleted: () { - setState(() => _listEmailAddress.removeAt(index)); - _onUpdateListEmailAddressAction?.call(_prefixEmailAddress, _listEmailAddress); - }, - )), - onTagChanged: (String value) { - if (GetUtils.isEmail(value)) { - setState(() => _listEmailAddress.add(EmailAddress(value, value))); - _onUpdateListEmailAddressAction?.call(_prefixEmailAddress, _listEmailAddress); - } else { - _appToast.showErrorToast(AppLocalizations.of(context).email_address_is_not_in_the_correct_format); - } - }, - findSuggestions: (String query) { - if (query.isNotEmpty && _onSuggestionEmailAddress != null) { - return _onSuggestionEmailAddress!(query); - } - return []; - }, - suggestionBuilder: (context, tagEditorState, emailAddress) { - return _buildSuggestionItem(setState, context, tagEditorState, emailAddress); - }, - ); + if (_isCollapse) _buildCounter(context, _listEmailAddress.length - newListEmailAddress.length), + ]), + onTagChanged: (String value) { + setState(() => _listEmailAddress.add(EmailAddress(null, value))); + _onUpdateListEmailAddressAction?.call(_prefixEmailAddress, _listEmailAddress); + }, + findSuggestions: (String query) { + if (query.isNotEmpty && _onSuggestionEmailAddress != null) { + return _onSuggestionEmailAddress!(query); + } + return []; + }, + suggestionBuilder: (context, tagEditorState, emailAddress) { + return _buildSuggestionItem(setState, context, tagEditorState, emailAddress); + }, + ) + )); }); } - List get _listEmail => _listEmailAddress.map((emailAddress) => emailAddress.asString()).toList(); + Widget _buildCounter(BuildContext context, int count) { + return Padding( + padding: EdgeInsets.only(left: 8, top: kIsWeb ? 8 : 0), + child: InkWell( + onTap: () => _onShowFullListEmailAddressAction?.call(_prefixEmailAddress), + child: Chip( + labelPadding: EdgeInsets.symmetric(horizontal: 8), + label: Text('+$count', maxLines: 1, overflow: kIsWeb ? null : TextOverflow.ellipsis), + labelStyle: TextStyle(color: Colors.black, fontSize: 17, fontWeight: FontWeight.normal), + backgroundColor: AppColor.colorEmailAddressTag, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide(width: 0, color: AppColor.colorEmailAddressTag), + ), + ), + ), + ); + } + + bool get _isCollapse { + return _listEmailAddress.length > 1 && expandMode == ExpandMode.COLLAPSE; + } Widget _buildSuggestionItem(StateSetter setState, BuildContext context, TagsEditorState tagEditorState, EmailAddress emailAddress) { return Container( @@ -175,14 +228,14 @@ class EmailAddressInputBuilder { backgroundColor: AppColor.avatarColor, child: Text( emailAddress.asString().isNotEmpty ? emailAddress.asString()[0].toUpperCase() : '', - style: TextStyle(color: AppColor.appColor, fontSize: 16, fontWeight: FontWeight.w500))), + style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.w600))), title: Text( emailAddress.asString(), - style: TextStyle(color: AppColor.nameUserColor, fontSize: 14, fontWeight: FontWeight.w500)), + style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.normal)), subtitle: emailAddress.displayName.isNotEmpty && emailAddress.emailAddress.isNotEmpty ? Text( - emailAddress.emailAddress, - style: TextStyle(color: AppColor.baseTextColor, fontSize: 12, fontWeight: FontWeight.w500)) + emailAddress.emailAddress, + style: TextStyle(color: AppColor.colorHintSearchBar, fontSize: 13, fontWeight: FontWeight.normal)) : null, onTap: () { setState(() => _listEmailAddress.add(emailAddress)); @@ -192,4 +245,6 @@ class EmailAddressInputBuilder { ), ); } + + bool _isEmailAddressValid(String emailAddress) => GetUtils.isEmail(emailAddress); } \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/suggestions_box_controller.dart b/lib/features/composer/presentation/widgets/suggestions_box_controller.dart deleted file mode 100644 index d16c83918..000000000 --- a/lib/features/composer/presentation/widgets/suggestions_box_controller.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; - -class SuggestionsBoxController { - final BuildContext context; - - OverlayEntry? overlayEntry; - - bool _isOpened = false; - - bool get isOpened => _isOpened; - - SuggestionsBoxController(this.context); - - void open() { - if (_isOpened) return; - assert(overlayEntry != null); - Overlay.of(context)?.insert(overlayEntry!); - _isOpened = true; - } - - void close() { - if (!_isOpened) return; - assert(overlayEntry != null); - overlayEntry!.remove(); - _isOpened = false; - } - - void toggle() { - if (_isOpened) { - close(); - } else { - open(); - } - } -} diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 22ba0ffb3..de7a1fd03 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -10,6 +10,7 @@ import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmapState; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; +import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:rxdart/transformers.dart'; import 'package:tmail_ui_user/features/base/base_mailbox_controller.dart'; import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; @@ -457,7 +458,7 @@ class MailboxController extends BaseMailboxController { showDialog( context: context, barrierColor: AppColor.colorDefaultCupertinoActionSheet, - builder: (BuildContext context) => (ConfirmDialogBuilder(_imagePaths) + builder: (BuildContext context) => PointerInterceptor(child: (ConfirmDialogBuilder(_imagePaths) ..key(Key('confirm_dialog_delete_mailbox')) ..title(AppLocalizations.of(context).delete_mailboxes) ..content(AppLocalizations.of(context).message_confirmation_dialog_delete_mailbox(presentationMailbox.name?.name ?? '')) @@ -467,7 +468,7 @@ class MailboxController extends BaseMailboxController { ..onCloseButtonAction(() => popBack()) ..onConfirmButtonAction(AppLocalizations.of(context).delete, () => _deleteMailboxAction(presentationMailbox)) ..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack())) - .build()); + .build())); } } diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index bf652e908..ad01f0734 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -207,10 +207,10 @@ class AppLocalizations { ); } - String get your_email_should_have_at_least_one_recipient { + String get message_dialog_send_email_without_recipient { return Intl.message( 'Your email should have at least one recipient', - name: 'your_email_should_have_at_least_one_recipient', + name: 'message_dialog_send_email_without_recipient', ); } @@ -810,10 +810,10 @@ class AppLocalizations { name: 'show_all'); } - String get message_confirm_send_email { + String get message_dialog_send_email_without_a_subject { return Intl.message( 'Are you sure to send messages without a subject?', - name: 'message_confirm_send_email'); + name: 'message_dialog_send_email_without_a_subject'); } String get app_name { @@ -888,4 +888,72 @@ class AppLocalizations { 'Collapse', name: 'collapse'); } + + String get save_to_drafts { + return Intl.message( + 'Save to drafts', + name: 'save_to_drafts', + ); + } + + String get hint_compose_email { + return Intl.message( + 'Start composing a letter...', + name: 'hint_compose_email'); + } + + String get email_address_suggestion { + return Intl.message( + 'Email address suggestion', + name: 'email_address_suggestion', + ); + } + + String get attach_file { + return Intl.message( + 'Attach file', + name: 'attach_file'); + } + + String get show { + return Intl.message( + 'Show', + name: 'show'); + } + + String get add_recipients { + return Intl.message( + 'Add recipients', + name: 'add_recipients'); + } + + String get sending_failed { + return Intl.message( + 'Sending failed', + name: 'sending_failed'); + } + + String get send_anyway { + return Intl.message( + 'Send anyway', + name: 'send_anyway'); + } + + String get empty_subject { + return Intl.message( + 'Empty subject', + name: 'empty_subject'); + } + + String get message_dialog_send_email_with_email_address_invalid { + return Intl.message( + 'Check the correctness of email addresses and try again', + name: 'message_dialog_send_email_with_email_address_invalid'); + } + + String get fix_email_addresses { + return Intl.message( + 'Fix email addresses', + name: 'fix_email_addresses'); + } } \ No newline at end of file