diff --git a/assets/images/ic_more.svg b/assets/images/ic_more.svg new file mode 100644 index 000000000..fc336ccbf --- /dev/null +++ b/assets/images/ic_more.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/ic_photo_library.svg b/assets/images/ic_photo_library.svg new file mode 100644 index 000000000..2c0c8a8b3 --- /dev/null +++ b/assets/images/ic_photo_library.svg @@ -0,0 +1,3 @@ + + + diff --git a/core/lib/core.dart b/core/lib/core.dart index 5ddd061ca..7c810c869 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -33,7 +33,9 @@ export 'presentation/views/text/text_form_field_builder.dart'; export 'presentation/views/image/icon_builder.dart'; export 'presentation/views/context_menu/context_menu_action_builder.dart'; export 'presentation/views/context_menu/context_menu_builder.dart'; -export 'presentation/views/dialog/downloading_file_dialog_builder.dart'; +export 'presentation/views/context_menu/context_menu_header_builder.dart'; +export 'presentation/views/context_menu/simple_context_menu_action_builder.dart'; +export 'presentation/views/dialog/loading_dialog_builder.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 5e7019762..132831867 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -39,6 +39,8 @@ class ImagePaths { String get icSelected => _getImagePath('ic_selected.svg'); String get icExpandAttachment => _getImagePath('ic_expand_attachment.svg'); String get icDownload => _getImagePath('ic_download.svg'); + String get icMore => _getImagePath('ic_more.svg'); + String get icPhotoLibrary => _getImagePath('ic_photo_library.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/state/success.dart b/core/lib/presentation/state/success.dart index 36f54c3ee..a94af9c70 100644 --- a/core/lib/presentation/state/success.dart +++ b/core/lib/presentation/state/success.dart @@ -6,10 +6,16 @@ abstract class ViewState extends Success {} class UIState extends ViewState { static final idle = UIState(); - static final loading = UIState(); UIState() : super(); @override List get props => []; } + +class LoadingState extends UIState { + LoadingState(); + + @override + List get props => []; +} \ No newline at end of file diff --git a/core/lib/presentation/views/context_menu/context_menu_header_builder.dart b/core/lib/presentation/views/context_menu/context_menu_header_builder.dart new file mode 100644 index 000000000..220e721f6 --- /dev/null +++ b/core/lib/presentation/views/context_menu/context_menu_header_builder.dart @@ -0,0 +1,32 @@ + +import 'package:core/core.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class ContextMenuHeaderBuilder { + final Key _key; + String? _label; + TextStyle? _textStyle; + + ContextMenuHeaderBuilder(this._key); + + void addLabel(String label) { + _label = label; + } + + void textStyle(TextStyle textStyle) { + _textStyle = textStyle; + } + + ListTile build() { + return ListTile( + key: _key, + title: Transform( + transform: Matrix4.translationValues(12, 5, 0.0), + child: Text( + _label ?? '', + style: _textStyle ?? TextStyle(fontSize: 20.0, color: AppColor.nameUserColor, fontWeight: FontWeight.w500), + ), + )); + } +} \ No newline at end of file diff --git a/core/lib/presentation/views/context_menu/simple_context_menu_action_builder.dart b/core/lib/presentation/views/context_menu/simple_context_menu_action_builder.dart new file mode 100644 index 000000000..a3fe77784 --- /dev/null +++ b/core/lib/presentation/views/context_menu/simple_context_menu_action_builder.dart @@ -0,0 +1,27 @@ + +import 'package:core/presentation/views/context_menu/context_menu_action_builder.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class SimpleContextMenuActionBuilder extends ContextMenuActionBuilder { + SimpleContextMenuActionBuilder( + Key key, + SvgPicture actionIcon, + String actionName + ) : super(key, actionIcon, actionName); + + @override + ListTile build() { + return ListTile( + key: key, + leading: Padding( + padding: EdgeInsets.only(left: 12), + child: actionIcon), + title: Text(actionName, style: actionTextStyle()), + onTap: () { + if (onContextMenuActionClick != null) { + onContextMenuActionClick!(null); + } + }); + } +} diff --git a/core/lib/presentation/views/dialog/loading_dialog_builder.dart b/core/lib/presentation/views/dialog/loading_dialog_builder.dart new file mode 100644 index 000000000..40c6dd9a2 --- /dev/null +++ b/core/lib/presentation/views/dialog/loading_dialog_builder.dart @@ -0,0 +1,33 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class LoadingDialogBuilder { + final Key _key; + final String _title; + + LoadingDialogBuilder(this._key, this._title); + + Widget build() { + return CupertinoAlertDialog( + key: _key, + title: Text(_title, style: TextStyle(fontSize: 17.0, color: AppColor.appColor)), + content: Padding( + padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), + child: Center( + child: Column( + children: [ + SizedBox( + width: 20.0, + height: 20.0, + child: CupertinoActivityIndicator()), + SizedBox(height: 16), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/model/lib/account/account_request.dart b/model/lib/account/account_request.dart index 5b24e8da1..773d27b89 100644 --- a/model/lib/account/account_request.dart +++ b/model/lib/account/account_request.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:equatable/equatable.dart'; import 'package:model/model.dart'; @@ -12,6 +14,8 @@ class AccountRequest with EquatableMixin { 'password': password.value, }; + String basicAuth() => 'Basic ${base64Encode(utf8.encode('${userName.userName}:${password.value}'))}'; + @override List get props => [userName, password]; } \ No newline at end of file diff --git a/model/lib/email/attachment.dart b/model/lib/email/attachment.dart index 139a0b718..ebca713a1 100644 --- a/model/lib/email/attachment.dart +++ b/model/lib/email/attachment.dart @@ -16,14 +16,14 @@ class Attachment with EquatableMixin { final MediaType? type; final String? cid; - Attachment( + Attachment({ this.partId, this.blobId, this.size, this.name, this.type, this.cid - ); + }); String getDownloadUrl(String baseDownloadUrl, AccountId accountId) { final downloadUriTemplate = UriTemplate('$baseDownloadUrl'); diff --git a/model/lib/extensions/session_extension.dart b/model/lib/extensions/session_extension.dart index 6e93ad672..2659ceeb3 100644 --- a/model/lib/extensions/session_extension.dart +++ b/model/lib/extensions/session_extension.dart @@ -1,5 +1,7 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:uri/uri.dart'; extension SessionExtension on Session { String getDownloadUrl() { @@ -9,4 +11,13 @@ extension SessionExtension on Session { } return Uri.decodeFull(baseUrl); } + + Uri getUploadUrl(AccountId accountId) { + final baseUrl = '${uploadUrl.origin}${uploadUrl.path}'; + final uploadUriTemplate = UriTemplate('${Uri.decodeFull(baseUrl)}'); + final uploadUri = uploadUriTemplate.expand({ + 'accountId' : '${accountId.id.value}' + }); + return Uri.parse(uploadUri); + } } \ No newline at end of file diff --git a/model/lib/model.dart b/model/lib/model.dart index 4ffca65f8..8754fd07e 100644 --- a/model/lib/model.dart +++ b/model/lib/model.dart @@ -41,4 +41,9 @@ export 'download/download_task_id.dart'; // Contact export 'contact/contact.dart'; -export 'contact/device_contact.dart'; \ No newline at end of file +export 'contact/device_contact.dart'; + +// Upload +export 'upload/file_info.dart'; +export 'upload/upload_request.dart'; +export 'upload/upload_response.dart'; \ No newline at end of file diff --git a/model/lib/upload/file_info.dart b/model/lib/upload/file_info.dart new file mode 100644 index 000000000..63c1274d0 --- /dev/null +++ b/model/lib/upload/file_info.dart @@ -0,0 +1,17 @@ + +import 'package:equatable/equatable.dart'; + +class FileInfo with EquatableMixin { + final String fileName; + final String filePath; + final int fileSize; + + FileInfo(this.fileName, this.filePath, this.fileSize); + + factory FileInfo.empty() { + return FileInfo('', '', 0); + } + + @override + List get props => [fileName, filePath, fileSize]; +} \ No newline at end of file diff --git a/model/lib/upload/upload_request.dart b/model/lib/upload/upload_request.dart new file mode 100644 index 000000000..ab8fbff3a --- /dev/null +++ b/model/lib/upload/upload_request.dart @@ -0,0 +1,17 @@ + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:model/model.dart'; + +class UploadRequest with EquatableMixin { + + final Uri uploadUrl; + final FileInfo fileInfo; + final AccountId accountId; + final AccountRequest accountRequest; + + UploadRequest(this.uploadUrl, this.accountId, this.fileInfo, this.accountRequest); + + @override + List get props => [uploadUrl, accountId, fileInfo, accountRequest]; +} \ No newline at end of file diff --git a/model/lib/upload/upload_response.dart b/model/lib/upload/upload_response.dart new file mode 100644 index 000000000..4d2e65571 --- /dev/null +++ b/model/lib/upload/upload_response.dart @@ -0,0 +1,40 @@ + +import 'package:equatable/equatable.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:jmap_dart_client/http/converter/account_id_converter.dart'; +import 'package:jmap_dart_client/http/converter/id_converter.dart'; +import 'package:jmap_dart_client/http/converter/media_type_converter.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:model/email/attachment.dart'; + +part 'upload_response.g.dart'; + +@AccountIdConverter() +@MediaTypeConverter() +@IdConverter() +@JsonSerializable() +class UploadResponse with EquatableMixin { + + final AccountId accountId; + final Id blobId; + final MediaType type; + final int size; + + UploadResponse(this.accountId, this.blobId, this.type, this.size); + + factory UploadResponse.fromJson(Map json) => _$UploadResponseFromJson(json); + + Map toJson() => _$UploadResponseToJson(this); + + @override + List get props => [accountId, blobId, type, size]; +} + +extension UploadResponseExtension on UploadResponse { + Attachment toAttachmentFile(String nameFile) { + return Attachment(blobId: blobId, size: UnsignedInt(size), name: nameFile, type: type); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 880d8e92d..62f93dad5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -119,6 +119,9 @@ dependencies: url: git://github.com/linagora/flutter_contacts.git ref: master + # file_picker + file_picker: 3.0.2+2 + dev_dependencies: flutter_test: sdk: flutter