TF-390 Apply new ui composer for browser

This commit is contained in:
dab246
2022-03-24 09:08:25 +07:00
committed by Dat H. Pham
parent c2f6434686
commit 8700959362
22 changed files with 820 additions and 494 deletions
+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.0006 1.71423C17.6812 1.71423 22.2863 6.3193 22.2863 11.9999C22.2863 17.6806 17.6812 22.2857 12.0006 22.2857C6.31991 22.2857 1.71484 17.6806 1.71484 11.9999C1.71484 6.3193 6.31991 1.71423 12.0006 1.71423ZM12.0006 3.42852C7.26669 3.42852 3.42913 7.26608 3.42913 11.9999C3.42913 16.7338 7.26669 20.5714 12.0006 20.5714C16.7344 20.5714 20.572 16.7338 20.572 11.9999C20.572 7.26608 16.7344 3.42852 12.0006 3.42852ZM12.0006 6.85709C12.4739 6.85709 12.8577 7.24085 12.8577 7.71423V11.1428H16.2863C16.7597 11.1428 17.1434 11.5266 17.1434 11.9999C17.1434 12.4733 16.7597 12.8571 16.2863 12.8571H12.8577V16.2857C12.8577 16.759 12.4739 17.1428 12.0006 17.1428C11.5272 17.1428 11.1434 16.759 11.1434 16.2857V12.8571H7.71484C7.24146 12.8571 6.8577 12.4733 6.8577 11.9999C6.8577 11.5266 7.24146 11.1428 7.71484 11.1428H11.1434V7.71423C11.1434 7.24085 11.5272 6.85709 12.0006 6.85709Z" fill="#007AFF"/>
</svg>

After

Width:  |  Height:  |  Size: 1004 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 10.3017L6.64021 14.7682C6.21593 15.1218 5.58537 15.0645 5.2318 14.6402C4.87824 14.2159 4.93556 13.5853 5.35984 13.2318L11.3598 8.23178C11.7307 7.92274 12.2694 7.92274 12.6402 8.23178L18.6402 13.2318C19.0645 13.5853 19.1218 14.2159 18.7682 14.6402C18.4147 15.0645 17.7841 15.1218 17.3598 14.7682L12 10.3017Z" fill="#99A2AD"/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.3591 8.30754C19.7692 8.71759 19.7692 9.38241 19.3591 9.79246L15.3185 13.8333L19.3591 17.8742C19.735 18.2501 19.7663 18.8401 19.4531 19.2516L19.3591 19.3591C18.9491 19.7692 18.2843 19.7692 17.8742 19.3591L13.8333 15.3185L9.79246 19.3591C9.38241 19.7692 8.71759 19.7692 8.30754 19.3591C7.89749 18.9491 7.89749 18.2843 8.30754 17.8742L12.3482 13.8333L8.30754 9.79246C7.93166 9.41658 7.90033 8.82662 8.21357 8.41506L8.30754 8.30754C8.71759 7.89749 9.38241 7.89749 9.79246 8.30754L13.8333 12.3482L17.8742 8.30754C18.2843 7.89749 18.9491 7.89749 19.3591 8.30754Z" fill="#99A2AD"/>
</svg>

After

Width:  |  Height:  |  Size: 691 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="45" height="44" viewBox="0 0 45 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M22.5 0C34.6503 0 44.5 9.84974 44.5 22C44.5 34.1503 34.6503 44 22.5 44C10.3497 44 0.5 34.1503 0.5 22C0.5 9.84974 10.3497 0 22.5 0ZM7.95108 9.39432C5.02236 12.7716 3.25 17.1788 3.25 22C3.25 32.6315 11.8685 41.25 22.5 41.25C27.3212 41.25 31.7284 39.4776 35.1057 36.5489L7.95108 9.39432ZM22.5 2.75C17.6788 2.75 13.2716 4.52236 9.89432 7.45108L37.0489 34.6057C39.9776 31.2284 41.75 26.8212 41.75 22C41.75 11.3685 33.1315 2.75 22.5 2.75Z" fill="#818C99"/>
</svg>

After

Width:  |  Height:  |  Size: 563 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.00001 13.9C5.00001 13.4029 5.40295 13 5.90001 13H10.1C10.5971 13 11 13.4029 11 13.9V18.1C11 18.5971 10.5971 19 10.1 19C9.60294 19 9.2 18.5971 9.2 18.1V16.0728L5.5364 19.7364C5.18492 20.0879 4.61508 20.0879 4.2636 19.7364C3.91213 19.3849 3.91213 18.8151 4.2636 18.4636L7.92721 14.8H5.90001C5.40295 14.8 5.00001 14.3971 5.00001 13.9Z" fill="#99A2AD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.7364 4.2636C20.0879 4.61508 20.0879 5.18492 19.7364 5.5364L16.0728 9.2H18.0982C18.5952 9.2 18.9982 9.60294 18.9982 10.1C18.9982 10.5971 18.5952 11 18.0982 11H13.9C13.4029 11 13 10.5971 13 10.1V5.90001C13 5.40295 13.4029 5.00001 13.9 5.00001C14.3971 5.00001 14.8 5.40295 14.8 5.90001V7.92721L18.4636 4.2636C18.8151 3.91213 19.3849 3.91213 19.7364 4.2636Z" fill="#99A2AD"/>
</svg>

After

Width:  |  Height:  |  Size: 930 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="6" y="14" width="15.5556" height="3" rx="1.5" fill="#99A2AD"/>
</svg>

After

Width:  |  Height:  |  Size: 175 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="18.5" width="16" height="1.5" rx="0.75" fill="#99A2AD"/>
</svg>

Before

Width:  |  Height:  |  Size: 175 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 28C21.732 28 28 21.732 28 14C28 6.26801 21.732 0 14 0C6.26801 0 0 6.26801 0 14C0 21.732 6.26801 28 14 28ZM19.7071 12.2929L14.7071 7.29289C14.3166 6.90237 13.6834 6.90237 13.2929 7.29289L8.29289 12.2929C7.90237 12.6834 7.90237 13.3166 8.29289 13.7071C8.68342 14.0976 9.31658 14.0976 9.70711 13.7071L13 10.4142V20C13 20.5523 13.4477 21 14 21C14.5523 21 15 20.5523 15 20V10.4142L18.2929 13.7071C18.6834 14.0976 19.3166 14.0976 19.7071 13.7071C20.0976 13.3166 20.0976 12.6834 19.7071 12.2929Z" fill="#99A2AD"/>
</svg>

After

Width:  |  Height:  |  Size: 663 B

-4
View File
@@ -1,4 +0,0 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="18" fill="#3C3C43" fill-opacity="0.18"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7355 10.3422C20.555 10.133 20.2914 10 19.9951 10C19.6865 10 19.4135 10.1449 19.233 10.3696C19.1945 10.4179 12.3897 17.225 12.3897 17.225C12.0034 17.614 12.0034 18.3302 12.3897 18.7188C12.7759 19.1078 13.5681 19.1078 13.9539 18.7188L18.8637 13.7378V28.7207C18.8637 29.2736 19.4492 29.7 19.9951 29.7C20.5416 29.7 21.1565 29.2736 21.1565 28.7207V13.7378L26.1093 18.8763C26.4951 19.2653 27.224 19.2653 27.6103 18.8763C27.9965 18.4878 27.9965 17.614 27.6103 17.225C27.6103 17.225 20.7726 10.385 20.7355 10.3422Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 747 B

+4
View File
@@ -0,0 +1,4 @@
<svg width="45" height="44" viewBox="0 0 45 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.2387 25.7541C16.553 27.609 16.1222 28.9122 15.9461 29.6638C15.3928 32.026 14.9904 32.5581 17.0528 31.4355C19.1152 30.3129 29.0994 24.7519 31.3269 23.516C34.2306 21.905 34.2694 22.0309 31.1711 20.3204C28.8114 19.0176 18.9442 13.6017 17.0528 12.5382C15.1614 11.4748 15.3928 11.9477 15.9461 14.3099C16.1245 15.0712 16.5616 16.3861 17.2574 18.2545C17.7441 19.5612 18.8762 20.5202 20.2452 20.7854L26.0097 21.9019C26.0639 21.9128 26.099 21.9655 26.0882 22.0196C26.0802 22.0592 26.0493 22.0901 26.0097 22.0981L20.2322 23.2136C18.8588 23.4788 17.7237 24.4421 17.2387 25.7541Z" fill="#007AFF"/>
<path d="M22.5 0C34.6503 0 44.5 9.84974 44.5 22C44.5 34.1503 34.6503 44 22.5 44C10.3497 44 0.5 34.1503 0.5 22C0.5 9.84974 10.3497 0 22.5 0ZM7.95108 9.39432C5.02236 12.7716 3.25 17.1788 3.25 22C3.25 32.6315 11.8685 41.25 22.5 41.25C27.3212 41.25 31.7284 39.4776 35.1057 36.5489L7.95108 9.39432ZM22.5 2.75C17.6788 2.75 13.2716 4.52236 9.89432 7.45108L37.0489 34.6057C39.9776 31.2284 41.75 26.8212 41.75 22C41.75 11.3685 33.1315 2.75 22.5 2.75Z" fill="#818C99"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -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)],
@@ -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;
@@ -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)
);
}
@@ -51,3 +52,46 @@ Widget buildIconWebHasPosition(BuildContext context, {
)
);
}
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<Color>((Set<MaterialState> states) => AppColor.colorFocusButton),
shape: MaterialStateProperty.all(CircleBorder()),
padding: MaterialStateProperty.resolveWith<EdgeInsets>((Set<MaterialState> states) => EdgeInsets.all(5)),
elevation: MaterialStateProperty.resolveWith<double>((Set<MaterialState> 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()
),
);
}
@@ -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,16 +158,18 @@ 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: [
if (_cancelText.isNotEmpty)
Expanded(child: _buildButton(
name: _cancelText,
bgColor: _colorCancelButton,
radius: _radiusButton,
textStyle: _styleTextCancelButton,
action: _onCancelButtonAction)),
SizedBox(width: 16),
if (_confirmText.isNotEmpty && _cancelText.isNotEmpty) SizedBox(width: 16),
if (_confirmText.isNotEmpty)
Expanded(child: _buildButton(
name: _confirmText,
bgColor: _colorConfirmButton,
@@ -125,9 +125,6 @@ class ComposerBindings extends BaseBindings {
Get.find<SaveEmailAddressesInteractor>(),
Get.find<GetAutoCompleteInteractor>(),
Get.find<GetAutoCompleteWithDeviceContactInteractor>(),
Get.find<AppToast>(),
Get.find<ImagePaths>(),
Get.find<ResponsiveUtils>(),
Get.find<Uuid>(),
Get.find<DeviceInfoPlugin>(),
Get.find<LocalFilePickerInteractor>(),
@@ -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,6 +53,9 @@ import 'package:uuid/uuid.dart';
class ComposerController extends BaseController {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final _appToast = Get.find<AppToast>();
final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
final expandMode = ExpandMode.COLLAPSE.obs;
final expandModeMobile = ExpandMode.EXPAND.obs;
@@ -61,16 +65,17 @@ class ComposerController extends BaseController {
final isInitialRecipient = false.obs;
final attachments = <Attachment>[].obs;
final emailContents = Rxn<List<EmailContent>>();
final listEmailAddressType = <PrefixEmailAddress>[].obs;
final subjectEmail = Rxn<String>();
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<Attachment> initialAttachments = <Attachment>[];
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) {
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);
} else {
_showConfirmDialogSendEmail(context);
}
} else {
_appToast.showErrorToast(AppLocalizations.of(context).your_email_should_have_at_least_one_recipient);
}
}
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();
}
@@ -18,7 +18,6 @@ class ComposerView extends GetWidget<ComposerController> {
final responsiveUtils = Get.find<ResponsiveUtils>();
final imagePaths = Get.find<ImagePaths>();
final _appToast = Get.find<AppToast>();
final keyboardUtils = Get.find<KeyboardUtils>();
@override
@@ -128,7 +127,7 @@ class ComposerView extends GetWidget<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
? (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<ComposerController> {
? (EmailAddressInputBuilder(
context,
imagePaths,
_appToast,
PrefixEmailAddress.bcc,
controller.listBccEmailAddress,
controller.listEmailAddressType,
controller: controller.bccEmailAddressController,
isInitial: controller.isInitialRecipient.value,)
..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress))
@@ -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<ComposerController> {
responsiveUtils: responsiveUtils,
mobile: Scaffold(
backgroundColor: Colors.white,
body: Align(alignment: Alignment.center, child: Card(
color: Colors.transparent,
child: Container(
body: Container(
color: Colors.white,
child: PointerInterceptor(child: Column(
child: LayoutBuilder(builder: (context, constraints) => 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))),
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()),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(16)),
child: PointerInterceptor(child: Row(children: [
Padding(
padding: EdgeInsets.only(right: 8),
padding: EdgeInsets.only(left: 10),
child: buildIconWeb(
icon: SvgPicture.asset(imagePaths.icCloseComposer, color: Colors.white, fit: BoxFit.fill),
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
}
Widget _buildAppBar(BuildContext context) {
return Transform(
transform: Matrix4.translationValues(15.0, 0.0, 0.0),
child: Row(
return 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),
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<ComposerController> {
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(
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(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
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,
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,
isInitial: controller.isInitialRecipient.value,
expandMode: controller.expandMode.value)
..addExpandAddressActionClick(() => controller.expandEmailAddressAction())
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.expandMode.value == ExpandMode.EXPAND
)),
Obx(() => controller.listEmailAddressType.contains(PrefixEmailAddress.cc) == true
? Divider(color: AppColor.colorDividerComposer, height: 1)
: SizedBox.shrink()),
Obx(() => controller.expandMode.value == ExpandMode.EXPAND
? (EmailAddressInputBuilder(
context,
imagePaths,
appToast,
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()
.build())
: SizedBox.shrink()
),
Obx(() => controller.expandMode.value == ExpandMode.EXPAND
Obx(() => controller.listEmailAddressType.contains(PrefixEmailAddress.bcc) == true
? Divider(color: AppColor.colorDividerComposer, height: 1)
: SizedBox.shrink()),
Obx(() => controller.expandMode.value == ExpandMode.EXPAND
? (EmailAddressInputBuilder(
context,
imagePaths,
appToast,
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()
.build())
: SizedBox.shrink()
),
],
),
)
);
}
@@ -377,61 +382,74 @@ class ComposerView extends GetWidget<ComposerController> {
child: Text(
'${AppLocalizations.of(context).subject_email}:',
style: TextStyle(fontSize: 15, color: AppColor.colorHintEmailAddressInput))),
Expanded(child: (TextFieldBuilder()
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: AppColor.colorEmailAddress, fontSize: 15))
..textStyle(TextStyle(color: Colors.black, fontSize: 15, fontWeight: FontWeight.normal))
..textDecoration(InputDecoration(contentPadding: EdgeInsets.zero, border: InputBorder.none))
..addController(controller.subjectEmailInputController))
.build()
.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<ComposerController> {
}
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: '<p></p>',
hint: '${AppLocalizations.of(context).hint_compose_email}',
initialText: initContent,
darkMode: false,
),
@@ -460,7 +480,7 @@ class ComposerView extends GetWidget<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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;
}
}
}
@@ -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<List<EmailAddress>> Function(String word);
typedef OnUpdateListEmailAddressAction = void Function(PrefixEmailAddress, List<EmailAddress>);
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<EmailAddress> _listEmailAddress;
final List<PrefixEmailAddress> _listEmailAddressType;
final TextEditingController? controller;
final FocusNode? focusNode;
final bool? isInitial;
final bool hasAvatar;
OnOpenExpandAddressActionClick? _onOpenExpandAddressActionClick;
List<EmailAddress> _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,109 +47,137 @@ 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<EmailAddress>(
length: _listEmail.length,
final newListEmailAddress = _isCollapse ? _listEmailAddress.sublist(0, 1) : _listEmailAddress;
return FocusScope(child: Focus(
onFocusChange: (focus) => _onFocusEmailAddressChangeAction?.call(_prefixEmailAddress, focus),
child: TagEditor<EmailAddress>(
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) {
if (GetUtils.isEmail(value)) {
setState(() => _listEmailAddress.add(EmailAddress(value, value)));
setState(() => _listEmailAddress.add(EmailAddress(null, 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),
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(_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,
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: 0, color: AppColor.emailAddressChipColor),
side: BorderSide(
width: _isEmailAddressValid(_listEmailAddress[index].emailAddress) ? 0 : 1,
color: _isEmailAddressValid(_listEmailAddress[index].emailAddress) ? AppColor.colorEmailAddressTag : AppColor.colorBorderEmailAddressInvalid),
),
avatar: hasAvatar == true
avatar: newListEmailAddress[index].displayName.isNotEmpty
? CircleAvatar(
backgroundColor: AppColor.colorTextButton,
child: Text(
_listEmail[index].isNotEmpty ? _listEmail[index][0].toUpperCase() : '',
_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);
},
)),
)
)
),
if (_isCollapse) _buildCounter(context, _listEmailAddress.length - newListEmailAddress.length),
]),
onTagChanged: (String value) {
if (GetUtils.isEmail(value)) {
setState(() => _listEmailAddress.add(EmailAddress(value, value)));
setState(() => _listEmailAddress.add(EmailAddress(null, 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) {
@@ -157,11 +188,33 @@ class EmailAddressInputBuilder {
suggestionBuilder: (context, tagEditorState, emailAddress) {
return _buildSuggestionItem(setState, context, tagEditorState, emailAddress);
},
);
)
));
});
}
List<String> 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<EmailAddress> 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))
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);
}
@@ -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();
}
}
}
@@ -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()));
}
}
+72 -4
View File
@@ -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');
}
}