diff --git a/assets/images/empty_image_default.svg b/assets/images/empty_image_default.svg
new file mode 100644
index 000000000..fd0b26259
--- /dev/null
+++ b/assets/images/empty_image_default.svg
@@ -0,0 +1,9 @@
+
diff --git a/core/lib/core.dart b/core/lib/core.dart
index 7c810c869..b4d3b0101 100644
--- a/core/lib/core.dart
+++ b/core/lib/core.dart
@@ -36,6 +36,8 @@ export 'presentation/views/context_menu/context_menu_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';
+export 'presentation/views/dialog/downloading_file_dialog_builder.dart';
+export 'presentation/views/background/background_widget_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 132831867..52960210a 100644
--- a/core/lib/presentation/resources/image_paths.dart
+++ b/core/lib/presentation/resources/image_paths.dart
@@ -41,6 +41,7 @@ class ImagePaths {
String get icDownload => _getImagePath('ic_download.svg');
String get icMore => _getImagePath('ic_more.svg');
String get icPhotoLibrary => _getImagePath('ic_photo_library.svg');
+ String get icEmptyImageDefault => _getImagePath('empty_image_default.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
diff --git a/core/lib/presentation/views/background/background_widget_builder.dart b/core/lib/presentation/views/background/background_widget_builder.dart
new file mode 100644
index 000000000..dee90bc87
--- /dev/null
+++ b/core/lib/presentation/views/background/background_widget_builder.dart
@@ -0,0 +1,56 @@
+
+import 'package:core/core.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+class BackgroundWidgetBuilder {
+ Key? _key;
+ SvgPicture? _image;
+ String? _text;
+
+ final BuildContext _context;
+
+ BackgroundWidgetBuilder(this._context);
+
+ void key(Key key) {
+ _key = key;
+ }
+
+ void image(SvgPicture image) {
+ _image = image;
+ }
+
+ void text(String text) {
+ _text = text;
+ }
+
+ Widget build() {
+ return Center(
+ key: _key ?? Key('BackgroundWidgetBuilder'),
+ child: CustomScrollView(
+ slivers: [
+ SliverFillRemaining(
+ child: Container(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ _image ?? SizedBox.shrink(),
+ Padding(
+ padding: EdgeInsets.only(top: _image != null ? 16 : 0),
+ child: Text(
+ _text ?? '',
+ style: TextStyle(color: AppColor.baseTextColor, fontSize: 16),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ],
+ ),
+ height: MediaQuery.of(_context).size.height,
+ ),
+ )
+ ]
+ )
+ );
+ }
+}
\ No newline at end of file
diff --git a/core/lib/presentation/views/list/tree_view.dart b/core/lib/presentation/views/list/tree_view.dart
index 1c9c55c60..9cd5bf7be 100644
--- a/core/lib/presentation/views/list/tree_view.dart
+++ b/core/lib/presentation/views/list/tree_view.dart
@@ -50,84 +50,43 @@ class _TreeViewData extends StatelessWidget {
}
}
-class TreeViewChild extends StatefulWidget {
- final bool? startExpanded;
+class TreeViewChild {
+ final BuildContext context;
+ final bool? isExpanded;
final Widget parent;
final List children;
final VoidCallback? onTap;
- TreeViewChild({
- required this.parent,
- required this.children,
- this.startExpanded,
- this.onTap,
- Key? key,
- }) : super(key: key);
+ TreeViewChild(
+ this.context,
+ {
+ required this.parent,
+ required this.children,
+ this.isExpanded,
+ this.onTap,
+ Key? key,
+ }
+ );
- @override
- TreeViewChildState createState() => TreeViewChildState();
-
- TreeViewChild copyWith(
- TreeViewChild source, {
- bool? startExpanded,
- Widget? parent,
- List? children,
- VoidCallback? onTap,
- }) {
- return TreeViewChild(
- parent: parent ?? source.parent,
- children: children ?? source.children,
- startExpanded: startExpanded ?? source.startExpanded,
- onTap: onTap ?? source.onTap,
- );
- }
-}
-
-class TreeViewChildState extends State {
- bool? isExpanded;
-
- @override
- void initState() {
- super.initState();
- isExpanded = widget.startExpanded;
- }
-
- @override
- void didChangeDependencies() {
- isExpanded = widget.startExpanded ?? TreeView.of(context).startExpanded;
- super.didChangeDependencies();
- }
-
- @override
- Widget build(BuildContext context) {
+ Widget build() {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
- child: widget.parent,
+ child: parent,
onTap: () {
- if (widget.onTap != null) {
- widget.onTap!();
+ if (onTap != null) {
+ onTap!();
}
- toggleExpanded();
},
),
AnimatedContainer(
duration: Duration(milliseconds: 400),
child: isExpanded!
- ? Column(
- mainAxisSize: MainAxisSize.min,
- children: widget.children,
- )
+ ? Column(mainAxisSize: MainAxisSize.min, children: children)
: Offstage(),
),
],
);
}
-
- void toggleExpanded() {
- setState(() {
- this.isExpanded = !this.isExpanded!;
- });
- }
}
\ No newline at end of file
diff --git a/lib/features/destination_picker/presentation/destination_picker_controller.dart b/lib/features/destination_picker/presentation/destination_picker_controller.dart
index 2fb6d376e..667f79e07 100644
--- a/lib/features/destination_picker/presentation/destination_picker_controller.dart
+++ b/lib/features/destination_picker/presentation/destination_picker_controller.dart
@@ -9,6 +9,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_i
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart';
+import 'package:tmail_ui_user/features/mailbox/presentation/extensions/list_mailbox_node_extension.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -17,7 +18,9 @@ class DestinationPickerController extends BaseController {
final GetAllMailboxInteractor _getAllMailboxInteractor;
final TreeBuilder _treeBuilder;
- final folderMailboxTree = MailboxTree(MailboxNode.root()).obs;
+ MailboxTree folderMailboxTree = MailboxTree(MailboxNode.root());
+ final defaultMailboxList = [].obs;
+ final folderMailboxNodeList = [].obs;
DestinationPickerArguments? destinationPickerArguments;
@@ -46,7 +49,13 @@ class DestinationPickerController extends BaseController {
}
@override
- void onDone() {}
+ void onDone() {
+ viewState.value.map((success) {
+ if (success is GetAllMailboxSuccess) {
+ defaultMailboxList.value = success.defaultMailboxList;
+ }
+ });
+ }
@override
void onError(error) {}
@@ -59,7 +68,20 @@ class DestinationPickerController extends BaseController {
}
void _buildTree(List folderMailboxList) async {
- folderMailboxTree.value = await _treeBuilder.generateMailboxTree(folderMailboxList);
+ folderMailboxTree = await _treeBuilder.generateMailboxTree(folderMailboxList);
+ folderMailboxNodeList.value = folderMailboxTree.root.childrenItems ?? [];
+ }
+
+ void toggleMailboxFolder(MailboxNode selectedMailboxNode) {
+ final newExpandMode = selectedMailboxNode.expandMode == ExpandMode.COLLAPSE
+ ? ExpandMode.EXPAND
+ : ExpandMode.COLLAPSE;
+
+ final newMailboxNodeList = folderMailboxNodeList.updateNode(
+ selectedMailboxNode.item.id,
+ selectedMailboxNode.copyWith(newExpandMode: newExpandMode)) ?? [];
+
+ folderMailboxNodeList.value = newMailboxNodeList;
}
void moveEmailToMailboxAction(PresentationMailbox destinationMailbox) {
diff --git a/lib/features/destination_picker/presentation/destination_picker_view.dart b/lib/features/destination_picker/presentation/destination_picker_view.dart
index 956e7a736..b3be3a8de 100644
--- a/lib/features/destination_picker/presentation/destination_picker_view.dart
+++ b/lib/features/destination_picker/presentation/destination_picker_view.dart
@@ -4,6 +4,7 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:model/model.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/destination_picker_controller.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/widgets/app_bar_destination_picker_builder.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart';
@@ -87,7 +88,7 @@ class DestinationPickerView extends GetWidget {
Widget _buildLoadingView() {
return Obx(() => controller.viewState.value.fold(
(failure) => SizedBox.shrink(),
- (success) => success == UIState.loading
+ (success) => success is LoadingState
? Center(child: Padding(
padding: EdgeInsets.only(top: 16),
child: SizedBox(
@@ -127,7 +128,8 @@ class DestinationPickerView extends GetWidget {
_responsiveUtils,
defaultMailboxList[index],
mailboxDisplayed: MailboxDisplayed.destinationPicker)
- ..onOpenMailboxAction((mailbox) => controller.moveEmailToMailboxAction(mailbox)))
+ ..onOpenMailboxAction((mailbox) =>
+ controller.moveEmailToMailboxAction(mailbox.toPresentationMailboxWithMailboxPath(mailbox.name?.name ?? ''))))
.build());
} else {
return SizedBox.shrink();
@@ -137,7 +139,7 @@ class DestinationPickerView extends GetWidget {
}
Widget _buildFolderMailbox(BuildContext context) {
- return Obx(() => controller.folderMailboxTree.value.root.hasChildren()
+ return Obx(() => controller.folderMailboxNodeList.isNotEmpty
? Transform(
transform: Matrix4.translationValues(-12.0, 0.0, 0.0),
child: Padding(
@@ -145,7 +147,7 @@ class DestinationPickerView extends GetWidget {
child: TreeView(
startExpanded: false,
key: Key('folder_mailbox_list'),
- children: _buildListChildTileWidget(context, controller.folderMailboxTree.value.root.childrenItems!))))
+ children: _buildListChildTileWidget(context, controller.folderMailboxNodeList))))
: SizedBox.shrink()
);
}
@@ -155,28 +157,40 @@ class DestinationPickerView extends GetWidget {
.map((mailboxNode) => mailboxNode.hasChildren()
? Padding(
padding: EdgeInsets.only(left: 20),
- child: TreeViewChild(
- key: Key('children_tree_mailbox_child'),
- parent: MailBoxFolderTileBuilder(
- context,
- _imagePaths,
- _responsiveUtils,
- mailboxNode,
- mailboxDisplayed: MailboxDisplayed.destinationPicker)
- .build(),
- children: _buildListChildTileWidget(context, mailboxNode.childrenItems!)))
+ child: TreeViewChild(
+ context,
+ key: Key('children_tree_mailbox_child'),
+ isExpanded: mailboxNode.expandMode == ExpandMode.EXPAND,
+ parent: (MailBoxFolderTileBuilder(
+ context,
+ _imagePaths,
+ _responsiveUtils,
+ mailboxNode,
+ mailboxDisplayed: MailboxDisplayed.destinationPicker)
+ ..addOnSelectMailboxFolderClick((mailboxNode) => controller.moveEmailToMailboxAction(mailboxNode.item.toPresentationMailboxWithMailboxPath(
+ mailboxNode.getPathMailboxNode(
+ controller.folderMailboxTree,
+ controller.defaultMailboxList,
+ ))))
+ ..addOnExpandFolderActionClick((mailboxNode) => controller.toggleMailboxFolder(mailboxNode)))
+ .build(),
+ children: _buildListChildTileWidget(context, mailboxNode.childrenItems!)
+ ).build())
: Padding(
padding: EdgeInsets.only(left: 20),
- child: GestureDetector(
- onTap: () => controller.moveEmailToMailboxAction(mailboxNode.item),
- child: MailBoxFolderTileBuilder(
+ child: (MailBoxFolderTileBuilder(
context,
_imagePaths,
_responsiveUtils,
mailboxNode,
mailboxDisplayed: MailboxDisplayed.destinationPicker)
- .build(),
- )))
+ ..addOnSelectMailboxFolderClick((mailboxNode) => controller.moveEmailToMailboxAction(mailboxNode.item.toPresentationMailboxWithMailboxPath(
+ mailboxNode.getPathMailboxNode(
+ controller.folderMailboxTree,
+ controller.defaultMailboxList,
+ )))))
+ .build(),
+ ))
.toList();
}
}
\ No newline at end of file
diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart
index d2f932f4c..a5fa066a8 100644
--- a/lib/features/email/data/datasource/email_datasource.dart
+++ b/lib/features/email/data/datasource/email_datasource.dart
@@ -11,7 +11,7 @@ abstract class EmailDataSource {
Future sendEmail(AccountId accountId, EmailRequest emailRequest);
- Future markAsRead(AccountId accountId, EmailId emailId, ReadActions readActions);
+ Future> markAsRead(AccountId accountId, List emails, ReadActions readActions);
Future> downloadAttachments(
List attachments,
@@ -28,5 +28,5 @@ abstract class EmailDataSource {
CancelToken cancelToken
);
- Future moveToMailbox(AccountId accountId, MoveRequest moveRequest);
+ Future> moveToMailbox(AccountId accountId, MoveRequest moveRequest);
}
\ No newline at end of file
diff --git a/lib/features/email/data/datasource_impl/email_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_datasource_impl.dart
index 2fbf04712..2731397da 100644
--- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart
+++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart
@@ -32,9 +32,9 @@ class EmailDataSourceImpl extends EmailDataSource {
}
@override
- Future markAsRead(AccountId accountId, EmailId emailId, ReadActions readActions) {
+ Future> markAsRead(AccountId accountId, List emails, ReadActions readActions) {
return Future.sync(() async {
- return await emailAPI.markAsRead(accountId, emailId, readActions);
+ return await emailAPI.markAsRead(accountId, emails, readActions);
}).catchError((error) {
throw error;
});
@@ -70,7 +70,7 @@ class EmailDataSourceImpl extends EmailDataSource {
}
@override
- Future moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
+ Future> moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
return Future.sync(() async {
return await emailAPI.moveToMailbox(accountId, moveRequest);
}).catchError((error) {
diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart
index b35bce6b2..2fac78fa6 100644
--- a/lib/features/email/data/network/email_api.dart
+++ b/lib/features/email/data/network/email_api.dart
@@ -15,7 +15,6 @@ import 'package:jmap_dart_client/jmap/jmap_request.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/get/get_email_method.dart';
import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart';
-import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/email/set/set_email_method.dart';
import 'package:jmap_dart_client/jmap/mail/email/set/set_email_response.dart';
import 'package:jmap_dart_client/jmap/mail/email/submission/address.dart';
@@ -113,28 +112,33 @@ class EmailAPI {
});
}
- Future markAsRead(AccountId accountId, EmailId emailId, ReadActions readAction) async {
+ Future> markAsRead(AccountId accountId, List emails, ReadActions readActions) async {
+ final emailIds = emails.map((email) => email.id).toList();
+
final setEmailMethod = SetEmailMethod(accountId)
- ..addUpdates({
- emailId.id: KeyWordIdentifier.emailSeen.generateReadActionPath(readAction)
- });
+ ..addUpdates(emailIds.generateMapUpdateObjectMarkAsRead(readActions));
+
+ final getEmailMethod = GetEmailMethod(accountId)
+ ..addIds(emailIds.toIds().toSet())
+ ..addProperties(Properties({'keywords'}));
final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
- final setEmailInvocation = requestBuilder.invocation(setEmailMethod);
+ requestBuilder.invocation(setEmailMethod);
+
+ final getEmailInvocation = requestBuilder.invocation(getEmailMethod);
final response = await (requestBuilder
..usings(setEmailMethod.requiredCapabilities))
.build()
.execute();
- final setEmailResponse = response.parse(
- setEmailInvocation.methodCallId,
- SetEmailResponse.deserialize);
+ final getEmailResponse = response.parse(
+ getEmailInvocation.methodCallId,
+ GetEmailResponse.deserialize);
return Future.sync(() async {
- final emailUpdated = setEmailResponse!.updated![emailId.id];
- return emailUpdated == null;
+ return getEmailResponse!.list;
}).catchError((error) {
throw error;
});
@@ -188,12 +192,10 @@ class EmailAPI {
cancelToken: cancelToken);
}
- Future moveToMailbox(AccountId accountId, MoveRequest moveRequest) async {
+ Future> moveToMailbox(AccountId accountId, MoveRequest moveRequest) async {
final setEmailMethod = SetEmailMethod(accountId)
- ..addUpdates({
- moveRequest.emailId.id: moveRequest.currentMailboxId
- .generateMoveToMailboxActionPath(moveRequest.destinationMailboxId)
- });
+ ..addUpdates(moveRequest.emailIds
+ .generateMapUpdateObjectMoveToMailbox(moveRequest.currentMailboxId, moveRequest.destinationMailboxId));
final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation());
@@ -209,8 +211,10 @@ class EmailAPI {
SetEmailResponse.deserialize);
return Future.sync(() async {
- final emailUpdated = setEmailResponse!.updated![moveRequest.emailId.id];
- return emailUpdated == null;
+ final mapUpdated = setEmailResponse!.updated!;
+ return moveRequest.emailIds
+ .where((emailId) => mapUpdated.containsKey(emailId.id))
+ .toList();
}).catchError((error) {
throw error;
});
diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart
index 3299f99b1..568944b06 100644
--- a/lib/features/email/data/repository/email_repository_impl.dart
+++ b/lib/features/email/data/repository/email_repository_impl.dart
@@ -25,8 +25,8 @@ class EmailRepositoryImpl extends EmailRepository {
}
@override
- Future markAsRead(AccountId accountId, EmailId emailId, ReadActions readAction) {
- return emailDataSource.markAsRead(accountId, emailId, readAction);
+ Future> markAsRead(AccountId accountId, List emails, ReadActions readActions) {
+ return emailDataSource.markAsRead(accountId, emails, readActions);
}
@override
@@ -56,7 +56,7 @@ class EmailRepositoryImpl extends EmailRepository {
}
@override
- Future moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
+ Future> moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
return emailDataSource.moveToMailbox(accountId, moveRequest);
}
}
\ No newline at end of file
diff --git a/lib/features/email/domain/model/move_request.dart b/lib/features/email/domain/model/move_request.dart
index e90f6013d..2290db693 100644
--- a/lib/features/email/domain/model/move_request.dart
+++ b/lib/features/email/domain/model/move_request.dart
@@ -6,29 +6,28 @@ import 'package:model/model.dart';
class MoveRequest with EquatableMixin {
- final EmailId emailId;
+ final List emailIds;
final MailboxId currentMailboxId;
- final MailboxName currentMailboxName;
final MailboxId destinationMailboxId;
- final MailboxName destinationMailboxName;
final MoveAction moveAction;
+ final String? destinationPath;
MoveRequest(
- this.emailId,
+ this.emailIds,
this.currentMailboxId,
- this.currentMailboxName,
this.destinationMailboxId,
- this.destinationMailboxName,
this.moveAction,
+ {
+ this.destinationPath,
+ }
);
@override
List