TF-295 Apply new UI for composer web browser

This commit is contained in:
dab246
2022-03-16 15:57:59 +07:00
committed by Dat H. Pham
parent 8c66ef9105
commit 1f9fd7c0a7
38 changed files with 1181 additions and 219 deletions
@@ -127,6 +127,7 @@ class ComposerBindings extends BaseBindings {
Get.find<GetAutoCompleteWithDeviceContactInteractor>(),
Get.find<AppToast>(),
Get.find<ImagePaths>(),
Get.find<ResponsiveUtils>(),
Get.find<Uuid>(),
Get.find<DeviceInfoPlugin>(),
Get.find<LocalFilePickerInteractor>(),
@@ -19,8 +19,10 @@ import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_value.dart';
import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/composer/domain/model/auto_complete_pattern.dart';
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
@@ -35,15 +37,16 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_inter
import 'package:tmail_ui_user/features/composer/domain/usecases/update_email_drafts_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/upload_mutiple_attachment_interactor.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_action.dart';
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:core/utils/app_logger.dart';
import 'package:uuid/uuid.dart';
class ComposerController extends BaseController {
@@ -51,11 +54,14 @@ class ComposerController extends BaseController {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final expandMode = ExpandMode.COLLAPSE.obs;
final expandModeAttachments = ExpandMode.EXPAND.obs;
final composerArguments = Rxn<ComposerArguments>();
final isEnableEmailSendButton = false.obs;
final isInitialRecipient = false.obs;
final attachments = <Attachment>[].obs;
final emailContents = <EmailContent>[].obs;
final subjectEmail = Rxn<String>();
final screenDisplayMode = ScreenDisplayMode.normal.obs;
final SendEmailInteractor _sendEmailInteractor;
final SaveEmailAddressesInteractor _saveEmailAddressInteractor;
@@ -63,6 +69,7 @@ class ComposerController extends BaseController {
final GetAutoCompleteWithDeviceContactInteractor _getAutoCompleteWithDeviceContactInteractor;
final AppToast _appToast;
final ImagePaths _imagePaths;
final ResponsiveUtils responsiveUtils;
final Uuid _uuid;
final LocalFilePickerInteractor _localFilePickerInteractor;
final UploadMultipleAttachmentInteractor _uploadMultipleAttachmentInteractor;
@@ -74,7 +81,6 @@ class ComposerController extends BaseController {
List<EmailAddress> listToEmailAddress = <EmailAddress>[];
List<EmailAddress> listCcEmailAddress = <EmailAddress>[];
List<EmailAddress> listBccEmailAddress = <EmailAddress>[];
String? _subjectEmail;
ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.localContact;
HtmlEditorApi? htmlEditorApi;
final HtmlEditorBrowser.HtmlEditorController htmlControllerBrowser = HtmlEditorBrowser.HtmlEditorController();
@@ -85,8 +91,13 @@ class ComposerController extends BaseController {
final bccEmailAddressController = TextEditingController();
List<Attachment> initialAttachments = <Attachment>[];
String? _textEditor;
void setSubjectEmail(String subject) => _subjectEmail = subject;
void setTextEditor(String? text) => _textEditor = text;
String? get textEditor => _textEditor;
void setSubjectEmail(String subject) => subjectEmail.value = subject;
Future<String> _getEmailBodyText({bool onlyText = false}) async {
if (kIsWeb) {
@@ -107,6 +118,7 @@ class ComposerController extends BaseController {
this._getAutoCompleteWithDeviceContactInteractor,
this._appToast,
this._imagePaths,
this.responsiveUtils,
this._uuid,
this._deviceInfoPlugin,
this._localFilePickerInteractor,
@@ -178,7 +190,7 @@ class ComposerController extends BaseController {
}
void _initEmail() {
final arguments = Get.arguments;
final arguments = kIsWeb ? mailboxDashBoardController.routerArguments : Get.arguments;
if (arguments is ComposerArguments) {
composerArguments.value = arguments;
if (arguments.emailActionType == EmailActionType.edit) {
@@ -211,6 +223,14 @@ class ComposerController extends BaseController {
return '';
}
String? get initTextEditorComposer {
if (composerArguments.value?.emailActionType == EmailActionType.compose) {
return textEditor;
} else {
return textEditor ?? getContentEmail();
}
}
Tuple2<String, String>? _getHeaderEmailQuoted(String locale, ComposerArguments arguments) {
if (arguments.presentationEmail != null) {
final sentDate = arguments.presentationEmail?.sentAt;
@@ -226,8 +246,9 @@ class ComposerController extends BaseController {
}
void _initToEmailAddress(ComposerArguments arguments) {
if (arguments.presentationEmail != null) {
final userEmailAddress = EmailAddress(null, arguments.userProfile.email);
final userProfile = mailboxDashBoardController.userProfile.value;
if (arguments.presentationEmail != null && userProfile != null) {
final userEmailAddress = EmailAddress(null, userProfile.email);
final recipients = arguments.presentationEmail!.generateRecipientsEmailAddressForComposer(arguments.emailActionType, arguments.mailboxRole);
@@ -302,12 +323,12 @@ class ComposerController extends BaseController {
return emailQuotedHtml;
}
Future<Email> _generateEmail(ComposerArguments arguments, {bool asDrafts = false}) async {
Future<Email> _generateEmail(Map<Role, MailboxId> mapDefaultMailboxId, UserProfile userProfile, {bool asDrafts = false}) async {
final generateEmailId = EmailId(Id(_uuid.v1()));
final outboxMailboxId = arguments.mapMailboxId[PresentationMailbox.roleOutbox];
final draftMailboxId = arguments.mapMailboxId[PresentationMailbox.roleDrafts];
final outboxMailboxId = mapDefaultMailboxId[PresentationMailbox.roleOutbox];
final draftMailboxId = mapDefaultMailboxId[PresentationMailbox.roleDrafts];
final listFromEmailAddress = {
EmailAddress(null, arguments.userProfile.email)
EmailAddress(null, userProfile.email)
};
final generatePartId = PartId(_uuid.v1());
final generateBlobId = Id(_uuid.v1());
@@ -323,7 +344,7 @@ class ComposerController extends BaseController {
cc: listCcEmailAddress.toSet(),
bcc: listBccEmailAddress.toSet(),
keywords: asDrafts ? {KeyWordIdentifier.emailDraft : true} : null,
subject: _subjectEmail,
subject: subjectEmail.value,
htmlBody: {
EmailBodyPart(
partId: generatePartId,
@@ -363,30 +384,44 @@ class ComposerController extends BaseController {
clearFocusEditor(context);
if (isEnableEmailSendButton.value) {
final arguments = composerArguments.value;
if (arguments != null) {
_saveEmailAddress();
final email = await _generateEmail(arguments);
final accountId = arguments.session.accounts.keys.first;
final sentMailboxId = arguments.mapMailboxId[PresentationMailbox.roleSent];
final submissionCreateId = Id(_uuid.v1());
mailboxDashBoardController.consumeState(_sendEmailInteractor.execute(
accountId,
EmailRequest(
email,
submissionCreateId,
mailboxIdSaved: sentMailboxId,
emailIdDestroyed: arguments.emailActionType == EmailActionType.edit ? arguments.presentationEmail?.id : null)));
if (subjectEmail.value?.isNotEmpty == true) {
_handleSendMessages(context);
} else {
_showConfirmDialogSendEmail(context);
}
popBack();
} else {
_appToast.showErrorToast(AppLocalizations.of(context).your_email_should_have_at_least_one_recipient);
}
}
void _handleSendMessages(BuildContext context) async {
final arguments = composerArguments.value;
final session = mailboxDashBoardController.sessionCurrent;
final mapDefaultMailboxId = mailboxDashBoardController.mapDefaultMailboxId;
final userProfile = mailboxDashBoardController.userProfile.value;
if (arguments != null && session != null && mapDefaultMailboxId.isNotEmpty && userProfile != null) {
_saveEmailAddress();
final email = await _generateEmail(mapDefaultMailboxId, userProfile);
final accountId = session.accounts.keys.first;
final sentMailboxId = mapDefaultMailboxId[PresentationMailbox.roleSent];
final submissionCreateId = Id(_uuid.v1());
mailboxDashBoardController.consumeState(_sendEmailInteractor.execute(
accountId,
EmailRequest(email, submissionCreateId, mailboxIdSaved: sentMailboxId,
emailIdDestroyed: arguments.emailActionType == EmailActionType.edit
? arguments.presentationEmail?.id
: null)));
}
if (kIsWeb) {
closeComposerWeb();
} else {
popBack();
}
}
void _saveEmailAddress() async {
final listEmailAddressCanSave = Set<EmailAddress>();
listEmailAddressCanSave.addAll(listToEmailAddress + listCcEmailAddress + listBccEmailAddress);
@@ -460,10 +495,10 @@ class ComposerController extends BaseController {
}
void _uploadAttachmentsAction(List<FileInfo> pickedFiles) async {
if (composerArguments.value != null) {
final accountId = composerArguments.value!.session.accounts.keys.first;
final uploadUrl = composerArguments.value!.session.getUploadUrl(accountId);
final session = mailboxDashBoardController.sessionCurrent;
if (session != null) {
final accountId = session.accounts.keys.first;
final uploadUrl = session.getUploadUrl(accountId);
consumeState(_uploadMultipleAttachmentInteractor.execute(pickedFiles, accountId, uploadUrl));
}
}
@@ -508,9 +543,9 @@ class ComposerController extends BaseController {
final oldEmailBody = kIsWeb ? getContentEmail() : '\n${getContentEmail()}\n';
final isEmailBodyChanged = !oldEmailBody.isSame(newEmailBody);
final newEmailSubject = _subjectEmail;
final subjectEmail = arguments.presentationEmail?.getEmailTitle().trim() ?? '';
final oldEmailSubject = arguments.emailActionType.getSubjectComposer(Get.context!, subjectEmail);
final newEmailSubject = subjectEmail.value;
final titleEmail = arguments.presentationEmail?.getEmailTitle().trim() ?? '';
final oldEmailSubject = arguments.emailActionType.getSubjectComposer(Get.context!, titleEmail);
final isEmailSubjectChanged = !oldEmailSubject.isSame(newEmailSubject);
final recipients = arguments.presentationEmail
@@ -544,13 +579,18 @@ class ComposerController extends BaseController {
clearFocusEditor(context);
final arguments = composerArguments.value;
if (arguments != null && Get.context != null) {
final mapDefaultMailboxId = mailboxDashBoardController.mapDefaultMailboxId;
final userProfile = mailboxDashBoardController.userProfile.value;
final session = mailboxDashBoardController.sessionCurrent;
if (arguments != null && Get.context != null && mapDefaultMailboxId.isNotEmpty
&& userProfile != null && session != null) {
final isChanged = await _isEmailChanged(arguments);
if (isChanged) {
_saveEmailAddress();
final newEmail = await _generateEmail(arguments, asDrafts: true);
final accountId = arguments.session.accounts.keys.first;
final newEmail = await _generateEmail(mapDefaultMailboxId, userProfile, asDrafts: true);
final accountId = session.accounts.keys.first;
final oldEmail = arguments.presentationEmail;
if (arguments.emailActionType == EmailActionType.edit && oldEmail != null) {
@@ -563,10 +603,10 @@ class ComposerController extends BaseController {
}
void _getEmailContentAction(ComposerArguments arguments) async {
final baseDownloadUrl = arguments.session.getDownloadUrl();
final accountId = arguments.session.accounts.keys.first;
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl();
final accountId = mailboxDashBoardController.sessionCurrent?.accounts.keys.first;
final emailId = arguments.presentationEmail?.id;
if (emailId != null) {
if (emailId != null && baseDownloadUrl != null && accountId != null) {
consumeState(_getEmailContentInteractor.execute(accountId, emailId, baseDownloadUrl));
}
}
@@ -594,7 +634,7 @@ class ComposerController extends BaseController {
if (arguments.emailActionType == EmailActionType.edit) {
return arguments.presentationEmail?.from?.first.emailAddress ?? '';
} else {
return arguments.userProfile.email;
return mailboxDashBoardController.userProfile.value?.email ?? '';
}
}
return '';
@@ -603,6 +643,69 @@ class ComposerController extends BaseController {
void clearFocusEditor(BuildContext context) {
if (!kIsWeb) {
htmlEditorApi?.unfocus(context);
htmlControllerBrowser.clearFocus();
}
}
void closeComposerWeb() {
mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.none);
}
void minimizeComposer() {
_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;
}
void _updateTextForEditor() async {
final textCurrent = await htmlControllerBrowser.getText();
log('_updateTextForEditor() | textCurrent: $textCurrent');
htmlControllerBrowser.insertHtml(textCurrent);
}
void deleteComposer() {
mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.none);
}
void toggleDisplayAttachments() {
final newExpandMode = expandModeAttachments.value == ExpandMode.COLLAPSE ? ExpandMode.EXPAND : ExpandMode.COLLAPSE;
expandModeAttachments.value = newExpandMode;
}
void _showConfirmDialogSendEmail(BuildContext context) {
if (responsiveUtils.isMobileDevice(context)) {
(ConfirmationDialogActionSheetBuilder(context)
..messageText(AppLocalizations.of(context).message_confirm_send_email)
..styleConfirmButton(TextStyle(fontSize: 20, fontWeight: FontWeight.normal, color: Colors.black))
..onCancelAction(AppLocalizations.of(context).cancel, () => popBack())
..onConfirmAction(AppLocalizations.of(context).send, () {
popBack();
_handleSendMessages(context);
})).show();
} else {
showDialog(
context: context,
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
builder: (BuildContext context) => PointerInterceptor(child: (ConfirmDialogBuilder(_imagePaths)
..key(Key('confirm_dialog_send_message'))
..content(AppLocalizations.of(context).message_confirm_send_email)
..colorConfirmButton(AppColor.colorTextButton)
..colorCancelButton(AppColor.colorCancelButton)
..styleContent(TextStyle(fontSize: 15, fontWeight: FontWeight.normal, color: AppColor.colorMessageDialog))
..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, () {
popBack();
_handleSendMessages(context);
})
..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack()))
.build()));
}
}
@@ -0,0 +1,529 @@
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/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:model/model.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/attachment_file_composer_builder.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/email_address_input_builder.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
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
Widget build(BuildContext context) {
return ResponsiveWidget(
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))),
]
))
)
))
),
desktop: Obx(() {
return Stack(children: [
if (controller.screenDisplayMode == ScreenDisplayMode.normal)
Positioned(right: 10, bottom: 10, child: Card(
elevation: 20,
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
width: responsiveUtils.getSizeWidthScreen(context) * 0.5,
height: responsiveUtils.getSizeHeightScreen(context) * 0.65,
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)),
]
)
)
)
)),
if (controller.screenDisplayMode == ScreenDisplayMode.minimize)
Positioned(right: 10, bottom: 10, child: Card(
elevation: 20,
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(color: AppColor.colorTextButton, borderRadius: BorderRadius.all(Radius.circular(20))),
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.closeComposerWeb())),
]
))
)
)),
if (controller.screenDisplayMode == ScreenDisplayMode.fullScreen)
Scaffold(
backgroundColor: Colors.black38,
body: Align(alignment: Alignment.center, child: Card(
color: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
child: Container(
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
width: responsiveUtils.getSizeWidthScreen(context) * 0.8,
height: responsiveUtils.getSizeHeightScreen(context) * 0.85,
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)),
]
))
)
)
)
)
)
]);
}),
tablet: Scaffold(
backgroundColor: Colors.black38,
body: Align(alignment: Alignment.center, child: Card(
color: Colors.transparent,
child: Container(
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.all(Radius.circular(20))),
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)),
]
))
)
)
))
),
);
}
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.closeComposerWeb()),
]
),
);
}
Widget _buildAppBarForMobile(BuildContext context, bool isEnableSendButton) {
return Container(
padding: EdgeInsets.only(left: 10, right: 10, top: 10),
color: Colors.white,
child: Row(
children: [
buildIconWeb(
icon: SvgPicture.asset(imagePaths.icCloseComposer, fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).close,
onTap: () => controller.closeComposerWeb()),
Spacer(),
buildIconWeb(
icon: SvgPicture.asset(imagePaths.icAttachmentsComposer, fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).pick_attachments,
onTap: () => controller.openFilePickerByType(context, FileType.any)),
buildIconWeb(
icon: SvgPicture.asset(
imagePaths.icSendComposer,
color: isEnableSendButton ? AppColor.colorTextButton : AppColor.colorDisableMailboxCreateButton,
fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).send,
onTap: () => controller.sendEmailAction(context)),
]
),
);
}
Widget _buildBottomBar(BuildContext context, bool isEnableSendButton) {
return Container(
padding: EdgeInsets.only(left: 10, right: 20, top: 18, bottom: 18),
color: AppColor.colorBottomBarComposer,
child: Row(
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)
),
)
]
),
);
}
Widget _buildTitleComposer(BuildContext context) {
return Obx(() => Text(
controller.subjectEmail.isNotEmpty == true
? controller.subjectEmail.value ?? ''
: AppLocalizations.of(context).new_message.capitalizeFirstEach,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500,
color: controller.screenDisplayMode == ScreenDisplayMode.minimize ? Colors.white : Colors.black),
));
}
Widget _buildEmailAddress(BuildContext context) {
return 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,
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()
),
],
);
}
Widget _buildSubjectEmail(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(right: 8, top: 16),
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()
)
]
);
}
Widget _buildEditorAndAttachments(BuildContext context) {
return SingleChildScrollView(
physics: ClampingScrollPhysics(),
child: Column(
children: [
Obx(() => controller.attachments.isNotEmpty
? Padding(
padding: EdgeInsets.symmetric(vertical: 4, horizontal: 10),
child: _buildAttachmentsTitle(context, controller.attachments, controller.expandModeAttachments.value))
: SizedBox.shrink()),
Obx(() => controller.attachments.isEmpty
? _buildAttachmentsLoadingView()
: SizedBox.shrink()),
Obx(() => controller.attachments.isNotEmpty
? Padding(
padding: EdgeInsets.only(bottom: 8, left: 10, right: 10),
child: _buildAttachmentsList(context, controller.attachments, controller.expandModeAttachments.value))
: SizedBox.shrink()),
Padding(
padding: EdgeInsets.symmetric(horizontal: 10),
child: _buildComposerEditor(context)),
]
)
);
}
Widget _buildComposerEditor(BuildContext context) {
return Obx(() => HtmlEditorBrowser.HtmlEditor(
key: Key('composer_editor_web'),
controller: controller.htmlControllerBrowser,
htmlEditorOptions: HtmlEditorBrowser.HtmlEditorOptions(
hint: '',
initialText: controller.initTextEditorComposer,
autoAdjustHeight: true,
darkMode: false,
disableScrollbarEditor: true,
),
htmlToolbarOptions: HtmlEditorBrowser.HtmlToolbarOptions(
toolbarPosition: HtmlEditorBrowser.ToolbarPosition.custom
),
otherOptions: HtmlEditorBrowser.OtherOptions(height: 550),
callbacks: HtmlEditorBrowser.Callbacks(onBeforeCommand: (String? currentHtml) {
controller.setTextEditor(currentHtml);
}, onChangeContent: (String? changed) {
controller.setTextEditor(changed);
}),
));
}
Widget _buildAttachmentsLoadingView({EdgeInsets? padding, double? size}) {
return Obx(() => controller.viewState.value.fold(
(failure) => SizedBox.shrink(),
(success) => success is UploadingAttachmentState
? Center(child: Padding(
padding: padding ?? EdgeInsets.all(10),
child: SizedBox(
width: size ?? 20,
height: size ?? 20,
child: CupertinoActivityIndicator(color: AppColor.colorTextButton))))
: SizedBox.shrink()));
}
Widget _buildAttachmentsTitle(BuildContext context, List<Attachment> attachments, ExpandMode expandModeAttachment) {
return Row(
children: [
Text(
'${AppLocalizations.of(context).attachments} (${filesize(attachments.totalSize(), 0)}):',
style: TextStyle(fontSize: 12, color: AppColor.colorHintEmailAddressInput, fontWeight: FontWeight.normal)),
_buildAttachmentsLoadingView(padding: EdgeInsets.only(left: 16), size: 16),
Spacer(),
Material(
type: MaterialType.circle,
color: Colors.transparent,
child: TextButton(
child: Text(
expandModeAttachment == ExpandMode.EXPAND
? AppLocalizations.of(context).hide
: '${AppLocalizations.of(context).show_all} (${attachments.length})',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12, color: AppColor.colorTextButton)),
onPressed: () => controller.toggleDisplayAttachments()
)
)
],
);
}
Widget _buildAttachmentsList(BuildContext context, List<Attachment> attachments, ExpandMode expandMode) {
if (expandMode == ExpandMode.EXPAND) {
return LayoutBuilder(builder: (context, constraints) {
return GridView.builder(
key: Key('list_attachment_full'),
primary: false,
shrinkWrap: true,
itemCount: attachments.length,
gridDelegate: SliverGridDelegateFixedHeight(
height: 60,
crossAxisCount: _getMaxItemRowListAttachment(context, constraints),
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0),
itemBuilder: (context, index) =>
(AttachmentFileComposerBuilder(context, imagePaths, attachments[index])
..addOnDeleteAttachmentAction((attachment) => controller.removeAttachmentAction(attachment)))
.build()
);
});
} else {
return LayoutBuilder(builder: (context, constraints) {
return Align(
alignment: Alignment.centerLeft,
child: SizedBox(
height: 60,
child: ListView.builder(
key: Key('list_attachment_minimize'),
shrinkWrap: true,
physics: ClampingScrollPhysics(),
scrollDirection: Axis.horizontal,
itemCount: attachments.length,
itemBuilder: (context, index) =>
(AttachmentFileComposerBuilder(context, imagePaths, attachments[index],
itemMargin: EdgeInsets.only(right: 8),
maxWidth: _getMaxWidthItemListAttachment(context, constraints),
maxHeight: 60)
..addOnDeleteAttachmentAction((attachment) => controller.removeAttachmentAction(attachment)))
.build()
)
)
);
});
}
}
int _getMaxItemRowListAttachment(BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth < ResponsiveUtils.minTabletWidth) {
return 2;
} else if (constraints.maxWidth < ResponsiveUtils.minTabletLargeWidth) {
return 3;
} else {
return 4;
}
}
double _getMaxWidthItemListAttachment(BuildContext context, BoxConstraints constraints) {
final currentWidth = constraints.maxWidth - 40;
return currentWidth / _getMaxItemRowListAttachment(context, constraints);
}
}
@@ -0,0 +1,6 @@
enum ScreenDisplayMode {
fullScreen,
minimize,
normal
}
@@ -0,0 +1,87 @@
import 'package:core/core.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
typedef OnDeleteAttachmentAction = void Function(Attachment attachment);
class AttachmentFileComposerBuilder {
final BuildContext context;
final ImagePaths imagePaths;
final Attachment attachment;
final double? maxWidth;
final double? maxHeight;
final EdgeInsets? itemMargin;
OnDeleteAttachmentAction? _onDeleteAttachmentAction;
Widget? buttonAction;
AttachmentFileComposerBuilder(
this.context,
this.imagePaths,
this.attachment,
{
this.maxWidth,
this.maxHeight,
this.itemMargin,
}
);
void addOnDeleteAttachmentAction(OnDeleteAttachmentAction onDeleteAttachmentAction) {
_onDeleteAttachmentAction = onDeleteAttachmentAction;
}
Widget build() {
return Theme(
data: ThemeData(
splashColor: Colors.transparent ,
highlightColor: Colors.transparent),
child: Container(
margin: itemMargin ?? EdgeInsets.zero,
padding: EdgeInsets.zero,
alignment: Alignment.center,
width: maxWidth,
height: maxHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: AppColor.colorInputBorderCreateMailbox),
color: Colors.white),
child: Stack(children: [
ListTile(
contentPadding: EdgeInsets.only(right: 16),
onTap: () {},
leading: Transform(
transform: Matrix4.translationValues(8.0, -3.0, 0.0),
child: SvgPicture.asset(imagePaths.icFileAttachment, fit: BoxFit.fill)),
title: Transform(
transform: Matrix4.translationValues(-4.0, -6.0, 0.0),
child: Text(
attachment.name ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 12, color: AppColor.colorNameEmail, fontWeight: FontWeight.w500),
)),
subtitle: attachment.size != null && attachment.size?.value != 0
? Transform(
transform: Matrix4.translationValues(-4.0, -5.0, 0.0),
child: Text(
filesize(attachment.size?.value, 0),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 10, color: AppColor.colorContentEmail, fontWeight: FontWeight.normal)))
: null,
),
Positioned(right: -5, top: -5, child: buildIconWeb(
icon: SvgPicture.asset(imagePaths.icDeleteAttachment, fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).delete,
onTap: () => _onDeleteAttachmentAction?.call(attachment)))
]),
)
);
}
}
@@ -26,6 +26,7 @@ class EmailAddressInputBuilder {
final List<EmailAddress> _listEmailAddress;
final TextEditingController? controller;
final bool? isInitial;
final bool hasAvatar;
OnOpenExpandAddressActionClick? _onOpenExpandAddressActionClick;
OnUpdateListEmailAddressAction? _onUpdateListEmailAddressAction;
@@ -51,6 +52,7 @@ class EmailAddressInputBuilder {
this._listEmailAddress,
{
this.isInitial,
this.hasAvatar = false,
this.controller,
this.expandMode = ExpandMode.COLLAPSE,
}
@@ -69,7 +71,7 @@ class EmailAddressInputBuilder {
Expanded(child: _buildTagEditor()),
if (_prefixEmailAddress == PrefixEmailAddress.to)
Padding(
padding: EdgeInsets.only(top: 4),
padding: EdgeInsets.only(top: 8),
child: _buildButtonExpandAddress())
]
);
@@ -79,12 +81,10 @@ class EmailAddressInputBuilder {
return Material(
type: MaterialType.circle,
color: Colors.transparent,
child: IconButton(
icon: SvgPicture.asset(
expandMode == ExpandMode.EXPAND ? _imagePaths.icExpandAddress : _imagePaths.icMoreReceiver,
width: 20,
height: 20,
fit: BoxFit.fill),
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()
)
);
@@ -98,6 +98,7 @@ class EmailAddressInputBuilder {
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
hasAddButton: false,
tagSpacing: 8,
resetTextOnSubmitted: true,
textStyle: TextStyle(color: AppColor.colorEmailAddress, fontSize: 14, fontWeight: FontWeight.w500),
onSubmitted: (value) {
@@ -117,16 +118,22 @@ class EmailAddressInputBuilder {
tagBuilder: (context, index) => Padding(
padding: EdgeInsets.only(top: kIsWeb ? 8 : 0),
child: Chip(
labelPadding: EdgeInsets.only(left: 8, right: 8, bottom: 2),
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,
avatar: CircleAvatar(
backgroundColor: AppColor.colorTextButton,
child: Text(
_listEmail[index].isNotEmpty ? _listEmail[index][0].toUpperCase() : '',
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500))),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(width: 0, color: AppColor.emailAddressChipColor),
),
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);
@@ -2,6 +2,7 @@ import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
@@ -33,6 +34,7 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_
import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_dialog_builder.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_action.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -480,16 +482,16 @@ class EmailController extends BaseController {
void composeEmailFromEmailAddress(EmailAddress emailAddress) {
popBack();
if (canComposeEmail()) {
push(
AppRoutes.COMPOSER,
arguments: ComposerArguments(
emailActionType: EmailActionType.composeFromEmailAddress,
emailAddress: emailAddress,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role,
session: mailboxDashBoardController.sessionCurrent!,
userProfile: mailboxDashBoardController.userProfile.value!,
mapMailboxId: mailboxDashBoardController.mapDefaultMailboxId));
final arguments = ComposerArguments(
emailActionType: EmailActionType.composeFromEmailAddress,
emailAddress: emailAddress,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
if (kIsWeb) {
if (mailboxDashBoardController.dashBoardAction != DashBoardAction.compose) {
mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.compose, arguments: arguments);
}
} else {
push(AppRoutes.COMPOSER, arguments: arguments);
}
}
@@ -497,11 +499,6 @@ class EmailController extends BaseController {
popBack();
}
bool canComposeEmail() => mailboxDashBoardController.sessionCurrent != null
&& mailboxDashBoardController.userProfile.value != null
&& mailboxDashBoardController.mapDefaultMailboxId.isNotEmpty
&& mailboxDashBoardController.selectedEmail.value != null;
void backToThreadView() {
attachmentsExpandMode.value = ExpandMode.COLLAPSE;
emailAddressExpandMode.value = ExpandMode.COLLAPSE;
@@ -513,30 +510,36 @@ class EmailController extends BaseController {
if (emailActionType == EmailActionType.compose) {
composeEmailAction();
} else {
if (canComposeEmail()) {
push(
AppRoutes.COMPOSER,
arguments: ComposerArguments(
emailActionType: emailActionType,
presentationEmail: mailboxDashBoardController.selectedEmail.value!,
emailContents: emailContents,
attachments: attachments,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role,
session: mailboxDashBoardController.sessionCurrent!,
userProfile: mailboxDashBoardController.userProfile.value!,
mapMailboxId: mailboxDashBoardController.mapDefaultMailboxId));
final arguments = ComposerArguments(
emailActionType: emailActionType,
presentationEmail: mailboxDashBoardController.selectedEmail.value!,
emailContents: emailContents,
attachments: attachments,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
if (kIsWeb) {
if (mailboxDashBoardController.dashBoardAction != DashBoardAction.compose) {
mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.compose, arguments: arguments);
}
if (Get.currentRoute == AppRoutes.EMAIL) {
popBack();
}
} else {
push(AppRoutes.COMPOSER, arguments: arguments);
}
}
}
void composeEmailAction() {
if (canComposeEmail()) {
push(
AppRoutes.COMPOSER,
arguments: ComposerArguments(
session: mailboxDashBoardController.sessionCurrent!,
userProfile: mailboxDashBoardController.userProfile.value!,
mapMailboxId: mailboxDashBoardController.mapDefaultMailboxId));
if (kIsWeb) {
if (mailboxDashBoardController.dashBoardAction != DashBoardAction.compose) {
mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.compose, arguments: ComposerArguments());
}
if (Get.currentRoute == AppRoutes.EMAIL) {
popBack();
}
} else {
push(AppRoutes.COMPOSER, arguments: ComposerArguments());
}
}
}
@@ -216,7 +216,7 @@ class EmailView extends GetView {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 20),
padding: EdgeInsets.only(top: kIsWeb ? 10: 20),
child: Text(
'${prefixEmailAddress.asName(context)}:',
style: TextStyle(fontSize: 14, color: AppColor.colorEmailAddressPrefix))),
@@ -1,19 +1,15 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/main/routes/router_arguments.dart';
class ComposerArguments with EquatableMixin {
class ComposerArguments extends RouterArguments {
final EmailActionType emailActionType;
final PresentationEmail? presentationEmail;
final List<EmailContent>? emailContents;
final EmailAddress? emailAddress;
final List<Attachment>? attachments;
final Session session;
final UserProfile userProfile;
final Map<Role, MailboxId> mapMailboxId;
final Role? mailboxRole;
ComposerArguments({
@@ -23,9 +19,6 @@ class ComposerArguments with EquatableMixin {
this.attachments,
this.mailboxRole,
this.emailAddress,
required this.session,
required this.userProfile,
required this.mapMailboxId,
});
@override
@@ -35,8 +28,5 @@ class ComposerArguments with EquatableMixin {
attachments,
mailboxRole,
emailAddress,
session,
userProfile,
mapMailboxId,
];
}
@@ -2,7 +2,9 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
typedef OnBackActionClick = void Function();
typedef OnEmailActionClick = void Function(PresentationEmail, EmailActionType);
@@ -51,9 +53,9 @@ class AppBarMailWidgetBuilder {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
children: [
if ((!_responsiveUtils.isDesktop(_context) && !_responsiveUtils.isTabletLarge(_context)))
if (_conditionShowBackButton())
_buildBackButton(),
if ((!_responsiveUtils.isDesktop(_context) && !_responsiveUtils.isTabletLarge(_context)))
if (_conditionShowBackButton())
Expanded(child: _buildMailboxName()),
if (_presentationEmail != null) _buildListOptionButton(),
]
@@ -62,6 +64,16 @@ class AppBarMailWidgetBuilder {
);
}
bool _conditionShowBackButton() {
if (Get.currentRoute == AppRoutes.MAILBOX_DASHBOARD) {
return !_responsiveUtils.isDesktop(_context) && !_responsiveUtils.isTabletLarge(_context);
} else if (Get.currentRoute == AppRoutes.EMAIL) {
return _responsiveUtils.isMobileDevice(_context) || _responsiveUtils.isTablet(_context);
} else {
return false;
}
}
Widget _buildBackButton() {
return IconButton(
icon: SvgPicture.asset(_imagePaths.icBack, width: 20, height: 20, color: AppColor.colorTextButton, fit: BoxFit.fill),
@@ -78,7 +78,7 @@ class AttachmentFileTileBuilder {
}},
leading: Transform(
transform: Matrix4.translationValues(14.0, 2.0, 0.0),
child: SvgPicture.asset(_imagePaths.icAttachmentFile, width: 24, height: 24, fit: BoxFit.fill)),
child: SvgPicture.asset(_imagePaths.icFileAttachment, width: 24, height: 24, fit: BoxFit.fill)),
title: Transform(
transform: Matrix4.translationValues(-8.0, -8.0, 0.0),
child: Text(
@@ -453,6 +453,8 @@ class MailboxController extends BaseMailboxController {
..title(AppLocalizations.of(context).delete_mailboxes)
..content(AppLocalizations.of(context).message_confirmation_dialog_delete_mailbox(presentationMailbox.name?.name ?? ''))
..addIcon(SvgPicture.asset(_imagePaths.icRemoveDialog, fit: BoxFit.fill))
..colorConfirmButton(AppColor.colorConfirmActionDialog)
..styleTextConfirmButton(TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorActionDeleteConfirmDialog))
..onCloseButtonAction(() => popBack())
..onConfirmButtonAction(AppLocalizations.of(context).delete, () => _deleteMailboxAction(presentationMailbox))
..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack()))
@@ -11,16 +11,20 @@ import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_bindings.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/email_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_user_profile_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_user_profile_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_email_drafts_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_action.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/search_state.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/router_arguments.dart';
class MailboxDashBoardController extends BaseController {
@@ -36,6 +40,7 @@ class MailboxDashBoardController extends BaseController {
final userProfile = Rxn<UserProfile>();
final searchState = SearchState.initial().obs;
final suggestionSearch = <String>[].obs;
final dashBoardAction = DashBoardAction.none.obs;
SearchQuery? searchQuery;
Session? sessionCurrent;
@@ -43,6 +48,7 @@ class MailboxDashBoardController extends BaseController {
Map<MailboxId, PresentationMailbox> mapMailbox = Map();
TextEditingController searchInputController = TextEditingController();
FocusNode searchFocus = FocusNode();
RouterArguments? routerArguments;
MailboxDashBoardController(
this._getUserProfileInteractor,
@@ -225,6 +231,20 @@ class MailboxDashBoardController extends BaseController {
}
}
void dispatchDashBoardAction(DashBoardAction action, {RouterArguments? arguments}) {
switch(action) {
case DashBoardAction.none:
routerArguments = null;
Get.delete<ComposerController>();
break;
case DashBoardAction.compose:
routerArguments = arguments;
ComposerBindings().dependencies();
break;
}
dashBoardAction.value = action;
}
@override
void onClose() {
searchInputController.dispose();
@@ -1,9 +1,12 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'
if (dart.library.html) 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart';
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_view.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_action.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
class MailboxDashBoardView extends GetWidget<MailboxDashBoardController> {
@@ -30,35 +33,40 @@ class MailboxDashBoardView extends GetWidget<MailboxDashBoardController> {
desktop: SizedBox.shrink()
),
drawerEnableOpenDragGesture: !responsiveUtils.isDesktop(context),
body: ResponsiveWidget(
responsiveUtils: responsiveUtils,
desktop: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(flex: 1, child: MailboxView()),
Expanded(flex: 1, child: ThreadView()),
Expanded(flex: 2, child: EmailView()),
],
),
),
tabletLarge: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: responsiveUtils.getSizeWidthScreen(context) * 0.35,
padding: EdgeInsets.symmetric(horizontal: 16),
child: ThreadView()
body: Stack(children: [
ResponsiveWidget(
responsiveUtils: responsiveUtils,
desktop: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(flex: 1, child: MailboxView()),
Expanded(flex: 1, child: ThreadView()),
Expanded(flex: 2, child: EmailView()),
],
),
Expanded(child: EmailView()),
],
),
),
tablet: ThreadView(),
mobile: ThreadView()),
),
tabletLarge: Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: responsiveUtils.getSizeWidthScreen(context) * 0.35,
padding: EdgeInsets.symmetric(horizontal: 16),
child: ThreadView()
),
Expanded(child: EmailView()),
],
),
),
tablet: ThreadView(),
mobile: ThreadView()),
Obx(() => controller.dashBoardAction == DashBoardAction.compose
? ComposerView()
: SizedBox.shrink()),
]),
);
}
}
@@ -0,0 +1,5 @@
enum DashBoardAction {
none,
compose
}
@@ -1,7 +1,6 @@
import 'dart:math';
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
@@ -28,6 +27,7 @@ import 'package:tmail_ui_user/features/email/presentation/model/composer_argumen
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_action.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart';
@@ -675,10 +675,6 @@ class ThreadController extends BaseController {
}
}
bool canComposeEmail() => mailboxDashBoardController.sessionCurrent != null
&& mailboxDashBoardController.userProfile.value != null
&& mailboxDashBoardController.mapDefaultMailboxId.isNotEmpty;
bool isSelectionEnabled() => currentSelectMode.value == SelectMode.ACTIVE;
void pressEmailSelectionAction(BuildContext context, EmailActionType actionType, List<PresentationEmail> selectionEmail) {
@@ -709,27 +705,25 @@ class ThreadController extends BaseController {
}
void editEmail(PresentationEmail presentationEmail) {
if (canComposeEmail()) {
push(
AppRoutes.COMPOSER,
arguments: ComposerArguments(
emailActionType: EmailActionType.edit,
presentationEmail: presentationEmail,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role,
session: mailboxDashBoardController.sessionCurrent!,
userProfile: mailboxDashBoardController.userProfile.value!,
mapMailboxId: mailboxDashBoardController.mapDefaultMailboxId));
final arguments = ComposerArguments(
emailActionType: EmailActionType.edit,
presentationEmail: presentationEmail,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
if (kIsWeb) {
mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.compose, arguments: arguments);
} else {
push(AppRoutes.COMPOSER, arguments: arguments);
}
}
void composeEmailAction() {
if (canComposeEmail()) {
push(
AppRoutes.COMPOSER,
arguments: ComposerArguments(
session: mailboxDashBoardController.sessionCurrent!,
userProfile: mailboxDashBoardController.userProfile.value!,
mapMailboxId: mailboxDashBoardController.mapDefaultMailboxId));
if (kIsWeb) {
if (mailboxDashBoardController.dashBoardAction != DashBoardAction.compose) {
mailboxDashBoardController.dispatchDashBoardAction(DashBoardAction.compose, arguments: ComposerArguments());
}
} else {
push(AppRoutes.COMPOSER, arguments: ComposerArguments());
}
}
}
@@ -773,4 +773,47 @@ class AppLocalizations {
'Email address copied to clipboard',
name: 'email_address_copied_to_clipboard');
}
String get minimize {
return Intl.message(
'Minimize',
name: 'minimize');
}
String get fullscreen {
return Intl.message(
'Fullscreen',
name: 'fullscreen');
}
String get close {
return Intl.message(
'Close',
name: 'close');
}
String get send {
return Intl.message(
'Send',
name: 'send');
}
String get attachments {
return Intl.message(
'Attachments',
name: 'attachments');
}
String get show_all {
return Intl.message(
'Show all',
name: 'show_all');
}
String get message_confirm_send_email {
return Intl.message(
'Are you sure to send messages without a subject?',
name: 'message_confirm_send_email');
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
import 'package:get/get_navigation/src/routes/get_route.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_bindings.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'
if (dart.library.html) 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_bindings.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_view.dart';
import 'package:tmail_ui_user/features/home/presentation/home_bindings.dart';
+3
View File
@@ -0,0 +1,3 @@
import 'package:equatable/equatable.dart';
abstract class RouterArguments with EquatableMixin {}