diff --git a/lib/features/composer/data/datasource/composer_datasource.dart b/lib/features/composer/data/datasource/composer_datasource.dart index 5f47a46da..0c090c512 100644 --- a/lib/features/composer/data/datasource/composer_datasource.dart +++ b/lib/features/composer/data/datasource/composer_datasource.dart @@ -1,5 +1,8 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; abstract class ComposerDataSource { Future saveEmailAddresses(List listEmailAddress); + + Future uploadAttachment(UploadRequest uploadRequest); } \ No newline at end of file diff --git a/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart b/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart index a47de7517..a4b972a1e 100644 --- a/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart +++ b/lib/features/composer/data/datasource_impl/composer_datasource_impl.dart @@ -1,12 +1,26 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart'; +import 'package:tmail_ui_user/features/composer/data/network/composer_api.dart'; class ComposerDataSourceImpl extends ComposerDataSource { - ComposerDataSourceImpl(); + final ComposerAPI _composerAPI; + + ComposerDataSourceImpl(this._composerAPI); @override Future saveEmailAddresses(List listEmailAddress) { throw UnimplementedError(); } + + @override + Future uploadAttachment(UploadRequest uploadRequest) { + return Future.sync(() async { + final uploadResponse = await _composerAPI.uploadAttachment(uploadRequest); + return uploadResponse.toAttachmentFile(uploadRequest.fileInfo.fileName); + }).catchError((error) { + throw error; + }); + } } \ No newline at end of file diff --git a/lib/features/composer/data/datasource_impl/local_composer_datasource_impl.dart b/lib/features/composer/data/datasource_impl/local_composer_datasource_impl.dart index ac5ca8330..6e3dd6633 100644 --- a/lib/features/composer/data/datasource_impl/local_composer_datasource_impl.dart +++ b/lib/features/composer/data/datasource_impl/local_composer_datasource_impl.dart @@ -1,4 +1,6 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; +import 'package:model/upload/upload_request.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart'; import 'package:tmail_ui_user/features/composer/data/local/email_address_database_manager.dart'; @@ -16,4 +18,9 @@ class LocalComposerDataSourceImpl extends ComposerDataSource { throw error; }); } + + @override + Future uploadAttachment(UploadRequest uploadRequest) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/composer/data/network/composer_api.dart b/lib/features/composer/data/network/composer_api.dart new file mode 100644 index 000000000..dc5fee9a8 --- /dev/null +++ b/lib/features/composer/data/network/composer_api.dart @@ -0,0 +1,28 @@ + +import 'dart:io'; + +import 'package:core/core.dart'; +import 'package:dio/dio.dart'; +import 'package:model/model.dart'; + +class ComposerAPI { + + final DioClient _dioClient; + + ComposerAPI(this._dioClient); + + Future uploadAttachment(UploadRequest uploadRequest) async { + final resultJson = await _dioClient.post( + Uri.decodeFull(uploadRequest.uploadUrl.toString()), + options: Options(headers: _buildHeaderRequestParam(uploadRequest.accountRequest.basicAuth())), + data: File(uploadRequest.fileInfo.filePath).readAsBytesSync()); + return UploadResponse.fromJson(resultJson); + } + + Map _buildHeaderRequestParam(String basicAuth) { + final headerParam = _dioClient.getHeaders(); + headerParam[HttpHeaders.authorizationHeader] = basicAuth; + headerParam[HttpHeaders.acceptHeader] = DioClient.jmapHeader; + return headerParam; + } +} \ No newline at end of file diff --git a/lib/features/composer/data/repository/composer_repository_impl.dart b/lib/features/composer/data/repository/composer_repository_impl.dart index 963a8c011..9399518e9 100644 --- a/lib/features/composer/data/repository/composer_repository_impl.dart +++ b/lib/features/composer/data/repository/composer_repository_impl.dart @@ -1,6 +1,8 @@ import 'package:core/core.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; +import 'package:model/upload/upload_request.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart'; import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart'; @@ -14,4 +16,9 @@ class ComposerRepositoryImpl extends ComposerRepository { Future saveEmailAddresses(List listEmailAddress) { return composerDataSources[DataSourceType.local]!.saveEmailAddresses(listEmailAddress); } + + @override + Future uploadAttachment(UploadRequest uploadRequest) { + return composerDataSources[DataSourceType.network]!.uploadAttachment(uploadRequest); + } } \ No newline at end of file diff --git a/lib/features/composer/domain/repository/composer_repository.dart b/lib/features/composer/domain/repository/composer_repository.dart index 8313a1334..28a149983 100644 --- a/lib/features/composer/domain/repository/composer_repository.dart +++ b/lib/features/composer/domain/repository/composer_repository.dart @@ -1,5 +1,8 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; +import 'package:model/model.dart'; abstract class ComposerRepository { Future saveEmailAddresses(List listEmailAddress); + + Future uploadAttachment(UploadRequest uploadRequest); } \ No newline at end of file diff --git a/lib/features/composer/domain/state/upload_attachment_state.dart b/lib/features/composer/domain/state/upload_attachment_state.dart new file mode 100644 index 000000000..27f32c6df --- /dev/null +++ b/lib/features/composer/domain/state/upload_attachment_state.dart @@ -0,0 +1,27 @@ +import 'package:core/core.dart'; +import 'package:model/model.dart'; + +class UploadAttachmentLoadingState extends UIState { + UploadAttachmentLoadingState(); + + @override + List get props => []; +} + +class UploadAttachmentSuccess extends UIState { + final Attachment attachment; + + UploadAttachmentSuccess(this.attachment); + + @override + List get props => [attachment]; +} + +class UploadAttachmentFailure extends FeatureFailure { + final exception; + + UploadAttachmentFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/composer/domain/usecases/upload_attachment_interactor.dart b/lib/features/composer/domain/usecases/upload_attachment_interactor.dart new file mode 100644 index 000000000..4a2a5341a --- /dev/null +++ b/lib/features/composer/domain/usecases/upload_attachment_interactor.dart @@ -0,0 +1,30 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart'; +import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; + +class UploadAttachmentInteractor { + final ComposerRepository _composerRepository; + final CredentialRepository _credentialRepository; + + UploadAttachmentInteractor(this._composerRepository, this._credentialRepository); + + Stream> execute(FileInfo fileInfo, AccountId accountId, Uri uploadUrl) async* { + try { + yield Right(UploadAttachmentLoadingState()); + final result = await Future.wait( + [_credentialRepository.getUserName(), _credentialRepository.getPassword()], + eagerError: true + ).then((List responses) async { + final accountRequest = AccountRequest(responses.first, responses.last); + return await _composerRepository.uploadAttachment(UploadRequest(uploadUrl, accountId, fileInfo, accountRequest)); + }); + yield Right(UploadAttachmentSuccess(result)); + } catch (e) { + yield Left(UploadAttachmentFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index bca0d1dba..815795509 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -3,6 +3,7 @@ import 'package:core/presentation/utils/app_toast.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:html_editor_enhanced/html_editor.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/autocomplete_datasource.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/contact_datasource.dart'; @@ -12,6 +13,7 @@ import 'package:tmail_ui_user/features/composer/data/datasource_impl/contact_dat import 'package:tmail_ui_user/features/composer/data/datasource_impl/local_autocomplete_datasource_impl.dart'; import 'package:tmail_ui_user/features/composer/data/datasource_impl/local_composer_datasource_impl.dart'; import 'package:tmail_ui_user/features/composer/data/local/email_address_database_manager.dart'; +import 'package:tmail_ui_user/features/composer/data/network/composer_api.dart'; import 'package:tmail_ui_user/features/composer/data/repository/auto_complete_repository_impl.dart'; import 'package:tmail_ui_user/features/composer/data/repository/composer_repository_impl.dart'; import 'package:tmail_ui_user/features/composer/data/repository/contact_repository_impl.dart'; @@ -20,18 +22,22 @@ import 'package:tmail_ui_user/features/composer/domain/repository/composer_repos import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart'; +import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_addresses_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/login/data/repository/credential_repository_impl.dart'; +import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; +import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart'; import 'package:uuid/uuid.dart'; class ComposerBindings extends Bindings { @override void dependencies() { Get.lazyPut(() => EmailAddressDatabaseManager(Get.find())); - Get.lazyPut(() => ComposerDataSourceImpl()); + Get.lazyPut(() => ComposerDataSourceImpl(Get.find())); Get.lazyPut(() => LocalComposerDataSourceImpl(Get.find())); Get.lazyPut(() => Get.find()); Get.lazyPut(() => ComposerRepositoryImpl({ @@ -61,6 +67,12 @@ class ComposerBindings extends Bindings { Get.lazyPut(() => GetAutoCompleteWithDeviceContactInteractor( Get.find(), Get.find())); + Get.lazyPut(() => LocalFilePickerInteractor()); + Get.lazyPut(() => CredentialRepositoryImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => UploadAttachmentInteractor( + Get.find(), + Get.find())); Get.lazyPut(() => ComposerController( Get.find(), Get.find(), @@ -71,5 +83,8 @@ class ComposerBindings extends Bindings { Get.find(), Get.find(), Get.find())); + Get.find(), + Get.find(), + Get.find())); } } \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 93fa86ca4..d9b848eb7 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'package:core/core.dart'; import 'package:core/presentation/utils/app_toast.dart'; import 'package:dartz/dartz.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:html_editor_enhanced/html_editor.dart'; @@ -23,6 +24,10 @@ import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart' import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/search_email_address_state.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; +import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_addresses_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; @@ -30,7 +35,10 @@ import 'package:tmail_ui_user/features/email/presentation/constants/email_consta import 'package:tmail_ui_user/features/email/presentation/extensions/email_content_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/model/message_content.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:uuid/uuid.dart'; class ComposerController extends BaseController { @@ -39,6 +47,7 @@ class ComposerController extends BaseController { final composerArguments = Rxn(); final isEnableEmailSendButton = false.obs; final listReplyToEmailAddress = [].obs; + final attachments = [].obs; final SendEmailInteractor _sendEmailInteractor; final SaveEmailAddressesInteractor _saveEmailAddressInteractor; @@ -49,6 +58,9 @@ class ComposerController extends BaseController { final HtmlEditorController composerEditorController; final TextEditingController subjectEmailInputController; final HtmlMessagePurifier _htmlMessagePurifier; + final HtmlEditorController htmlEditorController; + final LocalFilePickerInteractor _localFilePickerInteractor; + final UploadAttachmentInteractor _uploadAttachmentInteractor; List listToEmailAddress = []; List listCcEmailAddress = []; @@ -68,6 +80,9 @@ class ComposerController extends BaseController { this.composerEditorController, this.subjectEmailInputController, this._htmlMessagePurifier, + this.htmlEditorController, + this._localFilePickerInteractor, + this._uploadAttachmentInteractor, ); @override @@ -82,20 +97,29 @@ class ComposerController extends BaseController { void onDone() { viewState.value.fold( (failure) { - _appToast.showErrorToast(AppLocalizations.of(Get.context!).error_message_sent); - Get.back(); + if (failure is SendEmailFailure) { + _sendEmailFailure(failure); + } else if (failure is LocalFilePickerFailure || failure is LocalFilePickerCancel) { + _pickFileFailure(failure); + } else if (failure is UploadAttachmentFailure) { + _uploadAttachmentsFailure(failure); + } }, (success) { - _saveEmailAddress(); - _appToast.showSuccessToast(AppLocalizations.of(Get.context!).message_sent); - Get.back(); + if (success is SendEmailSuccess) { + _sendEmailSuccess(success); + } else if (success is LocalFilePickerSuccess) { + _pickFileSuccess(success); + } else if (success is UploadAttachmentSuccess) { + _uploadAttachmentsSuccess(success); + } }); } @override void onError(error) { _appToast.showErrorToast(AppLocalizations.of(Get.context!).error_message_sent); - Get.back(); + popBack(); } void _getSelectedEmail() { @@ -275,6 +299,17 @@ class ComposerController extends BaseController { } } + void _sendEmailSuccess(Success success) { + _saveEmailAddress(); + _appToast.showSuccessToast(AppLocalizations.of(Get.context!).message_sent); + popBack(); + } + + void _sendEmailFailure(Failure failure) { + _appToast.showErrorToast(AppLocalizations.of(Get.context!).error_message_sent); + popBack(); + } + void _saveEmailAddress() { final listEmailAddressCanSave = Set(); listEmailAddressCanSave.addAll(listToEmailAddress + listCcEmailAddress + listBccEmailAddress); @@ -306,6 +341,63 @@ class ComposerController extends BaseController { (success) => success is GetAutoCompleteSuccess ? success.listEmailAddress : [])); } + void openPickAttachmentMenu(BuildContext context, List actionTiles) { + (ContextMenuBuilder(context) + ..addHeader( + (ContextMenuHeaderBuilder(Key('attachment_picker_context_menu_header_builder')) + ..addLabel(AppLocalizations.of(context).pick_attachments)) + .build()) + ..addTiles(actionTiles)) + .build(); + } + + void openFilePickerByType(BuildContext context, FileType fileType) async { + popBack(); + consumeState(_localFilePickerInteractor.execute(fileType: fileType)); + } + + void _pickFileFailure(Failure failure) { + if (failure is LocalFilePickerFailure) { + if (Get.context != null) { + _appToast.showErrorToast(AppLocalizations.of(Get.context!).can_not_upload_this_file_as_attachments); + } + } + } + + void _pickFileSuccess(Success success) { + if (success is LocalFilePickerSuccess) { + _uploadAttachmentsAction(success.fileInfo); + } + } + + void _uploadAttachmentsAction(FileInfo fileInfo) async { + if (composerArguments.value != null) { + final accountId = composerArguments.value!.session.accounts.keys.first; + final uploadUrl = composerArguments.value!.session.getUploadUrl(accountId); + + consumeState(_uploadAttachmentInteractor.execute(fileInfo, accountId, uploadUrl)); + } + } + + void _uploadAttachmentsFailure(Failure failure) { + if (Get.context != null) { + _appToast.showErrorToast(AppLocalizations.of(Get.context!).can_not_upload_this_file_as_attachments); + } + } + + void _uploadAttachmentsSuccess(Success success) { + if (success is UploadAttachmentSuccess) { + attachments.add(success.attachment); + if (Get.context != null) { + _appToast.showSuccessToast(AppLocalizations.of(Get.context!).attachments_uploaded_successfully); + } + } + } + + void removeAttachmentAction(Attachment attachmentRemoved) { + attachments.removeWhere((attachment) => attachment == attachmentRemoved); + } + void backToEmailViewAction() { Get.back(); } diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 11ecf5ecf..15bc75940 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -1,11 +1,17 @@ import 'package:core/core.dart'; +import 'package:file_picker/file_picker.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'; +import 'package:model/email/attachment.dart'; +import 'package:model/email/email_action_type.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/widgets/email_address_composer_widget_builder.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class ComposerView extends GetWidget { @@ -50,12 +56,40 @@ class ComposerView extends GetWidget { padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Obx(() => (TopBarComposerWidgetBuilder(imagePaths, controller.isEnableEmailSendButton.value) ..addSendEmailActionClick(() => controller.sendEmailAction(context)) + ..addAttachFileActionClick(() => controller.openPickAttachmentMenu(context, _pickAttachmentsActionTiles(context))) ..addBackActionClick(() => controller.backToEmailViewAction())) .build()), ); } Widget _buildEmailHeader(BuildContext context) { + List _pickAttachmentsActionTiles(BuildContext context) { + return [ + _pickPhotoAndVideoAction(context), + _browseFileAction(context), + SizedBox(height: 30), + ]; + } + + Widget _pickPhotoAndVideoAction(BuildContext context) { + return (SimpleContextMenuActionBuilder( + Key('pick_photo_and_video_context_menu_action'), + SvgPicture.asset(imagePaths.icPhotoLibrary, width: 24, height: 24, fit: BoxFit.fill), + AppLocalizations.of(context).photos_and_videos) + ..onActionClick((_) => controller.openFilePickerByType(context, FileType.media))) + .build(); + } + + Widget _browseFileAction(BuildContext context) { + return (SimpleContextMenuActionBuilder( + Key('browse_file_context_menu_action'), + SvgPicture.asset(imagePaths.icMore, width: 24, height: 24, fit: BoxFit.fill), + AppLocalizations.of(context).browse) + ..onActionClick((_) => controller.openFilePickerByType(context, FileType.any))) + .build(); + } + + Widget _buildEmailHeaderField(BuildContext context) { return Container( margin: EdgeInsets.zero, padding: EdgeInsets.only(top: 20), @@ -120,6 +154,10 @@ class ComposerView extends GetWidget { padding: EdgeInsets.only(bottom: 30, top: 16, left: 16, right: 16), child: _buildComposerEditer(context), ) + child: _buildEmailBodyEditorQuoted(context), + ), + _buildLoadingView(), + _buildAttachments(context), ] ) ); @@ -143,4 +181,43 @@ class ComposerView extends GetWidget { ), ); } + + Widget _buildLoadingView() { + return Obx(() => controller.viewState.value.fold( + (failure) => SizedBox.shrink(), + (success) => success is UploadAttachmentLoadingState + ? Center(child: Padding( + padding: EdgeInsets.only(top: 8), + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(color: AppColor.primaryColor)))) + : SizedBox.shrink())); + } + + Widget _buildAttachments(BuildContext context) { + return Obx(() => controller.attachments.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + primary: false, + key: Key('attachment_list'), + padding: EdgeInsets.only(top: 16, bottom: 24, right: 50, left: 24), + itemCount: controller.attachments.length, + itemBuilder: (context, index) => (AttachmentFileTileBuilder( + imagePaths, + controller.attachments[index], + 0, + 0) + ..height(60) + ..addButtonAction(_buildRemoveButtonAttachment(controller.attachments[index]))) + .build()) + : SizedBox.shrink()); + } + + Widget _buildRemoveButtonAttachment(Attachment attachment) { + return (IconBuilder(imagePaths.icComposerClose) + ..size(35) + ..addOnTapActionClick(() => controller.removeAttachmentAction(attachment))) + .build(); + } } \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart b/lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart index f22022f57..1437206d4 100644 --- a/lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart +++ b/lib/features/composer/presentation/widgets/top_bar_composer_widget_builder.dart @@ -5,10 +5,12 @@ import 'package:flutter/widgets.dart'; typedef OnBackActionClick = void Function(); typedef OnSendEmailActionClick = void Function(); +typedef OnAttachFileActionClick = void Function(); class TopBarComposerWidgetBuilder { OnBackActionClick? _onBackActionClick; OnSendEmailActionClick? _onSendEmailActionClick; + OnAttachFileActionClick? _onAttachFileActionClick; final ImagePaths _imagePaths; final bool _isEnableEmailSendButton; @@ -23,6 +25,10 @@ class TopBarComposerWidgetBuilder { _onSendEmailActionClick = onSendEmailActionClick; } + void addAttachFileActionClick(OnAttachFileActionClick onAttachFileActionClick) { + _onAttachFileActionClick = onAttachFileActionClick; + } + Widget build() { return Container( key: Key('top_bar_composer_widget'), @@ -64,7 +70,13 @@ class TopBarComposerWidgetBuilder { children: [ ButtonBuilder(_imagePaths.icComposerFileShare).key(Key('button_file_share')).build(), SizedBox(width: 2), - ButtonBuilder(_imagePaths.icShare).key(Key('button_attachment')).build(), + ButtonBuilder(_imagePaths.icShare) + .key(Key('button_attachment')) + .onPressActionClick(() { + if (_onAttachFileActionClick != null) { + _onAttachFileActionClick!(); + }}) + .build(), SizedBox(width: 2), ButtonBuilder(_imagePaths.icComposerSend) .key(Key('button_send_email')) diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index 971dc3cd0..46d7426b5 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -1,4 +1,5 @@ import 'package:core/core.dart'; +import 'package:core/presentation/views/dialog/downloading_file_dialog_builder.dart'; import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/features/email/presentation/extensions/email_body_part_extension.dart b/lib/features/email/presentation/extensions/email_body_part_extension.dart index 0adda96d1..e354021a2 100644 --- a/lib/features/email/presentation/extensions/email_body_part_extension.dart +++ b/lib/features/email/presentation/extensions/email_body_part_extension.dart @@ -2,5 +2,11 @@ import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart'; import 'package:model/model.dart'; extension EmailBodyPartExtension on EmailBodyPart { - Attachment toAttachment() => Attachment(partId, blobId, size, name, type, cid); + Attachment toAttachment() => Attachment( + partId: partId, + blobId: blobId, + size: size, + name: name, + type: type, + cid: cid); } diff --git a/lib/features/email/presentation/extensions/email_content_extension.dart b/lib/features/email/presentation/extensions/email_content_extension.dart index 68214dd02..6b1be40a9 100644 --- a/lib/features/email/presentation/extensions/email_content_extension.dart +++ b/lib/features/email/presentation/extensions/email_content_extension.dart @@ -1,5 +1,4 @@ import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart'; -import 'package:model/email/email_content.dart'; import 'package:tmail_ui_user/features/email/presentation/constants/email_constants.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/email/presentation/model/message_content.dart'; 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 a9060bb80..f54ab317e 100644 --- a/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart +++ b/lib/features/email/presentation/widgets/attachment_file_tile_builder.dart @@ -20,6 +20,9 @@ class AttachmentFileTileBuilder { OnDownloadAttachmentFileActionClick? _onDownloadAttachmentFileActionClick; OnExpandAttachmentActionClick? _onExpandAttachmentActionClick; + Widget? buttonAction; + double? heightItem; + AttachmentFileTileBuilder( this._imagePaths, this._attachment, @@ -39,6 +42,14 @@ class AttachmentFileTileBuilder { _expandMode = expandMode; } + void addButtonAction(Widget action) { + buttonAction = action; + } + + void height(double height) { + heightItem = height; + } + Widget build() { return Theme( data: ThemeData( @@ -47,6 +58,8 @@ class AttachmentFileTileBuilder { child: Container( key: Key('attach_file_tile'), alignment: Alignment.center, + margin: EdgeInsets.only(top:heightItem != null ? 8 : 0), + height: heightItem ?? null, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: AppColor.attachmentFileBorderColor), @@ -83,6 +96,11 @@ class AttachmentFileTileBuilder { maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12, color: AppColor.attachmentFileSizeColor))) + : null, + trailing: buttonAction != null + ? Transform( + transform: Matrix4.translationValues(6.0, heightItem != null ? -6.0 : 0.0, 0.0), + child: buttonAction) : null ), Transform( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index fb9b7aaf7..6d7add0c9 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -303,5 +303,41 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "attach_file_prepare_text": "Preparing to attach file...", + "@attach_file_prepare_text": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "can_not_upload_this_file_as_attachments": "Can not upload this file as attachments", + "@can_not_upload_this_file_as_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "attachments_uploaded_successfully": "Attachments uploaded successfully", + "@attachments_uploaded_successfully": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "pick_attachments": "Pick attachments", + "@pick_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "photos_and_videos": "Photos and Videos", + "@photos_and_videos": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "browse": "Browse", + "@browse": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index fb9b7aaf7..6d7add0c9 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -303,5 +303,41 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "attach_file_prepare_text": "Preparing to attach file...", + "@attach_file_prepare_text": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "can_not_upload_this_file_as_attachments": "Can not upload this file as attachments", + "@can_not_upload_this_file_as_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "attachments_uploaded_successfully": "Attachments uploaded successfully", + "@attachments_uploaded_successfully": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "pick_attachments": "Pick attachments", + "@pick_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "photos_and_videos": "Photos and Videos", + "@photos_and_videos": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "browse": "Browse", + "@browse": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 72a08f66a..ccdfaffc3 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2021-09-16T14:31:23.463339", + "@@last_modified": "2021-09-17T17:37:57.805293", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -303,5 +303,41 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "attach_file_prepare_text": "Preparing to attach file...", + "@attach_file_prepare_text": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "can_not_upload_this_file_as_attachments": "Can not upload this file as attachments", + "@can_not_upload_this_file_as_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "attachments_uploaded_successfully": "Attachments uploaded successfully", + "@attachments_uploaded_successfully": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "pick_attachments": "Pick attachments", + "@pick_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "photos_and_videos": "Photos and Videos", + "@photos_and_videos": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "browse": "Browse", + "@browse": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index fb9b7aaf7..6d7add0c9 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -303,5 +303,41 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "attach_file_prepare_text": "Preparing to attach file...", + "@attach_file_prepare_text": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "can_not_upload_this_file_as_attachments": "Can not upload this file as attachments", + "@can_not_upload_this_file_as_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "attachments_uploaded_successfully": "Attachments uploaded successfully", + "@attachments_uploaded_successfully": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "pick_attachments": "Pick attachments", + "@pick_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "photos_and_videos": "Photos and Videos", + "@photos_and_videos": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "browse": "Browse", + "@browse": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index fb9b7aaf7..6d7add0c9 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -303,5 +303,41 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "attach_file_prepare_text": "Preparing to attach file...", + "@attach_file_prepare_text": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "can_not_upload_this_file_as_attachments": "Can not upload this file as attachments", + "@can_not_upload_this_file_as_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "attachments_uploaded_successfully": "Attachments uploaded successfully", + "@attachments_uploaded_successfully": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "pick_attachments": "Pick attachments", + "@pick_attachments": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "photos_and_videos": "Photos and Videos", + "@photos_and_videos": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "browse": "Browse", + "@browse": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index 6309a4130..ff9cb78d7 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -5,6 +5,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/http/http_client.dart' as JmapHttpClient; +import 'package:tmail_ui_user/features/composer/data/network/composer_api.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; import 'package:tmail_ui_user/features/session/data/network/session_api.dart'; @@ -53,5 +54,6 @@ class NetworkBindings extends Bindings { Get.put(EmailAPI( Get.find(), Get.find())); + Get.put(ComposerAPI(Get.find())); } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 74f23fed4..57b41d4d8 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -351,5 +351,47 @@ class AppLocalizations { args: [count] ); } + + String get attach_file_prepare_text { + return Intl.message( + 'Preparing to attach file...', + name: 'attach_file_prepare_text' + ); + } + + String get can_not_upload_this_file_as_attachments { + return Intl.message( + 'Can not upload this file as attachments', + name: 'can_not_upload_this_file_as_attachments' + ); + } + + String get attachments_uploaded_successfully { + return Intl.message( + 'Attachments uploaded successfully', + name: 'attachments_uploaded_successfully' + ); + } + + String get pick_attachments { + return Intl.message( + 'Pick attachments', + name: 'pick_attachments' + ); + } + + String get photos_and_videos { + return Intl.message( + 'Photos and Videos', + name: 'photos_and_videos', + ); + } + + String get browse { + return Intl.message( + 'Browse', + name: 'browse', + ); + } }