diff --git a/assets/images/ic_attachment_file.svg b/assets/images/ic_attachment_file.svg
deleted file mode 100644
index 5dbd3c36c..000000000
--- a/assets/images/ic_attachment_file.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/assets/images/ic_attachments_composer.svg b/assets/images/ic_attachments_composer.svg
new file mode 100644
index 000000000..9dacdfabe
--- /dev/null
+++ b/assets/images/ic_attachments_composer.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/ic_close_composer.svg b/assets/images/ic_close_composer.svg
new file mode 100644
index 000000000..ab4826af2
--- /dev/null
+++ b/assets/images/ic_close_composer.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/ic_delete_attachment.svg b/assets/images/ic_delete_attachment.svg
new file mode 100644
index 000000000..e9d62d6be
--- /dev/null
+++ b/assets/images/ic_delete_attachment.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/ic_delete_composer.svg b/assets/images/ic_delete_composer.svg
new file mode 100644
index 000000000..52f30249f
--- /dev/null
+++ b/assets/images/ic_delete_composer.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/ic_file_attachment.svg b/assets/images/ic_file_attachment.svg
new file mode 100644
index 000000000..9524125b7
--- /dev/null
+++ b/assets/images/ic_file_attachment.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/images/ic_fullscreen_composer.svg b/assets/images/ic_fullscreen_composer.svg
new file mode 100644
index 000000000..9f92b83d7
--- /dev/null
+++ b/assets/images/ic_fullscreen_composer.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/ic_minimize_composer.svg b/assets/images/ic_minimize_composer.svg
new file mode 100644
index 000000000..ac0e2f5f4
--- /dev/null
+++ b/assets/images/ic_minimize_composer.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/ic_send_composer.svg b/assets/images/ic_send_composer.svg
new file mode 100644
index 000000000..4a446e6f8
--- /dev/null
+++ b/assets/images/ic_send_composer.svg
@@ -0,0 +1,3 @@
+
diff --git a/core/lib/core.dart b/core/lib/core.dart
index 4c2cdfa04..edba7690d 100644
--- a/core/lib/core.dart
+++ b/core/lib/core.dart
@@ -32,6 +32,7 @@ export 'presentation/views/text/rich_text_builder.dart';
export 'presentation/views/responsive/responsive_widget.dart';
export 'presentation/views/list/tree_view.dart';
export 'presentation/views/button/button_builder.dart';
+export 'presentation/views/button/icon_button_web.dart';
export 'presentation/views/image/avatar_builder.dart';
export 'presentation/views/list/sliver_grid_delegate_fixed_height.dart';
export 'presentation/views/text/text_form_field_builder.dart';
diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart
index b2fac64cd..af93e8f7b 100644
--- a/core/lib/presentation/extensions/color_extension.dart
+++ b/core/lib/presentation/extensions/color_extension.dart
@@ -79,12 +79,12 @@ extension AppColor on Color {
static const colorInputBorderCreateMailbox = Color(0x1F000000);
static const colorInputBackgroundErrorVerifyName = Color(0xFFFAEBEB);
static const colorInputBackgroundCreateMailbox = Color(0xFFF2F3F5);
- static const colorHintInputCreateMailbox= Color(0xFFA9B4C2);
- static const colorMessageConfirmDialog= Color(0xFF6D7885);
- static const colorActionDeleteConfirmDialog= Color(0xFFE64646);
- static const colorActionCancelDialog= Color(0xFF007AFF);
- static const colorMessageDialog= Color(0xFF222222);
- static const colorConfirmActionDialog= Color(0xFFF2F2F2);
+ static const colorHintInputCreateMailbox = Color(0xFFA9B4C2);
+ static const colorMessageConfirmDialog = Color(0xFF6D7885);
+ static const colorActionDeleteConfirmDialog = Color(0xFFE64646);
+ static const colorActionCancelDialog = Color(0xFF007AFF);
+ static const colorMessageDialog = Color(0xFF222222);
+ static const colorConfirmActionDialog = Color(0xFFF2F2F2);
static const colorEmailAddress = Color(0xFF333333);
static const colorHintEmailAddressInput = Color(0x993C3C43);
static const colorDividerComposer = Color(0xFFC6C6C8);
@@ -94,6 +94,10 @@ extension AppColor on Color {
static const colorEmailAddressPrefix = Color(0xFF9AA7B6);
static const colorEmailAddressTag = Color(0x146D7885);
static const colorLineLeftEmailView = Color(0x2999A2AD);
+ static const colorShadowComposer = Color(0x1F000000);
+ static const colorBottomBarComposer = Color(0x5CEBEDF0);
+ static const colorShadowComposerFullScreen = Color(0x33000000);
+ static const colorCancelButton = Color(0xFFF2F2F2);
static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart
index 6c4cac104..fa43be498 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -10,7 +10,6 @@ class ImagePaths {
String get icBack => _getImagePath('ic_back.svg');
String get icEyeDisable => _getImagePath('ic_eye_disable.svg');
String get icShare => _getImagePath('ic_share.svg');
- String get icAttachmentFile => _getImagePath('ic_attachment_file.svg');
String get icMoreReceiver => _getImagePath('ic_more_receiver.svg');
String get icComposerClose => _getImagePath('ic_composer_close.svg');
String get icComposerSend => _getImagePath('ic_composer_send.svg');
@@ -66,6 +65,14 @@ class ImagePaths {
String get icDeleteEmail => _getImagePath('ic_delete_email.svg');
String get icNewMessage => _getImagePath('ic_new_message.svg');
String get icUnreadStatus => _getImagePath('ic_unread_status.svg');
+ String get icAttachmentsComposer => _getImagePath('ic_attachments_composer.svg');
+ String get icCloseComposer => _getImagePath('ic_close_composer.svg');
+ String get icFullScreenComposer => _getImagePath('ic_fullscreen_composer.svg');
+ String get icMinimizeComposer => _getImagePath('ic_minimize_composer.svg');
+ String get icSendComposer => _getImagePath('ic_send_composer.svg');
+ String get icDeleteComposer => _getImagePath('ic_delete_composer.svg');
+ String get icFileAttachment => _getImagePath('ic_file_attachment.svg');
+ String get icDeleteAttachment => _getImagePath('ic_delete_attachment.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
diff --git a/core/lib/presentation/utils/responsive_utils.dart b/core/lib/presentation/utils/responsive_utils.dart
index d324e980a..85ecf2770 100644
--- a/core/lib/presentation/utils/responsive_utils.dart
+++ b/core/lib/presentation/utils/responsive_utils.dart
@@ -15,7 +15,7 @@ class ResponsiveUtils {
final double desktopVerticalMargin = 120.0;
final double desktopHorizontalMargin = 200.0;
- bool isMobileDevice(BuildContext context) => MediaQuery.of(context).size.shortestSide < 600;
+ bool isMobileDevice(BuildContext context) => MediaQuery.of(context).size.shortestSide < minTabletWidth;
double getSizeWidthScreen(BuildContext context) => MediaQuery.of(context).size.width;
diff --git a/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart b/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart
index 6359c62b6..40dc2a552 100644
--- a/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart
+++ b/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart
@@ -2,6 +2,7 @@
import 'package:core/core.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
+import 'package:pointer_interceptor/pointer_interceptor.dart';
typedef OnConfirmActionClick = void Function();
typedef OnCancelActionClick = void Function();
@@ -15,6 +16,9 @@ class ConfirmationDialogActionSheetBuilder {
String? _cancelText;
OnConfirmActionClick?_onConfirmActionClick;
OnCancelActionClick? _onCancelActionClick;
+ TextStyle? _styleConfirmButton;
+ TextStyle? _styleCancelButton;
+ TextStyle? _styleMessage;
ConfirmationDialogActionSheetBuilder(this._context);
@@ -32,11 +36,23 @@ class ConfirmationDialogActionSheetBuilder {
_messageText = message;
}
+ void styleMessage(TextStyle? style) {
+ _styleMessage = style;
+ }
+
+ void styleConfirmButton(TextStyle? style) {
+ _styleConfirmButton = style;
+ }
+
+ void styleCancelButton(TextStyle? style) {
+ _styleCancelButton = style;
+ }
+
void show() {
showCupertinoModalPopup(
context: _context,
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
- builder: (context) => CupertinoActionSheet(
+ builder: (context) => PointerInterceptor(child: CupertinoActionSheet(
actions: [
Container(
padding: EdgeInsets.symmetric(vertical: 8, horizontal: 10),
@@ -45,27 +61,27 @@ class ConfirmationDialogActionSheetBuilder {
child: Text(
_messageText ?? '',
textAlign: TextAlign.center,
- style: TextStyle(fontSize: 14, color: AppColor.colorMessageConfirmDialog)),
+ style: _styleMessage ?? TextStyle(fontSize: 14, color: AppColor.colorMessageConfirmDialog)),
onPressed: () => {},
)
),
Container(
- color: Colors.white,
- child: CupertinoActionSheetAction(
- child: Text(
- _confirmText ?? '',
- style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColor.colorActionDeleteConfirmDialog)),
- onPressed: () => _onConfirmActionClick?.call(),
- )
+ color: Colors.white,
+ child: CupertinoActionSheetAction(
+ child: Text(
+ _confirmText ?? '',
+ style: _styleConfirmButton ?? TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColor.colorActionDeleteConfirmDialog)),
+ onPressed: () => _onConfirmActionClick?.call(),
+ )
),
],
cancelButton: CupertinoActionSheetAction(
child: Text(
_cancelText ?? '',
- style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColor.colorActionCancelDialog)),
+ style: _styleCancelButton ?? TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColor.colorActionCancelDialog)),
onPressed: () => _onCancelActionClick?.call(),
),
- )
+ ))
);
}
}
\ No newline at end of file
diff --git a/core/lib/presentation/views/button/icon_button_web.dart b/core/lib/presentation/views/button/icon_button_web.dart
new file mode 100644
index 000000000..56db2a026
--- /dev/null
+++ b/core/lib/presentation/views/button/icon_button_web.dart
@@ -0,0 +1,43 @@
+
+import 'package:flutter/material.dart';
+
+typedef IconWebCallback = void Function();
+typedef IconWebHasPositionCallback = void Function(RelativeRect);
+
+Widget buildIconWeb({
+ required Widget icon,
+ String? tooltip,
+ IconWebCallback? onTap,
+ EdgeInsets? iconPadding,
+ double? iconSize,
+}) {
+ return Material(
+ type: MaterialType.circle,
+ color: Colors.transparent,
+ child: IconButton(icon: icon, iconSize: iconSize, padding: iconPadding ?? EdgeInsets.all(8.0), tooltip: tooltip, onPressed: onTap)
+ );
+}
+
+Widget buildIconWebHasPosition(BuildContext context, {
+ required Widget icon,
+ String? tooltip,
+ IconWebHasPositionCallback? onTap
+}) {
+ return GestureDetector(
+ onTapDown: (detail) {
+ final screenSize = MediaQuery.of(context).size;
+ final offset = detail.globalPosition;
+ final position = RelativeRect.fromLTRB(
+ offset.dx,
+ offset.dy,
+ screenSize.width - offset.dx,
+ screenSize.height - offset.dy,
+ );
+ onTap?.call(position);
+ },
+ child: Tooltip(
+ message: tooltip,
+ child: icon,
+ )
+ );
+}
\ No newline at end of file
diff --git a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart
index 57705110d..ac19397f1 100644
--- a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart
+++ b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart
@@ -16,6 +16,13 @@ class ConfirmDialogBuilder {
String _confirmText = '';
String _cancelText = '';
Widget? _iconWidget;
+ Color? _colorCancelButton;
+ Color? _colorConfirmButton;
+ TextStyle? _styleTextCancelButton;
+ TextStyle? _styleTextConfirmButton;
+ TextStyle? _styleTitle;
+ TextStyle? _styleContent;
+ double? _radiusButton;
OnConfirmButtonAction? _onConfirmButtonAction;
OnCancelButtonAction? _onCancelButtonAction;
@@ -39,6 +46,34 @@ class ConfirmDialogBuilder {
_iconWidget = icon;
}
+ void colorCancelButton(Color? color) {
+ _colorCancelButton = color;
+ }
+
+ void colorConfirmButton(Color? color) {
+ _colorConfirmButton = color;
+ }
+
+ void styleTextCancelButton(TextStyle? style) {
+ _styleTextCancelButton = style;
+ }
+
+ void styleTextConfirmButton(TextStyle? style) {
+ _styleTextConfirmButton = style;
+ }
+
+ void styleTitle(TextStyle? style) {
+ _styleTitle = style;
+ }
+
+ void styleContent(TextStyle? style) {
+ _styleContent = style;
+ }
+
+ void radiusButton(double? radius) {
+ _radiusButton = radius;
+ }
+
void onConfirmButtonAction(String confirmText, OnConfirmButtonAction? onConfirmButtonAction) {
_confirmText = confirmText;
_onConfirmButtonAction = onConfirmButtonAction;
@@ -66,52 +101,57 @@ class ConfirmDialogBuilder {
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16))),
child: Wrap(children: [
- Align(
- alignment: Alignment.centerRight,
- child: IconButton(
- padding: EdgeInsets.only(top: 16, right: 16),
- onPressed: () => _onCloseButtonAction?.call(),
- icon: SvgPicture.asset(_imagePath.icCloseMailbox, width: 30, height: 30, fit: BoxFit.fill))),
+ if (_onCloseButtonAction != null)
+ Align(
+ alignment: Alignment.centerRight,
+ child: IconButton(
+ padding: EdgeInsets.only(top: 16, right: 16),
+ onPressed: () => _onCloseButtonAction?.call(),
+ icon: SvgPicture.asset(_imagePath.icCloseMailbox, width: 30, height: 30, fit: BoxFit.fill))),
if (_iconWidget != null)
Container(
margin: EdgeInsets.only(top: 24),
alignment: Alignment.center,
child: _iconWidget,
),
- Padding(
- padding: EdgeInsets.only(top: 12),
- child: Center(
- child: Text(
- _title,
- textAlign: TextAlign.center,
- style: TextStyle(fontSize: 20.0, color: AppColor.colorActionDeleteConfirmDialog, fontWeight: FontWeight.w500)
+ if (_title.isNotEmpty)
+ Padding(
+ padding: EdgeInsets.only(top: 12),
+ child: Center(
+ child: Text(
+ _title,
+ textAlign: TextAlign.center,
+ style: _styleTitle ?? TextStyle(fontSize: 20.0, color: AppColor.colorActionDeleteConfirmDialog, fontWeight: FontWeight.w500)
+ )
)
- )
- ),
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24),
- child: Center(
- child: Text(_content,
- textAlign: TextAlign.center,
- style: TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog)
+ ),
+ if (_content.isNotEmpty)
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24),
+ child: Center(
+ child: Text(_content,
+ textAlign: TextAlign.center,
+ style: _styleContent ?? TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog)
+ ),
),
),
- ),
Padding(
padding: EdgeInsets.only(bottom: 16, left: 16, right: 16),
child: Row(
children: [
- Expanded(
- child: _buildButton(name: _cancelText, action: _onCancelButtonAction)
- ),
+ Expanded(child: _buildButton(
+ name: _cancelText,
+ bgColor: _colorCancelButton,
+ radius: _radiusButton,
+ textStyle: _styleTextCancelButton,
+ action: _onCancelButtonAction)),
SizedBox(width: 16),
- Expanded(
- child: _buildButton(
- name: _confirmText,
- bgColor: AppColor.colorConfirmActionDialog,
- nameColor: AppColor.colorActionDeleteConfirmDialog,
- action: _onConfirmButtonAction)
- )
+ Expanded(child: _buildButton(
+ name: _confirmText,
+ bgColor: _colorConfirmButton,
+ radius: _radiusButton,
+ textStyle: _styleTextConfirmButton,
+ action: _onConfirmButtonAction))
]
))
])
@@ -120,7 +160,7 @@ class ConfirmDialogBuilder {
}
Widget _buildButton({
- String? name, Color? nameColor, Color? bgColor, Function? action
+ String? name, TextStyle? textStyle, Color? bgColor, double? radius, Function? action
}) {
return SizedBox(
width: double.infinity,
@@ -133,7 +173,7 @@ class ConfirmDialogBuilder {
backgroundColor: MaterialStateProperty.resolveWith(
(Set states) => bgColor ?? AppColor.colorTextButton),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8),
+ borderRadius: BorderRadius.circular(radius ?? 8),
side: BorderSide(width: 0, color: bgColor ?? AppColor.colorTextButton),
)),
padding: MaterialStateProperty.resolveWith(
@@ -141,7 +181,7 @@ class ConfirmDialogBuilder {
elevation: MaterialStateProperty.resolveWith((Set states) => 0)),
child: Text(name ?? '',
textAlign: TextAlign.center,
- style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: nameColor ?? Colors.white)),
+ style: textStyle ?? TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Colors.white)),
)
);
}
diff --git a/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart b/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart
index 893eb1568..22dd8a3d2 100644
--- a/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart
+++ b/core/lib/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart
@@ -5,6 +5,7 @@ import 'dart:math';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/html_viewer/html_viewer_controller_for_web.dart';
import 'package:flutter/material.dart';
+import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'dart:developer' as developer;
import 'package:universal_html/html.dart' as html;
import 'package:core/presentation/utils/shims/dart_ui.dart' as ui;
@@ -221,10 +222,10 @@ class _HtmlContentViewerOnWebState extends State {
future: webInit,
builder: (context, snapshot) {
if (snapshot.hasData) {
- return HtmlElementView(
+ return PointerInterceptor(child: HtmlElementView(
key: ValueKey(htmlData),
viewType: createdViewId,
- );
+ ));
} else {
return Container();
}
diff --git a/core/pubspec.yaml b/core/pubspec.yaml
index 4d1cceeff..59d8e669b 100644
--- a/core/pubspec.yaml
+++ b/core/pubspec.yaml
@@ -74,6 +74,8 @@ dependencies:
http: 0.13.4
+ pointer_interceptor: 0.9.1
+
dev_dependencies:
flutter_test:
sdk: flutter
diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart
index ef8d7b908..0ddc7dbce 100644
--- a/lib/features/composer/presentation/composer_bindings.dart
+++ b/lib/features/composer/presentation/composer_bindings.dart
@@ -127,6 +127,7 @@ class ComposerBindings extends BaseBindings {
Get.find(),
Get.find(),
Get.find(),
+ Get.find(),
Get.find(),
Get.find(),
Get.find(),
diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart
index 9827841e5..82f179931 100644
--- a/lib/features/composer/presentation/composer_controller.dart
+++ b/lib/features/composer/presentation/composer_controller.dart
@@ -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();
final expandMode = ExpandMode.COLLAPSE.obs;
+ final expandModeAttachments = ExpandMode.EXPAND.obs;
final composerArguments = Rxn();
final isEnableEmailSendButton = false.obs;
final isInitialRecipient = false.obs;
final attachments = [].obs;
final emailContents = [].obs;
+ final subjectEmail = Rxn();
+ 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 listToEmailAddress = [];
List listCcEmailAddress = [];
List listBccEmailAddress = [];
- 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 initialAttachments = [];
+ 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 _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? _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 _generateEmail(ComposerArguments arguments, {bool asDrafts = false}) async {
+ Future _generateEmail(Map 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();
listEmailAddressCanSave.addAll(listToEmailAddress + listCcEmailAddress + listBccEmailAddress);
@@ -460,10 +495,10 @@ class ComposerController extends BaseController {
}
void _uploadAttachmentsAction(List 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()));
}
}
diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart
new file mode 100644
index 000000000..6638d92ad
--- /dev/null
+++ b/lib/features/composer/presentation/composer_view_web.dart
@@ -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 {
+
+ final responsiveUtils = Get.find();
+ final imagePaths = Get.find();
+ final appToast = Get.find();
+ final keyboardUtils = Get.find();
+
+ @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 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 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);
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/model/screen_display_mode.dart b/lib/features/composer/presentation/model/screen_display_mode.dart
new file mode 100644
index 000000000..074587b32
--- /dev/null
+++ b/lib/features/composer/presentation/model/screen_display_mode.dart
@@ -0,0 +1,6 @@
+
+enum ScreenDisplayMode {
+ fullScreen,
+ minimize,
+ normal
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/widgets/attachment_file_composer_builder.dart b/lib/features/composer/presentation/widgets/attachment_file_composer_builder.dart
new file mode 100644
index 000000000..671cacc2f
--- /dev/null
+++ b/lib/features/composer/presentation/widgets/attachment_file_composer_builder.dart
@@ -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)))
+ ]),
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/widgets/email_address_input_builder.dart b/lib/features/composer/presentation/widgets/email_address_input_builder.dart
index b05fb4dba..20df7b44f 100644
--- a/lib/features/composer/presentation/widgets/email_address_input_builder.dart
+++ b/lib/features/composer/presentation/widgets/email_address_input_builder.dart
@@ -26,6 +26,7 @@ class EmailAddressInputBuilder {
final List _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);
diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart
index 34a4462bc..8fbeaf19e 100644
--- a/lib/features/email/presentation/email_controller.dart
+++ b/lib/features/email/presentation/email_controller.dart
@@ -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());
}
}
}
\ No newline at end of file
diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart
index 0338c471a..91931833d 100644
--- a/lib/features/email/presentation/email_view.dart
+++ b/lib/features/email/presentation/email_view.dart
@@ -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))),
diff --git a/lib/features/email/presentation/model/composer_arguments.dart b/lib/features/email/presentation/model/composer_arguments.dart
index 8f323f726..0335abb80 100644
--- a/lib/features/email/presentation/model/composer_arguments.dart
+++ b/lib/features/email/presentation/model/composer_arguments.dart
@@ -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? emailContents;
final EmailAddress? emailAddress;
final List? attachments;
- final Session session;
- final UserProfile userProfile;
- final Map 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,
];
}
\ No newline at end of file
diff --git a/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart b/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart
index 5675ab258..3177bb14e 100644
--- a/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart
+++ b/lib/features/email/presentation/widgets/app_bar_mail_widget_builder.dart
@@ -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),
diff --git a/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart b/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart
index d4a1b2a2d..3500b8027 100644
--- a/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart
+++ b/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart
@@ -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(
diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart
index 19b9a4853..46eeb415d 100644
--- a/lib/features/mailbox/presentation/mailbox_controller.dart
+++ b/lib/features/mailbox/presentation/mailbox_controller.dart
@@ -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()))
diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart
index b67befe67..9d539c209 100644
--- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart
+++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart
@@ -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();
final searchState = SearchState.initial().obs;
final suggestionSearch = [].obs;
+ final dashBoardAction = DashBoardAction.none.obs;
SearchQuery? searchQuery;
Session? sessionCurrent;
@@ -43,6 +48,7 @@ class MailboxDashBoardController extends BaseController {
Map 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();
+ break;
+ case DashBoardAction.compose:
+ routerArguments = arguments;
+ ComposerBindings().dependencies();
+ break;
+ }
+ dashBoardAction.value = action;
+ }
+
@override
void onClose() {
searchInputController.dispose();
diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view.dart
index 4721f6a65..9e83e3c28 100644
--- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view.dart
+++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view.dart
@@ -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 {
@@ -30,35 +33,40 @@ class MailboxDashBoardView extends GetWidget {
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()),
+ ]),
);
}
}
\ No newline at end of file
diff --git a/lib/features/mailbox_dashboard/presentation/model/dashboard_action.dart b/lib/features/mailbox_dashboard/presentation/model/dashboard_action.dart
new file mode 100644
index 000000000..7af9da24b
--- /dev/null
+++ b/lib/features/mailbox_dashboard/presentation/model/dashboard_action.dart
@@ -0,0 +1,5 @@
+
+enum DashBoardAction {
+ none,
+ compose
+}
\ No newline at end of file
diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart
index 4fd9ad5c3..e6b6846b8 100644
--- a/lib/features/thread/presentation/thread_controller.dart
+++ b/lib/features/thread/presentation/thread_controller.dart
@@ -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 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());
}
}
}
\ No newline at end of file
diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart
index c256b2937..acade40bd 100644
--- a/lib/main/localizations/app_localizations.dart
+++ b/lib/main/localizations/app_localizations.dart
@@ -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');
+ }
+
}
\ No newline at end of file
diff --git a/lib/main/pages/app_pages.dart b/lib/main/pages/app_pages.dart
index 07e837d25..3704ea6e7 100644
--- a/lib/main/pages/app_pages.dart
+++ b/lib/main/pages/app_pages.dart
@@ -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';
diff --git a/lib/main/routes/router_arguments.dart b/lib/main/routes/router_arguments.dart
new file mode 100644
index 000000000..03e7ef5d9
--- /dev/null
+++ b/lib/main/routes/router_arguments.dart
@@ -0,0 +1,3 @@
+import 'package:equatable/equatable.dart';
+
+abstract class RouterArguments with EquatableMixin {}
\ No newline at end of file
diff --git a/pubspec.yaml b/pubspec.yaml
index 0eb28f40a..c2340e9fb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -125,11 +125,16 @@ dependencies:
url: https://github.com/Enough-Software/enough_html_editor.git
# html_editor_enhanced: Compose email for Web Browser
- html_editor_enhanced: 2.4.0+1
+ html_editor_enhanced:
+ git:
+ url: https://github.com/dab246/html-editor-enhanced.git
+ ref: html_editor_for_web
# fk_user_agent
fk_user_agent: 2.1.0
+ pointer_interceptor: 0.9.1
+
dev_dependencies:
flutter_test:
sdk: flutter