From ee615c7ccde4c241531b840119083c343c3832c8 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 26 Aug 2021 17:17:08 +0700 Subject: [PATCH] TF-46 Create composer widget with add presentation --- .../presentation/composer_bindings.dart | 9 + .../presentation/composer_controller.dart | 172 +++++++++++++ .../composer/presentation/composer_view.dart | 146 +++++++++++ .../email_action_type_extension.dart | 18 ++ .../prefix_email_address_extension.dart | 18 ++ ...email_address_composer_widget_builder.dart | 226 ++++++++++++++++++ .../top_bar_composer_widget_builder.dart | 83 +++++++ lib/l10n/intl_en.arb | 33 +++ lib/l10n/intl_fr.arb | 33 +++ lib/l10n/intl_messages.arb | 35 ++- lib/l10n/intl_ru.arb | 33 +++ lib/l10n/intl_vi.arb | 33 +++ lib/main/localizations/app_localizations.dart | 43 ++++ lib/main/pages/app_pages.dart | 6 + pubspec.yaml | 9 + 15 files changed, 896 insertions(+), 1 deletion(-) create mode 100644 lib/features/composer/presentation/composer_bindings.dart create mode 100644 lib/features/composer/presentation/composer_controller.dart create mode 100644 lib/features/composer/presentation/composer_view.dart create mode 100644 lib/features/composer/presentation/extensions/email_action_type_extension.dart create mode 100644 lib/features/composer/presentation/extensions/prefix_email_address_extension.dart create mode 100644 lib/features/composer/presentation/widgets/email_address_composer_widget_builder.dart create mode 100644 lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart new file mode 100644 index 000000000..fa4a9ca60 --- /dev/null +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; + +class ComposerBindings extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => ComposerController()); + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart new file mode 100644 index 000000000..4c2ba7aa7 --- /dev/null +++ b/lib/features/composer/presentation/composer_controller.dart @@ -0,0 +1,172 @@ + +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:html_editor_enhanced/html_editor.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/email/presentation/model/attachment_file.dart'; +import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/email_content_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/model/message_content.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class ComposerController extends GetxController { + + final expandMode = ExpandMode.COLLAPSE.obs; + final composerArguments = Rxn(); + final isEnableEmailSendButton = false.obs; + final listReplyToEmailAddress = [].obs; + final htmlEditorController = HtmlEditorController(); + + final listEmailAddressSuggestion = [ + EmailAddress('Mike Jones', 'user1@linagora.com'), + EmailAddress('Mike Smith', 'user2@linagora.com'), + EmailAddress('Jane Smith', 'user3@linagora.com'), + EmailAddress('User4', 'user4@linagora.com'), + EmailAddress('User5', 'user5@linagora.com') + ]; + List listToEmailAddress = []; + List listCcEmailAddress = []; + List listBccEmailAddress = []; + + ComposerController(); + + @override + void onReady() { + super.onReady(); + _getSelectedEmail(); + } + + void _getSelectedEmail() { + final arguments = Get.arguments; + if (arguments is ComposerArguments) { + composerArguments.value = arguments; + _initToEmailAddress(); + } + } + + EmailActionType getEmailActionType() => composerArguments.value != null + ? composerArguments.value!.emailActionType + : EmailActionType.compose; + + String getEmailSubject(BuildContext context) { + if (composerArguments.value != null && composerArguments.value?.presentationEmail != null) { + final subject = composerArguments.value!.presentationEmail?.subject; + return '${composerArguments.value!.emailActionType.prefixSubjectComposer(context)} $subject'; + } + return ''; + } + + Tuple2? getHeaderEmailQuoted(String locale) { + if (composerArguments.value != null && composerArguments.value?.presentationEmail != null) { + final sentDate = composerArguments.value!.presentationEmail?.sentAt; + final emailAddress = composerArguments.value!.presentationEmail?.from.listEmailAddressToString(isFullEmailAddress: true) ?? ''; + return Tuple2(sentDate.formatDate(pattern: 'MMM d, y h:mm a', locale: locale), emailAddress); + } + return null; + } + + Tuple3, List, Session>? getContentEmailQuoted() { + if (composerArguments.value != null) { + final emailContent = composerArguments.value!.emailContent; + final session = composerArguments.value!.session; + if (emailContent != null) { + final messageContents = emailContent.getListMessageContent(); + final attachmentsInline = emailContent.getListAttachmentInline(); + return Tuple3(messageContents, attachmentsInline, session); + } + } + return null; + } + + void expandEmailAddressAction() { + final newExpandMode = expandMode.value == ExpandMode.COLLAPSE ? ExpandMode.EXPAND : ExpandMode.COLLAPSE; + expandMode.value = newExpandMode; + } + + void _initToEmailAddress() { + if (composerArguments.value != null + && composerArguments.value?.presentationEmail != null + && composerArguments.value?.emailActionType == EmailActionType.reply) { + final replyToEmailAddress = composerArguments.value!.presentationEmail?.replyTo; + final fromEmailAddress = composerArguments.value!.presentationEmail?.from; + if (replyToEmailAddress != null && replyToEmailAddress.isNotEmpty) { + listReplyToEmailAddress.value = replyToEmailAddress.toList(); + } else if (fromEmailAddress != null && fromEmailAddress.isNotEmpty) { + listReplyToEmailAddress.value = fromEmailAddress.toList(); + } + } + _updateStatusEmailSendButton(); + } + + void updateListEmailAddress(PrefixEmailAddress prefixEmailAddress, List newListEmailAddress) { + switch(prefixEmailAddress) { + case PrefixEmailAddress.to: + listToEmailAddress = newListEmailAddress; + listReplyToEmailAddress.clear(); + break; + case PrefixEmailAddress.cc: + listCcEmailAddress = newListEmailAddress; + break; + case PrefixEmailAddress.bcc: + listBccEmailAddress = newListEmailAddress; + break; + } + _updateStatusEmailSendButton(); + } + + void _updateStatusEmailSendButton() { + if (listToEmailAddress.isNotEmpty + || listBccEmailAddress.isNotEmpty + || listCcEmailAddress.isNotEmpty + || listReplyToEmailAddress.isNotEmpty) { + isEnableEmailSendButton.value = true; + } else { + isEnableEmailSendButton.value = false; + } + } + + String getBodyEmailQuotedAsHtml(BuildContext context, HtmlMessagePurifier htmlMessagePurifier) { + final headerEmailQuoted = getHeaderEmailQuoted(Localizations.localeOf(context).toLanguageTag()); + final contentEmail = getContentEmailQuoted(); + + final placeHolderBodyEmailEditor = AppLocalizations.of(context).hint_content_email_composer + .addBlockTag('p', attribute: 'style=\"font-size:14px;color:#182952;\"') + .addNewLineTag(); + + final headerEmailQuotedAsHtml = headerEmailQuoted != null + ? AppLocalizations.of(context).header_email_quoted(headerEmailQuoted.value1, headerEmailQuoted.value2) + .addBlockTag('p', attribute: 'style=\"font-size:14px;font-style:italic;color:#182952;\"') + : ''; + + var trustAsHtml = ''; + if (contentEmail != null && contentEmail.value1.isNotEmpty) { + + final messageContent = contentEmail.value1.first; + final attachmentInlines = contentEmail.value2; + final session = contentEmail.value3; + final accountId = session.accounts.keys.first; + + final message = (attachmentInlines.isNotEmpty && messageContent.hasImageInlineWithCid()) + ? '${messageContent.getContentHasInlineAttachment(session, accountId, attachmentInlines)}' + : '${messageContent.content}'; + + trustAsHtml = htmlMessagePurifier.purifyHtmlMessage(message, allowAttributes: {'style', 'input', 'form'}) + .addBlockTag('div', attribute: 'style=\"margin-left:8px;margin-right:8px;padding-left:12px;padding-right:12px;border-left:6px solid #EFEFEF;\"') + .addNewLineTag(count: 2); + } + + final emailQuotedHtml = '$placeHolderBodyEmailEditor$headerEmailQuotedAsHtml$trustAsHtml' + .addBlockTag('div', attribute: 'style=\"padding-right:16px;padding-left:16px;background-color:#FBFBFF;width:100%\"'); + + return emailQuotedHtml; + } + + void backToEmailViewAction() { + Get.back(); + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart new file mode 100644 index 000000000..eb2980aca --- /dev/null +++ b/lib/features/composer/presentation/composer_view.dart @@ -0,0 +1,146 @@ +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:html_editor_enhanced/html_editor.dart'; +import 'package:model/email/email_action_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/email_address_composer_widget_builder.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class ComposerView extends GetWidget { + + final responsiveUtils = Get.find(); + final imagePaths = Get.find(); + final htmlMessagePurifier = Get.find(); + final keyboardUtils = Get.find(); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + backgroundColor: AppColor.primaryLightColor, + body: SafeArea( + right: false, + left: false, + child: Container( + margin: EdgeInsets.zero, + alignment: Alignment.topCenter, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + _buildTopBar(context), + _buildEmailHeaderField(context), + Expanded(child: GestureDetector( + onTap: () => keyboardUtils.hideKeyboard(context), + child: Container( + color: AppColor.bgComposer, + child: _buildEmailBody(context), + ), + )) + ]) + ) + ), + ); + } + + Widget _buildTopBar(BuildContext context) { + return Padding( + padding: EdgeInsets.only(left: 20, right: 24, top: 12, bottom: 12), + child: Obx(() => (TopBarComposerWidgetBuilder(imagePaths, controller.isEnableEmailSendButton.value) + ..addBackActionClick(() => controller.backToEmailViewAction())) + .build()), + ); + } + + Widget _buildEmailHeaderField(BuildContext context) { + return Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.only(top: 20), + color: AppColor.bgComposer, + alignment: Alignment.topCenter, + child: Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Obx(() { + return (EmailAddressComposerWidgetBuilder( + context, + imagePaths, + controller.expandMode.value, + controller.listEmailAddressSuggestion, + controller.listReplyToEmailAddress.isNotEmpty ? controller.listReplyToEmailAddress : controller.listToEmailAddress, + controller.listCcEmailAddress, + controller.listBccEmailAddress, + controller.composerArguments.value?.userProfile) + ..addExpandAddressActionClick(() => controller.expandEmailAddressAction()) + ..addOnUpdateListEmailAddressAction((prefixEmailAddress, listEmailAddress) => controller.updateListEmailAddress(prefixEmailAddress, listEmailAddress))) + .build(); + })), + _buildSubjectEmail(context), + Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + height: 1, + color: AppColor.dividerColor, + ) + ], + ), + ); + } + + Widget _buildSubjectEmail(BuildContext context) { + return Padding( + padding: EdgeInsets.only(left: 24, right: 24, bottom: 10, top: 8), + child: Obx(() => TextFieldBuilder() + .key(Key('subject_email_input')) + .textInputAction(TextInputAction.newline) + .maxLines(null) + .textStyle(TextStyle(color: AppColor.nameUserColor, fontSize: 14, fontWeight: FontWeight.w500)) + .textDecoration(InputDecoration( + hintText: AppLocalizations.of(context).subject_email, + hintStyle: TextStyle(color: AppColor.baseTextColor, fontSize: 14, fontWeight: FontWeight.w500), + contentPadding: EdgeInsets.zero, + filled: true, + border: InputBorder.none, + fillColor: AppColor.bgComposer)) + .setText(controller.getEmailSubject(context)) + .build()), + ); + } + + Widget _buildEmailBody(BuildContext context) { + return SingleChildScrollView( + physics : ClampingScrollPhysics(), + child: Container( + margin: EdgeInsets.zero, + color: AppColor.bgComposer, + padding: EdgeInsets.only(bottom: 30), + alignment: Alignment.topCenter, + child: _buildEmailBodyEditorQuoted(context) + ) + ); + } + + Widget _buildEmailBodyEditorQuoted(BuildContext context) { + return Obx(() => HtmlEditor( + key: Key('email_body_editor_quoted'), + controller: controller.htmlEditorController, + htmlEditorOptions: HtmlEditorOptions( + hint: AppLocalizations.of(context).hint_content_email_composer, + initialText: controller.getEmailActionType() != EmailActionType.compose + ? controller.getBodyEmailQuotedAsHtml(context, htmlMessagePurifier) + : null, + disableHorizontalScroll: false, + supportZoom: true, + horizontalScrollBarEnabled: true, + shouldEnsureVisible: true), + otherOptions: OtherOptions( + decoration: BoxDecoration(), + height: 600) + )); + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart new file mode 100644 index 000000000..0638febea --- /dev/null +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -0,0 +1,18 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:model/email/email_action_type.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +extension EmailActionTypeExtension on EmailActionType { + String prefixSubjectComposer(BuildContext context) { + switch(this) { + case EmailActionType.reply: + case EmailActionType.replyAll: + return AppLocalizations.of(context).prefix_reply_email; + case EmailActionType.forward: + return AppLocalizations.of(context).prefix_forward_email; + default: + return ''; + } + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/extensions/prefix_email_address_extension.dart b/lib/features/composer/presentation/extensions/prefix_email_address_extension.dart new file mode 100644 index 000000000..729ccde4e --- /dev/null +++ b/lib/features/composer/presentation/extensions/prefix_email_address_extension.dart @@ -0,0 +1,18 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +extension PrefixEmailAddressExtension on PrefixEmailAddress { + + String asName(BuildContext context) { + switch(this) { + case PrefixEmailAddress.to: + return AppLocalizations.of(context).to_email_address_prefix; + case PrefixEmailAddress.cc: + return AppLocalizations.of(context).cc_email_address_prefix; + case PrefixEmailAddress.bcc: + return AppLocalizations.of(context).bcc_email_address_prefix; + } + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/email_address_composer_widget_builder.dart b/lib/features/composer/presentation/widgets/email_address_composer_widget_builder.dart new file mode 100644 index 000000000..ffa0d2233 --- /dev/null +++ b/lib/features/composer/presentation/widgets/email_address_composer_widget_builder.dart @@ -0,0 +1,226 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_chips_input/flutter_chips_input.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; + +typedef OnOpenExpandAddressActionClick = void Function(); + +typedef OnUpdateListEmailAddressAction = void Function( + PrefixEmailAddress prefixEmailAddress, + List listEmailAddress +); + +class EmailAddressComposerWidgetBuilder { + + final ImagePaths _imagePaths; + final BuildContext _context; + final ExpandMode _expandMode; + final List _listEmailAddressSuggestion; + final List _listToEmailAddress; + final List _listCcEmailAddress; + final List _listBccEmailAddress; + final UserProfile? _userProfile; + + final keyToEmailAddress = new GlobalKey(); + + OnOpenExpandAddressActionClick? _onOpenExpandAddressActionClick; + OnUpdateListEmailAddressAction? _onUpdateListEmailAddressAction; + + EmailAddressComposerWidgetBuilder( + this._context, + this._imagePaths, + this._expandMode, + this._listEmailAddressSuggestion, + this._listToEmailAddress, + this._listCcEmailAddress, + this._listBccEmailAddress, + this._userProfile, + ); + + void addExpandAddressActionClick(OnOpenExpandAddressActionClick onOpenExpandAddressActionClick) { + _onOpenExpandAddressActionClick = onOpenExpandAddressActionClick; + } + + void addOnUpdateListEmailAddressAction(OnUpdateListEmailAddressAction onUpdateListEmailAddressAction) { + _onUpdateListEmailAddressAction = onUpdateListEmailAddressAction; + } + + Widget build() { + keyToEmailAddress.currentState?.selectSuggestion(_listToEmailAddress); + + return Theme( + data: ThemeData( + splashColor: Colors.transparent, + highlightColor: Colors.transparent), + child: MediaQuery( + data: MediaQueryData(padding: EdgeInsets.zero), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(bottom: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + 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: 14, color: AppColor.nameUserColor, fontWeight: FontWeight.w500))), + Expanded(child: RichText(text: TextSpan( + style: TextStyle(fontSize: 14, color: AppColor.nameUserColor), + children: [ + if (_userProfile?.getFullName().isNotEmpty == true) + TextSpan( + text: '${_userProfile?.getFullName()}', + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: AppColor.nameUserColor)), + if (_userProfile?.getEmailAddress()?.isNotEmpty == true) + TextSpan( + text: '<${_userProfile?.getEmailAddress()}>', + style: TextStyle(fontSize: 14, color: AppColor.nameUserColor)) + ]))) + ])), + _buildEmailAddressWidget(PrefixEmailAddress.to, hasExpandButton: true), + if (_expandMode == ExpandMode.EXPAND) _buildEmailAddressWidget(PrefixEmailAddress.cc), + if (_expandMode == ExpandMode.EXPAND) _buildEmailAddressWidget(PrefixEmailAddress.bcc), + ], + ) + ) + ); + } + + Widget _buildEmailAddressWidget(PrefixEmailAddress prefixEmailAddress, {bool hasExpandButton = false}) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(right: 8, top: _paddingTo(prefixEmailAddress)), + child: Text( + '${prefixEmailAddress.asName(_context)}:', + style: TextStyle(fontSize: 14, color: AppColor.nameUserColor, fontWeight: FontWeight.w500))), + Expanded(child: ChipsInput( + key: prefixEmailAddress == PrefixEmailAddress.to + ? keyToEmailAddress + : Key('${prefixEmailAddress.toString()}_email_address_input'), + initialValue: _getListEmailAddressCurrent(prefixEmailAddress), + textStyle: TextStyle(color: AppColor.nameUserColor, fontSize: 14, fontWeight: FontWeight.w500), + decoration: InputDecoration( + border: InputBorder.none, + hintText: AppLocalizations.of(_context).hint_text_email_address, + hintMaxLines: 1, + hintStyle: TextStyle(color: AppColor.baseTextColor, fontSize: 14, fontWeight: FontWeight.w500)), + initialSuggestions: _listEmailAddressSuggestion, + findSuggestions: (query) { + if (query.isNotEmpty) { + final lowercaseQuery = query.toLowerCase(); + return _listEmailAddressSuggestion + .where((emailAddress) => emailAddress.getName().toLowerCase().contains(query.toLowerCase()) + || emailAddress.getEmail().toLowerCase().contains(query.toLowerCase())) + .toList(growable: false) + ..sort((email1, email2) => email1.asString().toLowerCase().indexOf(lowercaseQuery).compareTo(email2.asString().toLowerCase().indexOf(lowercaseQuery))); + } + return []; + }, + onChanged: (data) { + print('data: $data'); + if (_onUpdateListEmailAddressAction != null) { + _onUpdateListEmailAddressAction!(prefixEmailAddress, data); + } + }, + chipBuilder: (context, state, emailAddress) { + return InputChip( + key: ObjectKey(emailAddress), + label: Text(emailAddress.asString()), + labelStyle: TextStyle(color: AppColor.nameUserColor, fontSize: 12, fontWeight: FontWeight.w500), + backgroundColor: AppColor.emailAddressChipColor, + padding: EdgeInsets.all(3), + deleteIconColor: AppColor.baseTextColor, + avatar: CircleAvatar( + backgroundColor: AppColor.avatarColor, + child: Text( + emailAddress.asString().isNotEmpty ? emailAddress.asString()[0].toUpperCase() : '', + style: TextStyle(color: AppColor.appColor, fontSize: 12, fontWeight: FontWeight.w500))), + onDeleted: () => state.deleteChip(emailAddress), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ); + }, + suggestionBuilder: (context, state, emailAddress) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.white), + color: Colors.white), + child: ListTile( + key: ObjectKey(emailAddress), + leading: CircleAvatar( + backgroundColor: AppColor.avatarColor, + child: Text( + emailAddress.asString().isNotEmpty ? emailAddress.asString()[0].toUpperCase() : '', + style: TextStyle(color: AppColor.appColor, fontSize: 16, fontWeight: FontWeight.w500))), + title: Text( + emailAddress.asString(), + style: TextStyle(color: AppColor.nameUserColor, fontSize: 14, fontWeight: FontWeight.w500)), + subtitle: emailAddress.getName().isNotEmpty && emailAddress.getEmail().isNotEmpty + ? Text( + emailAddress.getEmail(), + style: TextStyle(color: AppColor.baseTextColor, fontSize: 12, fontWeight: FontWeight.w500)) + : null, + onTap: () => state.selectSuggestion(emailAddress), + ), + ); + })), + if (hasExpandButton) _buildButtonExpandAddress() + ] + ); + } + + Widget _buildButtonExpandAddress() { + return GestureDetector( + onTap: () { + if (_onOpenExpandAddressActionClick != null) { + _onOpenExpandAddressActionClick!(); + }}, + child: Padding( + padding: EdgeInsets.only(top: 20), + child: SvgPicture.asset( + _expandMode == ExpandMode.EXPAND ? _imagePaths.icExpandAddress : _imagePaths.icMoreReceiver, + width: 20, + height: 20, + fit: BoxFit.fill), + ), + ); + } + + List _getListEmailAddressCurrent(PrefixEmailAddress prefixEmailAddress) { + switch(prefixEmailAddress) { + case PrefixEmailAddress.to: + return _listToEmailAddress; + case PrefixEmailAddress.cc: + return _listCcEmailAddress; + case PrefixEmailAddress.bcc: + return _listBccEmailAddress; + } + } + + double _paddingTo(PrefixEmailAddress prefixEmailAddress) { + switch(prefixEmailAddress) { + case PrefixEmailAddress.to: + return _listToEmailAddress.isNotEmpty ? 20 : 18; + case PrefixEmailAddress.cc: + return _listCcEmailAddress.isNotEmpty ? 20 : 18; + case PrefixEmailAddress.bcc: + return _listBccEmailAddress.isNotEmpty ? 20 : 18; + } + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart b/lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart new file mode 100644 index 000000000..de23c4846 --- /dev/null +++ b/lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart @@ -0,0 +1,83 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +typedef OnBackActionClick = void Function(); +typedef OnSendEmailActionClick = void Function(); + +class TopBarComposerWidgetBuilder { + OnBackActionClick? _onBackActionClick; + OnSendEmailActionClick? _onSendEmailActionClick; + + final ImagePaths _imagePaths; + bool isEnableEmailSendButton; + + TopBarComposerWidgetBuilder(this._imagePaths, this.isEnableEmailSendButton); + + void addBackActionClick(OnBackActionClick onBackActionClick) { + _onBackActionClick = onBackActionClick; + } + + void addSendEmailActionClick(OnSendEmailActionClick onSendEmailActionClick) { + _onSendEmailActionClick = onSendEmailActionClick; + } + + Widget build() { + return Container( + key: Key('top_bar_composer_widget'), + alignment: Alignment.center, + padding: EdgeInsets.zero, + color: Colors.white, + child: MediaQuery( + data: MediaQueryData(padding: EdgeInsets.zero), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded(child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children:[_buildBackButton()])), + _buildListOptionButton(), + ] + ) + ) + ); + } + + Widget _buildBackButton() { + return ButtonBuilder(_imagePaths.icComposerClose) + .padding(5) + .size(30) + .onPressActionClick(() { + if (_onBackActionClick != null) { + _onBackActionClick!(); + }}) + .build(); + } + + Widget _buildListOptionButton() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ButtonBuilder(_imagePaths.icComposerFileShare).key(Key('button_file_share')).build(), + SizedBox(width: 10), + ButtonBuilder(_imagePaths.icShare).key(Key('button_attachment')).build(), + SizedBox(width: 10), + ButtonBuilder(_imagePaths.icComposerSend) + .key(Key('button_send_email')) + .color(isEnableEmailSendButton ? AppColor.enableSendEmailButtonColor : AppColor.disableSendEmailButtonColor) + .onPressActionClick(() { + if (_onSendEmailActionClick != null && isEnableEmailSendButton) { + _onSendEmailActionClick!(); + } + }) + .build(), + SizedBox(width: 10), + ButtonBuilder(_imagePaths.icComposerMenu).key(Key('button_menu_composer')).build(), + ] + ); + } +} \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index ad0b6dcde..1ce56cd65 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -109,5 +109,38 @@ "@cc_email_address_prefix": { "type": "text", "placeholders": {} + }, + "hint_text_email_address": "Name or email address", + "@hint_text_email_address": { + "type": "text", + "placeholders": {} + }, + "subject_email": "Subject", + "@subject_email": { + "type": "text", + "placeholders": {} + }, + "hint_content_email_composer": "Start writing your email here", + "@hint_content_email_composer": { + "type": "text", + "placeholders": {} + }, + "header_email_quoted": "On {sentDate}, from {emailAddress}", + "@header_email_quoted": { + "type": "text", + "placeholders": { + "sentDate": {}, + "emailAddress": {} + } + }, + "prefix_reply_email": "Re:", + "@prefix_reply_email": { + "type": "text", + "placeholders": {} + }, + "prefix_forward_email": "Fwd:", + "@prefix_forward_email": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index ad0b6dcde..1ce56cd65 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -109,5 +109,38 @@ "@cc_email_address_prefix": { "type": "text", "placeholders": {} + }, + "hint_text_email_address": "Name or email address", + "@hint_text_email_address": { + "type": "text", + "placeholders": {} + }, + "subject_email": "Subject", + "@subject_email": { + "type": "text", + "placeholders": {} + }, + "hint_content_email_composer": "Start writing your email here", + "@hint_content_email_composer": { + "type": "text", + "placeholders": {} + }, + "header_email_quoted": "On {sentDate}, from {emailAddress}", + "@header_email_quoted": { + "type": "text", + "placeholders": { + "sentDate": {}, + "emailAddress": {} + } + }, + "prefix_reply_email": "Re:", + "@prefix_reply_email": { + "type": "text", + "placeholders": {} + }, + "prefix_forward_email": "Fwd:", + "@prefix_forward_email": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index fe2f98e17..2d88f3caf 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2021-08-18T17:44:10.952718", + "@@last_modified": "2021-08-27T17:46:09.196916", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -109,5 +109,38 @@ "@cc_email_address_prefix": { "type": "text", "placeholders": {} + }, + "hint_text_email_address": "Name or email address", + "@hint_text_email_address": { + "type": "text", + "placeholders": {} + }, + "subject_email": "Subject", + "@subject_email": { + "type": "text", + "placeholders": {} + }, + "hint_content_email_composer": "Start writing your email here", + "@hint_content_email_composer": { + "type": "text", + "placeholders": {} + }, + "header_email_quoted": "On {sentDate}, from {emailAddress}", + "@header_email_quoted": { + "type": "text", + "placeholders": { + "sentDate": {}, + "emailAddress": {} + } + }, + "prefix_reply_email": "Re:", + "@prefix_reply_email": { + "type": "text", + "placeholders": {} + }, + "prefix_forward_email": "Fwd:", + "@prefix_forward_email": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index ad0b6dcde..1ce56cd65 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -109,5 +109,38 @@ "@cc_email_address_prefix": { "type": "text", "placeholders": {} + }, + "hint_text_email_address": "Name or email address", + "@hint_text_email_address": { + "type": "text", + "placeholders": {} + }, + "subject_email": "Subject", + "@subject_email": { + "type": "text", + "placeholders": {} + }, + "hint_content_email_composer": "Start writing your email here", + "@hint_content_email_composer": { + "type": "text", + "placeholders": {} + }, + "header_email_quoted": "On {sentDate}, from {emailAddress}", + "@header_email_quoted": { + "type": "text", + "placeholders": { + "sentDate": {}, + "emailAddress": {} + } + }, + "prefix_reply_email": "Re:", + "@prefix_reply_email": { + "type": "text", + "placeholders": {} + }, + "prefix_forward_email": "Fwd:", + "@prefix_forward_email": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index ad0b6dcde..1ce56cd65 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -109,5 +109,38 @@ "@cc_email_address_prefix": { "type": "text", "placeholders": {} + }, + "hint_text_email_address": "Name or email address", + "@hint_text_email_address": { + "type": "text", + "placeholders": {} + }, + "subject_email": "Subject", + "@subject_email": { + "type": "text", + "placeholders": {} + }, + "hint_content_email_composer": "Start writing your email here", + "@hint_content_email_composer": { + "type": "text", + "placeholders": {} + }, + "header_email_quoted": "On {sentDate}, from {emailAddress}", + "@header_email_quoted": { + "type": "text", + "placeholders": { + "sentDate": {}, + "emailAddress": {} + } + }, + "prefix_reply_email": "Re:", + "@prefix_reply_email": { + "type": "text", + "placeholders": {} + }, + "prefix_forward_email": "Fwd:", + "@prefix_forward_email": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 9023e51be..5e528e506 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -156,5 +156,48 @@ class AppLocalizations { name: 'cc_email_address_prefix', ); } + + String get hint_text_email_address { + return Intl.message( + 'Name or email address', + name: 'hint_text_email_address', + ); + } + + String get subject_email { + return Intl.message( + 'Subject', + name: 'subject_email', + ); + } + + String get hint_content_email_composer { + return Intl.message( + 'Start writing your email here', + name: 'hint_content_email_composer', + ); + } + + String header_email_quoted(String sentDate, String emailAddress) { + return Intl.message( + 'On $sentDate, from $emailAddress', + name: 'header_email_quoted', + args: [sentDate, emailAddress] + ); + } + + String get prefix_reply_email { + return Intl.message( + 'Re:', + name: 'prefix_reply_email', + ); + } + + String get prefix_forward_email { + return Intl.message( + 'Fwd:', + name: 'prefix_forward_email', + ); + } } diff --git a/lib/main/pages/app_pages.dart b/lib/main/pages/app_pages.dart index 05a9445f2..83743f655 100644 --- a/lib/main/pages/app_pages.dart +++ b/lib/main/pages/app_pages.dart @@ -1,4 +1,6 @@ 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/home/presentation/home_bindings.dart'; import 'package:tmail_ui_user/features/home/presentation/home_view.dart'; import 'package:tmail_ui_user/features/login/presentation/login_bindings.dart'; @@ -39,5 +41,9 @@ class AppPages { GetPage( name: AppRoutes.EMAIL, page: () => EmailView()), + GetPage( + name: AppRoutes.COMPOSER, + page: () => ComposerView(), + binding: ComposerBindings()), ]; } diff --git a/pubspec.yaml b/pubspec.yaml index 1abe5f02b..fcc1244b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -89,6 +89,15 @@ dependencies: # flutter_widget_from_html flutter_widget_from_html: 0.6.2 + # flutter_chips_input + flutter_chips_input: 1.10.0 + + # html_editor_enhanced + html_editor_enhanced: + git: + url: git://github.com/dab246/html-editor-enhanced.git + ref: master + dev_dependencies: flutter_test: sdk: flutter