TF-83 Fix show message constraints when sending a message

This commit is contained in:
dab246
2022-04-07 14:45:01 +07:00
committed by Dat H. Pham
parent 63d1b6584f
commit a7d013cfc7
8 changed files with 162 additions and 96 deletions
+7 -7
View File
@@ -20,23 +20,23 @@ class AppToast {
gravity: ToastGravity.BOTTOM); gravity: ToastGravity.BOTTOM);
} }
void showSuccessToast(String message) { void showSuccessToast(String message, {bool isToastLengthLong = false}) {
Fluttertoast.showToast( Fluttertoast.showToast(
msg: message, msg: message,
fontSize: 16, fontSize: 16,
textColor: Colors.white, textColor: Colors.white,
backgroundColor: AppColor.toastSuccessBackgroundColor, backgroundColor: AppColor.toastSuccessBackgroundColor,
toastLength: Toast.LENGTH_SHORT, toastLength: isToastLengthLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM); gravity: ToastGravity.BOTTOM);
} }
void showErrorToast(String message) { void showErrorToast(String message, {bool isToastLengthLong = false}) {
Fluttertoast.showToast( Fluttertoast.showToast(
msg: message, msg: message,
fontSize: 16, fontSize: 16,
textColor: Colors.white, textColor: Colors.white,
backgroundColor: AppColor.toastErrorBackgroundColor, backgroundColor: AppColor.toastErrorBackgroundColor,
toastLength: Toast.LENGTH_SHORT, toastLength: isToastLengthLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM); gravity: ToastGravity.BOTTOM);
} }
@@ -64,7 +64,7 @@ class AppToast {
void showToastWithIcon(BuildContext context, { void showToastWithIcon(BuildContext context, {
String? message, String? icon, Color? bgColor, String? message, String? icon, Color? bgColor,
Color? textColor, double? radius, EdgeInsets? padding, Color? textColor, double? radius, EdgeInsets? padding,
TextStyle? textStyle, double? widthToast}) { TextStyle? textStyle, double? widthToast, Duration? toastLength}) {
double sizeWidth = context.width > 360 ? 360 : context.width - 40; double sizeWidth = context.width > 360 ? 360 : context.width - 40;
final toast = Material( final toast = Material(
color: bgColor ?? Colors.white, color: bgColor ?? Colors.white,
@@ -82,7 +82,7 @@ class AppToast {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (icon != null) SvgPicture.asset(icon, width: 24, height: 24, fit: BoxFit.fill), if (icon != null) SvgPicture.asset(icon, width: 24, height: 24, fit: BoxFit.fill),
SizedBox(width: 10.0), if (icon != null) SizedBox(width: 10.0),
Expanded(child: Text( Expanded(child: Text(
message ?? '', message ?? '',
style: textStyle ?? TextStyle(fontSize: 15, color: textColor ?? Colors.black))), style: textStyle ?? TextStyle(fontSize: 15, color: textColor ?? Colors.black))),
@@ -94,7 +94,7 @@ class AppToast {
fToast.showToast( fToast.showToast(
child: toast, child: toast,
gravity: ToastGravity.BOTTOM, gravity: ToastGravity.BOTTOM,
toastDuration: Duration(seconds: 2), toastDuration: toastLength ?? Duration(seconds: 2),
); );
} }
} }
+2 -2
View File
@@ -2,13 +2,13 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:core/core.dart'; import 'package:core/core.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart';
abstract class BaseController extends GetxController { abstract class BaseController extends GetxController with MessageDialogActionMixin {
final viewState = Rx<Either<Failure, Success>>(Right(UIState.idle)); final viewState = Rx<Either<Failure, Success>>(Right(UIState.idle));
final connectivityResult = Rxn<ConnectivityResult>(); final connectivityResult = Rxn<ConnectivityResult>();
void consumeState(Stream<Either<Failure, Success>> newStateStream) async { void consumeState(Stream<Either<Failure, Success>> newStateStream) async {
log('BaseController::consumeState():');
newStateStream.listen( newStateStream.listen(
(state) => onData(state), (state) => onData(state),
onError: (error) => onError(error), onError: (error) => onError(error),
@@ -0,0 +1,58 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
mixin MessageDialogActionMixin {
final _responsiveUtils = Get.find<ResponsiveUtils>();
final _imagePaths = Get.find<ImagePaths>();
void showConfirmDialogAction(
BuildContext context,
String message,
String actionName,
Function onConfirmAction,
{bool hasCancelButton = true, Widget? icon}
) {
if (_responsiveUtils.isMobile(context)) {
(ConfirmationDialogActionSheetBuilder(context)
..messageText(message)
..styleConfirmButton(TextStyle(fontSize: 20, fontWeight: FontWeight.normal, color: Colors.black))
..onCancelAction(AppLocalizations.of(context).cancel, () => popBack())
..onConfirmAction(actionName, () {
popBack();
onConfirmAction.call();
})).show();
} else {
showDialog(
context: context,
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
builder: (BuildContext context) => PointerInterceptor(child: (ConfirmDialogBuilder(_imagePaths)
..key(Key('confirm_dialog_action'))
..title(AppLocalizations.of(context).sending_failed)
..content(message)
..addIcon(icon)
..colorConfirmButton(AppColor.colorTextButton)
..colorCancelButton(AppColor.colorCancelButton)
..paddingTitle(icon != null ? EdgeInsets.only(top: 34) : EdgeInsets.only(top: 10))
..marginIcon(EdgeInsets.zero)
..paddingContent(EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 8))
..paddingButton(hasCancelButton ? null : EdgeInsets.only(bottom: 16, left: 44, right: 44))
..styleTitle(TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black))
..styleContent(TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail))
..styleTextCancelButton(TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton))
..styleTextConfirmButton(TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Colors.white))
..onConfirmButtonAction(actionName, () {
popBack();
onConfirmAction.call();
})
..onCancelButtonAction(hasCancelButton ? AppLocalizations.of(context).cancel : '', () => popBack())
..onCloseButtonAction(() => popBack()))
.build()));
}
}
}
@@ -6,6 +6,7 @@ import 'package:dartz/dartz.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:enough_html_editor/enough_html_editor.dart'; import 'package:enough_html_editor/enough_html_editor.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart';
import 'package:fk_user_agent/fk_user_agent.dart'; import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -23,7 +24,6 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart'; import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.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/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/auto_complete_pattern.dart';
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart'; import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
@@ -55,7 +55,6 @@ class ComposerController extends BaseController {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>(); final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final _appToast = Get.find<AppToast>(); final _appToast = Get.find<AppToast>();
final _imagePaths = Get.find<ImagePaths>(); final _imagePaths = Get.find<ImagePaths>();
final _responsiveUtils = Get.find<ResponsiveUtils>();
final expandModeAttachments = ExpandMode.EXPAND.obs; final expandModeAttachments = ExpandMode.EXPAND.obs;
final composerArguments = Rxn<ComposerArguments>(); final composerArguments = Rxn<ComposerArguments>();
@@ -386,11 +385,11 @@ class ComposerController extends BaseController {
clearFocusEditor(context); clearFocusEditor(context);
if (!isEnableEmailSendButton.value) { if (!isEnableEmailSendButton.value) {
_showConfirmDialogSendEmailFailed( showConfirmDialogAction(context,
context,
AppLocalizations.of(context).message_dialog_send_email_without_recipient, AppLocalizations.of(context).message_dialog_send_email_without_recipient,
AppLocalizations.of(context).add_recipients, AppLocalizations.of(context).add_recipients,
() => {}, () => {},
icon: SvgPicture.asset(_imagePaths.icSendToastError, fit: BoxFit.fill),
hasCancelButton: false); hasCancelButton: false);
return; return;
} }
@@ -399,10 +398,8 @@ class ComposerController extends BaseController {
final listEmailAddressInvalid = allListEmailAddress final listEmailAddressInvalid = allListEmailAddress
.where((emailAddress) => !GetUtils.isEmail(emailAddress.emailAddress)) .where((emailAddress) => !GetUtils.isEmail(emailAddress.emailAddress))
.toList(); .toList();
if (listEmailAddressInvalid.isNotEmpty) { if (listEmailAddressInvalid.isNotEmpty) {
_showConfirmDialogSendEmailFailed( showConfirmDialogAction(context,
context,
AppLocalizations.of(context).message_dialog_send_email_with_email_address_invalid, AppLocalizations.of(context).message_dialog_send_email_with_email_address_invalid,
AppLocalizations.of(context).fix_email_addresses, AppLocalizations.of(context).fix_email_addresses,
() { () {
@@ -410,16 +407,30 @@ class ComposerController extends BaseController {
ccAddressExpandMode.value = ExpandMode.EXPAND; ccAddressExpandMode.value = ExpandMode.EXPAND;
bccAddressExpandMode.value = ExpandMode.EXPAND; bccAddressExpandMode.value = ExpandMode.EXPAND;
}, },
icon: SvgPicture.asset(_imagePaths.icSendToastError, fit: BoxFit.fill),
hasCancelButton: false); hasCancelButton: false);
return; return;
} }
if (subjectEmail.value == null || subjectEmail.isEmpty == true) { if (subjectEmail.value == null || subjectEmail.isEmpty == true) {
_showConfirmDialogSendEmailFailed( showConfirmDialogAction(context,
context,
AppLocalizations.of(context).message_dialog_send_email_without_a_subject, AppLocalizations.of(context).message_dialog_send_email_without_a_subject,
AppLocalizations.of(context).send_anyway, AppLocalizations.of(context).send_anyway,
() => _handleSendMessages(context)); () => _handleSendMessages(context),
icon: SvgPicture.asset(_imagePaths.icEmpty, fit: BoxFit.fill),
);
return;
}
if (!_validateAttachmentsSize()) {
showConfirmDialogAction(
context,
AppLocalizations.of(context).message_dialog_send_email_exceeds_maximum_size('${
filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)}'),
AppLocalizations.of(context).got_it,
() => {},
icon: SvgPicture.asset(_imagePaths.icSendToastError, fit: BoxFit.fill),
hasCancelButton: false);
return; return;
} }
@@ -512,6 +523,10 @@ class ComposerController extends BaseController {
consumeState(_localFilePickerInteractor.execute(fileType: fileType)); consumeState(_localFilePickerInteractor.execute(fileType: fileType));
} }
void openFilePickerByTypeOnWeb(BuildContext context, FileType fileType) async {
consumeState(_localFilePickerInteractor.execute(fileType: fileType));
}
void _pickFileFailure(Failure failure) { void _pickFileFailure(Failure failure) {
if (failure is LocalFilePickerFailure) { if (failure is LocalFilePickerFailure) {
if (currentContext != null) { if (currentContext != null) {
@@ -522,32 +537,34 @@ class ComposerController extends BaseController {
void _pickFileSuccess(Success success) { void _pickFileSuccess(Success success) {
if (success is LocalFilePickerSuccess) { if (success is LocalFilePickerSuccess) {
if (_validateAttachmentsSize(success.pickedFiles)) { if (_validateAttachmentsSize(listFiles: success.pickedFiles)) {
_uploadAttachmentsAction(success.pickedFiles); _uploadAttachmentsAction(success.pickedFiles);
} else { } else {
if (currentContext != null) { if (currentContext != null) {
_appToast.showErrorToast(AppLocalizations.of(currentContext!).the_total_size_of_attachments_in_an_email_exceeds_the_limit); showConfirmDialogAction(
currentContext!,
AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size('${
filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)}'),
AppLocalizations.of(currentContext!).got_it,
() => {},
hasCancelButton: false);
} }
} }
} }
} }
bool _validateAttachmentsSize(List<FileInfo> listFiles) { bool _validateAttachmentsSize({List<FileInfo>? listFiles}) {
num currentTotalSize = 0; final currentTotalAttachmentsSize = attachments.totalSize();
if (attachments.isNotEmpty) { log('ComposerController::_validateAttachmentsSize(): $currentTotalAttachmentsSize');
final currentListSize = attachments num uploadedTotalSize = 0;
.map((attachment) => attachment.size?.value ?? 0) if (listFiles != null && listFiles.isNotEmpty) {
.toList(); final uploadedListSize = listFiles.map((file) => file.fileSize).toList();
currentTotalSize = currentListSize.reduce((sum, size) => sum + size); uploadedTotalSize = uploadedListSize.reduce((sum, size) => sum + size);
log('ComposerController::checkingAttachmentsLimit(): currentTotalSize: $currentTotalSize'); log('ComposerController::_validateAttachmentsSize(): uploadedTotalSize: $uploadedTotalSize');
} }
final uploadedListSize = listFiles.map((file) => file.fileSize).toList(); final totalSizeReadyToUpload = currentTotalAttachmentsSize + uploadedTotalSize;
final uploadedTotalSize = uploadedListSize.reduce((sum, size) => sum + size); log('ComposerController::_validateAttachmentsSize(): totalSizeReadyToUpload: $totalSizeReadyToUpload');
log('ComposerController::checkingAttachmentsLimit(): uploadedTotalSize: $uploadedTotalSize');
final totalSizeReadyToUpload = currentTotalSize + uploadedTotalSize;
log('ComposerController::checkingAttachmentsLimit(): totalSizeReadyToUpload: $totalSizeReadyToUpload');
final maxSizeAttachmentsPerEmail = mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value; final maxSizeAttachmentsPerEmail = mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value;
if (maxSizeAttachmentsPerEmail != null) { if (maxSizeAttachmentsPerEmail != null) {
@@ -743,46 +760,6 @@ class ComposerController extends BaseController {
expandModeAttachments.value = newExpandMode; expandModeAttachments.value = newExpandMode;
} }
void _showConfirmDialogSendEmailFailed(BuildContext context, String message,
String actionName, Function? onConfirmAction, {bool hasCancelButton = true}) {
if (_responsiveUtils.isMobile(context)) {
(ConfirmationDialogActionSheetBuilder(context)
..messageText(message)
..styleConfirmButton(TextStyle(fontSize: 20, fontWeight: FontWeight.normal, color: Colors.black))
..onCancelAction(AppLocalizations.of(context).cancel, () => popBack())
..onConfirmAction(actionName, () {
popBack();
onConfirmAction?.call();
})).show();
} else {
showDialog(
context: context,
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
builder: (BuildContext context) => PointerInterceptor(child: (ConfirmDialogBuilder(_imagePaths)
..key(Key('confirm_dialog_send_message'))
..title(AppLocalizations.of(context).sending_failed)
..content(message)
..addIcon(SvgPicture.asset(_imagePaths.icSendToastError, fit: BoxFit.fill))
..colorConfirmButton(AppColor.colorTextButton)
..colorCancelButton(AppColor.colorCancelButton)
..paddingTitle(EdgeInsets.only(top: 34))
..marginIcon(EdgeInsets.zero)
..paddingContent(EdgeInsets.only(left: 44, right: 44, bottom: 24, top: 8))
..paddingButton(hasCancelButton ? null : EdgeInsets.only(bottom: 16, left: 44, right: 44))
..styleTitle(TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black))
..styleContent(TextStyle(fontSize: 14, fontWeight: FontWeight.normal, color: AppColor.colorContentEmail))
..styleTextCancelButton(TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorTextButton))
..styleTextConfirmButton(TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: Colors.white))
..onConfirmButtonAction(actionName, () {
popBack();
onConfirmAction?.call();
})
..onCancelButtonAction(hasCancelButton ? AppLocalizations.of(context).cancel : '', () => popBack())
..onCloseButtonAction(() => popBack()))
.build()));
}
}
void addEmailAddressType(PrefixEmailAddress prefixEmailAddress) { void addEmailAddressType(PrefixEmailAddress prefixEmailAddress) {
listEmailAddressType.add(prefixEmailAddress); listEmailAddressType.add(prefixEmailAddress);
} }
@@ -410,7 +410,7 @@ class ComposerView extends GetWidget<ComposerController> {
buildIconWeb( buildIconWeb(
icon: SvgPicture.asset(imagePaths.icAttachmentsComposer, color: AppColor.colorTextButton, fit: BoxFit.fill), icon: SvgPicture.asset(imagePaths.icAttachmentsComposer, color: AppColor.colorTextButton, fit: BoxFit.fill),
tooltip: AppLocalizations.of(context).attach_file, tooltip: AppLocalizations.of(context).attach_file,
onTap: () => controller.openFilePickerByType(context, FileType.any)), onTap: () => controller.openFilePickerByTypeOnWeb(context, FileType.any)),
]) ])
) )
); );
+27 -7
View File
@@ -1,5 +1,5 @@
{ {
"@@last_modified": "2022-04-05T16:47:07.723009", "@@last_modified": "2022-04-07T14:40:38.896425",
"initializing_data": "Initializing data...", "initializing_data": "Initializing data...",
"@initializing_data": { "@initializing_data": {
"type": "text", "type": "text",
@@ -894,12 +894,6 @@
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "placeholders": {}
}, },
"the_total_size_of_attachments_in_an_email_exceeds_the_limit": "The total size of attachments in an email exceeds the limit.",
"@the_total_size_of_attachments_in_an_email_exceeds_the_limit": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"your_download_has_started": "Your download has started", "your_download_has_started": "Your download has started",
"@your_download_has_started": { "@your_download_has_started": {
"type": "text", "type": "text",
@@ -1015,5 +1009,31 @@
"type": "text", "type": "text",
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "placeholders": {}
},
"message_dialog_send_email_exceeds_maximum_size": "Your message could not be sent because it exceeds the maximum size of {maxSize}",
"@message_dialog_send_email_exceeds_maximum_size": {
"type": "text",
"placeholders_order": [
"maxSize"
],
"placeholders": {
"maxSize": {}
}
},
"message_dialog_upload_attachments_exceeds_maximum_size": "You have reached the maximum file size. Please upload files that total size is less than {maxSize}",
"@message_dialog_upload_attachments_exceeds_maximum_size": {
"type": "text",
"placeholders_order": [
"maxSize"
],
"placeholders": {
"maxSize": {}
}
},
"got_it": "Got it",
"@got_it": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
} }
} }
+22 -7
View File
@@ -950,13 +950,6 @@ class AppLocalizations {
name: 'fix_email_addresses'); name: 'fix_email_addresses');
} }
String get the_total_size_of_attachments_in_an_email_exceeds_the_limit {
return Intl.message(
'The total size of attachments in an email exceeds the limit.',
name: 'the_total_size_of_attachments_in_an_email_exceeds_the_limit'
);
}
String get your_download_has_started { String get your_download_has_started {
return Intl.message( return Intl.message(
'Your download has started', 'Your download has started',
@@ -1069,4 +1062,26 @@ class AppLocalizations {
'Version', 'Version',
name: 'version'); name: 'version');
} }
String message_dialog_send_email_exceeds_maximum_size(String maxSize) {
return Intl.message(
'Your message could not be sent because it exceeds the maximum size of $maxSize',
name: 'message_dialog_send_email_exceeds_maximum_size',
args: [maxSize]
);
}
String message_dialog_upload_attachments_exceeds_maximum_size(String maxSize) {
return Intl.message(
'You have reached the maximum file size. Please upload files that total size is less than $maxSize',
name: 'message_dialog_upload_attachments_exceeds_maximum_size',
args: [maxSize]
);
}
String get got_it {
return Intl.message(
'Got it',
name: 'got_it');
}
} }
@@ -3,12 +3,8 @@ import 'package:model/model.dart';
extension ListAttachmentExtension on List<Attachment> { extension ListAttachmentExtension on List<Attachment> {
num totalSize() { num totalSize() {
num totalSize = 0; final currentListSize = map((attachment) => attachment.size?.value ?? 0).toList();
forEach((attachment) { final totalSize = currentListSize.reduce((sum, size) => sum + size);
if (attachment.size != null) {
totalSize += attachment.size!.value;
}
});
return totalSize; return totalSize;
} }