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
@@ -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,7 +53,10 @@ 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;
final expandModeAttachments = 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) {
_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();
}
@@ -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(
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<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(
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<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(
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<ComposerController> {
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<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,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<EmailAddress>(
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<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) {
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<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))
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);
}
@@ -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');
}
}