TF-296 Fix upload file on browser

This commit is contained in:
dab246
2022-03-03 12:05:14 +07:00
committed by Dat H. Pham
parent 025bc53790
commit a24a4a2bfd
9 changed files with 108 additions and 50 deletions
+1 -1
View File
@@ -1 +1 @@
SERVER_URL=http://localhost
SERVER_URL=https://dev.open-paas.org
@@ -5,6 +5,7 @@ import 'dart:io';
import 'package:core/core.dart';
import 'package:dio/dio.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/main/utils/app_logger.dart';
class ComposerAPI {
@@ -13,15 +14,16 @@ class ComposerAPI {
ComposerAPI(this._dioClient);
Future<UploadResponse> uploadAttachment(UploadRequest uploadRequest) async {
final file = File(uploadRequest.fileInfo.filePath);
log('uploadAttachment: fileInfo: ${uploadRequest.fileInfo.props}');
final headerParam = _dioClient.getHeaders();
headerParam[HttpHeaders.contentTypeHeader] = uploadRequest.fileInfo.mimeType;
headerParam[HttpHeaders.contentLengthHeader] = file.readAsBytesSync().length;
headerParam[HttpHeaders.contentLengthHeader] = uploadRequest.fileInfo.fileSize;
final resultJson = await _dioClient.post(
Uri.decodeFull(uploadRequest.uploadUrl.toString()),
options: Options(headers: headerParam),
data: file.openRead());
data: uploadRequest.fileInfo.readStream);
return UploadResponse.fromJson(jsonDecode(resultJson));
}
}
@@ -1,6 +1,5 @@
import 'package:core/core.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart';
import 'package:tmail_ui_user/features/composer/data/datasource/autocomplete_datasource.dart';
@@ -121,7 +120,6 @@ class ComposerBindings extends BaseBindings {
@override
void bindingsController() {
Get.lazyPut(() => TextEditingController());
Get.lazyPut(() => ComposerController(
Get.find<SendEmailInteractor>(),
Get.find<SaveEmailAddressesInteractor>(),
@@ -131,7 +129,6 @@ class ComposerBindings extends BaseBindings {
Get.find<ImagePaths>(),
Get.find<Uuid>(),
Get.find<DeviceInfoPlugin>(),
Get.find<TextEditingController>(),
Get.find<LocalFilePickerInteractor>(),
Get.find<UploadMultipleAttachmentInteractor>(),
Get.find<SaveEmailAsDraftsInteractor>(),
@@ -7,8 +7,8 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:enough_html_editor/enough_html_editor.dart';
import 'package:file_picker/file_picker.dart';
import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart' as HtmlEditorBrowser;
import 'package:http_parser/http_parser.dart';
@@ -64,7 +64,6 @@ class ComposerController extends BaseController {
final AppToast _appToast;
final ImagePaths _imagePaths;
final Uuid _uuid;
final TextEditingController subjectEmailInputController;
final LocalFilePickerInteractor _localFilePickerInteractor;
final UploadMultipleAttachmentInteractor _uploadMultipleAttachmentInteractor;
final DeviceInfoPlugin _deviceInfoPlugin;
@@ -80,6 +79,7 @@ class ComposerController extends BaseController {
HtmlEditorApi? htmlEditorApi;
final HtmlEditorBrowser.HtmlEditorController htmlControllerBrowser = HtmlEditorBrowser.HtmlEditorController();
final subjectEmailInputController = TextEditingController();
final toEmailAddressController = TextEditingController();
final ccEmailAddressController = TextEditingController();
final bccEmailAddressController = TextEditingController();
@@ -109,7 +109,6 @@ class ComposerController extends BaseController {
this._imagePaths,
this._uuid,
this._deviceInfoPlugin,
this.subjectEmailInputController,
this._localFilePickerInteractor,
this._uploadMultipleAttachmentInteractor,
this._saveEmailAsDraftsInteractor,
@@ -136,12 +135,9 @@ class ComposerController extends BaseController {
@override
void onClose() {
subjectEmailInputController.dispose();
toEmailAddressController.dispose();
ccEmailAddressController.dispose();
bccEmailAddressController.dispose();
htmlControllerBrowser.clearFocus();
super.onClose();
}
@@ -359,6 +355,8 @@ class ComposerController extends BaseController {
}
void sendEmailAction(BuildContext context) async {
clearFocusEditor(context);
if (isEnableEmailSendButton.value) {
final arguments = composerArguments.value;
if (arguments != null) {
@@ -416,13 +414,25 @@ class ComposerController extends BaseController {
}
void openPickAttachmentMenu(BuildContext context, List<Widget> actionTiles) {
(ContextMenuBuilder(context)
..addHeader(
(ContextMenuHeaderBuilder(Key('attachment_picker_context_menu_header_builder'))
clearFocusEditor(context);
(ContextMenuBuilder(context)
..addHeader((ContextMenuHeaderBuilder(Key('attachment_picker_context_menu_header_builder'))
..addLabel(AppLocalizations.of(context).pick_attachments))
.build())
..addTiles(actionTiles))
.build();
..addTiles(actionTiles)
..addOnCloseContextMenuAction(() => popBack()))
.build();
}
void openPickAttachmentsForWeb(BuildContext context, RelativeRect? position, List<PopupMenuEntry> popupMenuItems) async {
await showMenu(
context: context,
position: position ?? RelativeRect.fromLTRB(16, 40, 16, 16),
color: Colors.white,
elevation: 5,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
items: popupMenuItems);
}
void openFilePickerByType(BuildContext context, FileType fileType) async {
@@ -525,7 +535,9 @@ class ComposerController extends BaseController {
return false;
}
void saveEmailAsDrafts() async {
void saveEmailAsDrafts(BuildContext context) async {
clearFocusEditor(context);
final arguments = composerArguments.value;
if (arguments != null && Get.context != null) {
final isChanged = await _isEmailChanged(arguments);
@@ -583,7 +595,14 @@ class ComposerController extends BaseController {
return '';
}
void backToEmailViewAction() {
void clearFocusEditor(BuildContext context) {
if (!kIsWeb) {
htmlEditorApi?.unfocus(context);
}
}
void backToEmailViewAction(BuildContext context) {
clearFocusEditor(context);
popBack();
}
}
@@ -25,13 +25,12 @@ class ComposerView extends GetWidget<ComposerController> {
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
controller.htmlEditorApi?.unfocus(context);
controller.saveEmailAsDrafts();
controller.saveEmailAsDrafts(context);
return true;
},
child: GestureDetector(
onTap: () {
controller.htmlEditorApi?.unfocus(context);
controller.clearFocusEditor(context);
},
child: Scaffold(
backgroundColor: AppColor.primaryLightColor,
@@ -60,19 +59,20 @@ class ComposerView extends GetWidget<ComposerController> {
Widget _buildTopBar(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Obx(() => (TopBarComposerWidgetBuilder(imagePaths, controller.isEnableEmailSendButton.value)
child: Obx(() => (TopBarComposerWidgetBuilder(context, imagePaths, controller.isEnableEmailSendButton.value)
..addSendEmailActionClick(() {
controller.htmlEditorApi?.unfocus(context);
controller.sendEmailAction(context);
})
..addAttachFileActionClick(() {
controller.htmlEditorApi?.unfocus(context);
controller.openPickAttachmentMenu(context, _pickAttachmentsActionTiles(context));
..addAttachFileActionClick((position) {
if (kIsWeb) {
controller.openPickAttachmentsForWeb(context,position, _pickAttachmentsActionTilesForWeb(context));
} else {
controller.openPickAttachmentMenu(context, _pickAttachmentsActionTiles(context));
}
})
..addBackActionClick(() {
controller.htmlEditorApi?.unfocus(context);
controller.saveEmailAsDrafts();
controller.backToEmailViewAction();
controller.saveEmailAsDrafts(context);
controller.backToEmailViewAction(context);
}))
.build()),
);
@@ -82,7 +82,15 @@ class ComposerView extends GetWidget<ComposerController> {
return [
_pickPhotoAndVideoAction(context),
_browseFileAction(context),
SizedBox(height: 30),
SizedBox(height: kIsWeb ? 16 : 30),
];
}
List<PopupMenuEntry> _pickAttachmentsActionTilesForWeb(BuildContext context) {
return [
PopupMenuItem(padding: EdgeInsets.symmetric(horizontal: 8), child: _pickPhotoAndVideoAction(context)),
PopupMenuDivider(height: 0.5),
PopupMenuItem(padding: EdgeInsets.symmetric(horizontal: 8), child: _browseFileAction(context)),
];
}
@@ -262,10 +270,11 @@ class ComposerView extends GetWidget<ComposerController> {
alignment: Alignment.topCenter,
child: Column(
children: [
if (kIsWeb) _buildAttachmentsLoadingView(),
Padding(
padding: EdgeInsets.only(top: 8, left: 16, right: 16, bottom: 24),
child: _buildComposerEditor(context)),
_buildAttachmentsLoadingView(),
if (!kIsWeb) _buildAttachmentsLoadingView(),
_buildAttachments(context),
],
),
@@ -290,7 +299,7 @@ class ComposerView extends GetWidget<ComposerController> {
htmlToolbarOptions: HtmlEditorBrowser.HtmlToolbarOptions(
toolbarPosition: HtmlEditorBrowser.ToolbarPosition.custom
),
otherOptions: HtmlEditorBrowser.OtherOptions(height: 550),
otherOptions: HtmlEditorBrowser.OtherOptions(height: 400),
);
} else {
if (controller.composerArguments.value?.emailActionType == EmailActionType.compose) {
@@ -317,7 +326,7 @@ class ComposerView extends GetWidget<ComposerController> {
(failure) => SizedBox.shrink(),
(success) => success is UploadingAttachmentState
? Center(child: Padding(
padding: EdgeInsets.only(bottom: controller.attachments.isNotEmpty ? 0 : 24),
padding: EdgeInsets.only(bottom: controller.attachments.isNotEmpty ? 0 : 24, top: kIsWeb ? 20 : 0),
child: SizedBox(
width: 20,
height: 20,
@@ -1,21 +1,27 @@
import 'package:core/core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
typedef OnBackActionClick = void Function();
typedef OnSendEmailActionClick = void Function();
typedef OnAttachFileActionClick = void Function();
typedef OnAttachFileActionClick = void Function(RelativeRect? position);
class TopBarComposerWidgetBuilder {
OnBackActionClick? _onBackActionClick;
OnSendEmailActionClick? _onSendEmailActionClick;
OnAttachFileActionClick? _onAttachFileActionClick;
final BuildContext _context;
final ImagePaths _imagePaths;
final bool _isEnableEmailSendButton;
TopBarComposerWidgetBuilder(this._imagePaths, this._isEnableEmailSendButton);
TopBarComposerWidgetBuilder(
this._context,
this._imagePaths,
this._isEnableEmailSendButton,
);
void addBackActionClick(OnBackActionClick onBackActionClick) {
_onBackActionClick = onBackActionClick;
@@ -68,14 +74,32 @@ class TopBarComposerWidgetBuilder {
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
key: Key('button_attachment'),
icon: SvgPicture.asset(_imagePaths.icShare, fit: BoxFit.fill),
onPressed: () {
if (_onAttachFileActionClick != null) {
_onAttachFileActionClick!();
MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () {
if (!kIsWeb) {
_onAttachFileActionClick?.call(null);
}
}
},
child: Padding(
padding: EdgeInsets.only(right: kIsWeb ? 16 : 0),
child: SvgPicture.asset(_imagePaths.icShare, fit: BoxFit.fill),
),
onTapDown: (detail) {
if (kIsWeb) {
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,
);
_onAttachFileActionClick?.call(position);
}
},
),
),
IconButton(
key: Key('button_send_email'),
@@ -2,6 +2,7 @@
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart';
@@ -11,13 +12,14 @@ class LocalFilePickerInteractor {
Stream<Either<Failure, Success>> execute({FileType fileType = FileType.any}) async* {
try {
final filesResult = await FilePicker.platform.pickFiles(type: fileType, allowMultiple: true);
final filesResult = await FilePicker.platform.pickFiles(type: fileType, allowMultiple: true, withReadStream: true);
if (filesResult != null && filesResult.files.isNotEmpty) {
final fileInfoResults = filesResult.files
.map((platformFile) => FileInfo(
platformFile.name,
platformFile.path ?? '',
platformFile.size)).toList();
kIsWeb ? '' : platformFile.path ?? '',
platformFile.size,
readStream: platformFile.readStream)).toList();
yield Right<Failure, Success>(LocalFilePickerSuccess(fileInfoResults));
} else {
yield Left<Failure, Success>(LocalFilePickerCancel());
+5 -3
View File
@@ -1,20 +1,22 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:mime/mime.dart';
class FileInfo with EquatableMixin {
final String fileName;
final String filePath;
final int fileSize;
final Stream<List<int>>? readStream;
FileInfo(this.fileName, this.filePath, this.fileSize);
FileInfo(this.fileName, this.filePath, this.fileSize, {this.readStream});
factory FileInfo.empty() {
return FileInfo('', '', 0);
}
String get mimeType => lookupMimeType(filePath) ?? 'application/json; charset=UTF-8';
String get mimeType => lookupMimeType(kIsWeb ? fileName : filePath) ?? 'application/json; charset=UTF-8';
@override
List<Object> get props => [fileName, filePath, fileSize];
List<Object?> get props => [fileName, filePath, fileSize, readStream];
}
+3
View File
@@ -130,6 +130,9 @@ dependencies:
# fk_user_agent
fk_user_agent: 2.1.0
# pointer_interceptor
pointer_interceptor: 0.9.3
dev_dependencies:
flutter_test:
sdk: flutter