TF-80 Move multiple email to mailbox from thread view

This commit is contained in:
dab246
2021-09-21 12:52:27 +07:00
committed by Dat H. Pham
parent 24e52dc6a4
commit a12efc8d2b
43 changed files with 1007 additions and 443 deletions
@@ -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 = <PresentationMailbox>[].obs;
final folderMailboxNodeList = <MailboxNode>[].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<PresentationMailbox> 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) {
@@ -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<DestinationPickerController> {
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<DestinationPickerController> {
_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<DestinationPickerController> {
}
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<DestinationPickerController> {
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<DestinationPickerController> {
.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();
}
}
@@ -11,7 +11,7 @@ abstract class EmailDataSource {
Future<bool> sendEmail(AccountId accountId, EmailRequest emailRequest);
Future<bool> markAsRead(AccountId accountId, EmailId emailId, ReadActions readActions);
Future<List<Email>> markAsRead(AccountId accountId, List<Email> emails, ReadActions readActions);
Future<List<DownloadTaskId>> downloadAttachments(
List<Attachment> attachments,
@@ -28,5 +28,5 @@ abstract class EmailDataSource {
CancelToken cancelToken
);
Future<bool> moveToMailbox(AccountId accountId, MoveRequest moveRequest);
Future<List<EmailId>> moveToMailbox(AccountId accountId, MoveRequest moveRequest);
}
@@ -32,9 +32,9 @@ class EmailDataSourceImpl extends EmailDataSource {
}
@override
Future<bool> markAsRead(AccountId accountId, EmailId emailId, ReadActions readActions) {
Future<List<Email>> markAsRead(AccountId accountId, List<Email> 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<bool> moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
Future<List<EmailId>> moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
return Future.sync(() async {
return await emailAPI.moveToMailbox(accountId, moveRequest);
}).catchError((error) {
+22 -18
View File
@@ -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<bool> markAsRead(AccountId accountId, EmailId emailId, ReadActions readAction) async {
Future<List<Email>> markAsRead(AccountId accountId, List<Email> 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<SetEmailResponse>(
setEmailInvocation.methodCallId,
SetEmailResponse.deserialize);
final getEmailResponse = response.parse<GetEmailResponse>(
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<bool> moveToMailbox(AccountId accountId, MoveRequest moveRequest) async {
Future<List<EmailId>> 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;
});
@@ -25,8 +25,8 @@ class EmailRepositoryImpl extends EmailRepository {
}
@override
Future<bool> markAsRead(AccountId accountId, EmailId emailId, ReadActions readAction) {
return emailDataSource.markAsRead(accountId, emailId, readAction);
Future<List<Email>> markAsRead(AccountId accountId, List<Email> emails, ReadActions readActions) {
return emailDataSource.markAsRead(accountId, emails, readActions);
}
@override
@@ -56,7 +56,7 @@ class EmailRepositoryImpl extends EmailRepository {
}
@override
Future<bool> moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
Future<List<EmailId>> moveToMailbox(AccountId accountId, MoveRequest moveRequest) {
return emailDataSource.moveToMailbox(accountId, moveRequest);
}
}
@@ -6,29 +6,28 @@ import 'package:model/model.dart';
class MoveRequest with EquatableMixin {
final EmailId emailId;
final List<EmailId> 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<Object?> get props => [
emailId,
emailIds,
currentMailboxId,
currentMailboxName,
destinationMailboxId,
destinationMailboxName,
moveAction
moveAction,
destinationPath,
];
}
@@ -10,7 +10,7 @@ abstract class EmailRepository {
Future<bool> sendEmail(AccountId accountId, EmailRequest emailRequest);
Future<bool> markAsRead(AccountId accountId, EmailId emailId, ReadActions readAction);
Future<List<Email>> markAsRead(AccountId accountId, List<Email> emails, ReadActions readActions);
Future<List<DownloadTaskId>> downloadAttachments(
List<Attachment> attachments,
@@ -27,5 +27,5 @@ abstract class EmailRepository {
CancelToken cancelToken
);
Future<bool> moveToMailbox(AccountId accountId, MoveRequest moveRequest);
Future<List<EmailId>> moveToMailbox(AccountId accountId, MoveRequest moveRequest);
}
@@ -1,15 +1,16 @@
import 'package:core/core.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/read_actions.dart';
import 'package:model/model.dart';
class MarkAsEmailReadSuccess extends UIState {
final EmailId emailId;
final Email updatedEmail;
final ReadActions readActions;
MarkAsEmailReadSuccess(this.emailId, this.readActions);
MarkAsEmailReadSuccess(this.updatedEmail, this.readActions);
@override
List<Object?> get props => [emailId, readActions];
List<Object?> get props => [updatedEmail, readActions];
}
class MarkAsEmailReadFailure extends FeatureFailure {
@@ -1,13 +1,29 @@
import 'package:core/core.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_request.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
class MoveToMailboxSuccess extends UIState {
final MoveRequest moveRequest;
final EmailId emailId;
final MailboxId currentMailboxId;
final MailboxId destinationMailboxId;
final MoveAction moveAction;
final String? destinationPath;
MoveToMailboxSuccess(this.moveRequest);
MoveToMailboxSuccess(
this.emailId,
this.currentMailboxId,
this.destinationMailboxId,
this.moveAction,
this.destinationPath);
@override
List<Object?> get props => [moveRequest];
List<Object?> get props => [
emailId,
currentMailboxId,
destinationMailboxId,
moveAction,
destinationPath];
}
class MoveToMailboxFailure extends FeatureFailure {
@@ -2,19 +2,24 @@ import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/read_actions.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
class MarkAsEmailReadInteractor {
final EmailRepository emailRepository;
final EmailRepository _emailRepository;
MarkAsEmailReadInteractor(this.emailRepository);
MarkAsEmailReadInteractor(this._emailRepository);
Stream<Either<Failure, Success>> execute(AccountId accountId, EmailId emailId, ReadActions readAction) async* {
Stream<Either<Failure, Success>> execute(AccountId accountId, Email email, ReadActions readAction) async* {
try {
final result = await emailRepository.markAsRead(accountId, emailId, readAction);
yield result ? Right(MarkAsEmailReadSuccess(emailId, readAction)) : Left(MarkAsEmailReadFailure(null, readAction));
final result = await _emailRepository.markAsRead(accountId, [email], readAction);
if (result.isNotEmpty) {
final updatedEmail = email.updatedEmail(newKeywords: result.first.keywords);
yield Right(MarkAsEmailReadSuccess(updatedEmail, readAction));
} else {
yield Left(MarkAsEmailReadFailure(null, readAction));
}
} catch (e) {
yield Left(MarkAsEmailReadFailure(e, readAction));
}
@@ -13,9 +13,16 @@ class MoveToMailboxInteractor {
Stream<Either<Failure, Success>> execute(AccountId accountId, MoveRequest moveRequest) async* {
try {
final result = await emailRepository.moveToMailbox(accountId, moveRequest);
yield result
? Right(MoveToMailboxSuccess(moveRequest))
: Left(MoveToMailboxFailure(null));
if (result.isNotEmpty) {
yield Right(MoveToMailboxSuccess(
result.first,
moveRequest.currentMailboxId,
moveRequest.destinationMailboxId,
moveRequest.moveAction,
moveRequest.destinationPath));
} else {
yield Left(MoveToMailboxFailure(null));
}
} catch (e) {
yield Left(MoveToMailboxFailure(e));
}
@@ -8,7 +8,6 @@ import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
@@ -47,6 +46,7 @@ class EmailController extends BaseController {
final emailAddressExpandMode = ExpandMode.COLLAPSE.obs;
final attachmentsExpandMode = ExpandMode.COLLAPSE.obs;
final emailContent = Rxn<EmailContent>();
EmailId? _currentEmailId;
EmailController(
this._getEmailContentInteractor,
@@ -62,12 +62,15 @@ class EmailController extends BaseController {
void onReady() {
super.onReady();
mailboxDashBoardController.selectedEmail.listen((presentationEmail) {
_clearEmailContent();
final accountId = mailboxDashBoardController.accountId.value;
if (accountId != null && presentationEmail != null) {
_getEmailContentAction(accountId, presentationEmail.id);
if (presentationEmail.isUnReadEmail()) {
markAsEmailRead(presentationEmail, ReadActions.markAsRead);
if (_currentEmailId != presentationEmail?.id) {
_currentEmailId = presentationEmail?.id;
_clearEmailContent();
final accountId = mailboxDashBoardController.accountId.value;
if (accountId != null && presentationEmail != null) {
_getEmailContentAction(accountId, presentationEmail.id);
if (presentationEmail.isUnReadEmail()) {
markAsEmailRead(presentationEmail, ReadActions.markAsRead);
}
}
}
});
@@ -131,11 +134,14 @@ class EmailController extends BaseController {
final accountId = mailboxDashBoardController.accountId.value;
final mailboxCurrent = mailboxDashBoardController.selectedMailbox.value;
if (accountId != null && mailboxCurrent != null) {
consumeState(_markAsEmailReadInteractor.execute(accountId, presentationEmail.id, readActions));
consumeState(_markAsEmailReadInteractor.execute(accountId, presentationEmail.toEmail(), readActions));
}
}
void _markAsEmailReadSuccess(Success success) {
if (success is MarkAsEmailReadSuccess) {
mailboxDashBoardController.setSelectedEmail(success.updatedEmail.toPresentationEmail(selectMode: SelectMode.ACTIVE));
}
mailboxDashBoardController.dispatchState(Right(success));
if (success is MarkAsEmailReadSuccess && success.readActions == ReadActions.markAsUnread) {
@@ -144,7 +150,9 @@ class EmailController extends BaseController {
}
void _markAsEmailReadFailure(Failure failure) {
backToThreadView();
if (failure is MarkAsEmailReadFailure && failure.readActions == ReadActions.markAsUnread) {
backToThreadView();
}
}
void toggleDisplayAttachmentsAction() {
@@ -244,19 +252,18 @@ class EmailController extends BaseController {
final accountId = mailboxDashBoardController.accountId.value;
if (currentMailbox != null && accountId != null) {
final mailboxDestination = await push(
final destinationMailbox = await push(
AppRoutes.DESTINATION_PICKER,
arguments: DestinationPickerArguments(accountId, [email.id], currentMailbox)
);
if (mailboxDestination != null && mailboxDestination is PresentationMailbox) {
if (destinationMailbox != null && destinationMailbox is PresentationMailbox) {
_moveToMailbox(accountId, MoveRequest(
email.id,
[email.id],
currentMailbox.id,
currentMailbox.name ?? MailboxName(''),
mailboxDestination.id,
mailboxDestination.name ?? MailboxName(''),
MoveAction.moveTo));
destinationMailbox.id,
MoveAction.moveTo,
destinationPath: destinationMailbox.mailboxPath));
}
}
}
@@ -269,19 +276,17 @@ class EmailController extends BaseController {
mailboxDashBoardController.dispatchState(Right(success));
if (success is MoveToMailboxSuccess
&& success.moveRequest.moveAction == MoveAction.moveTo
&& success.moveAction == MoveAction.moveTo
&& Get.context != null && Get.overlayContext != null) {
_appToast.showToastWithAction(
Get.overlayContext!,
AppLocalizations.of(Get.context!).moved_to_mailbox(success.moveRequest.destinationMailboxName.name),
AppLocalizations.of(Get.context!).moved_to_mailbox(success.destinationPath ?? ''),
AppLocalizations.of(Get.context!).undo_action,
() {
final newMoveRequest = MoveRequest(
success.moveRequest.emailId,
success.moveRequest.destinationMailboxId,
success.moveRequest.destinationMailboxName,
success.moveRequest.currentMailboxId,
success.moveRequest.currentMailboxName,
[success.emailId],
success.destinationMailboxId,
success.currentMailboxId,
MoveAction.undo);
_undoMoveToMailbox(newMoveRequest);
}
@@ -0,0 +1,18 @@
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
extension ListMailboxNodeExtensions on List<MailboxNode> {
List<MailboxNode>? updateNode(MailboxId currentMailboxId, MailboxNode newNode) {
return map((MailboxNode child) {
if (child.item.id == currentMailboxId) {
return newNode;
} else {
if (child.hasChildren()) {
return child.copyWith(mailboxNodes: child.updateNode(currentMailboxId, newNode));
}
return child;
}
}).toList();
}
}
@@ -11,10 +11,11 @@ import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_i
import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart';
import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart';
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:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -26,8 +27,8 @@ class MailboxController extends BaseController {
final TreeBuilder _treeBuilder;
final ResponsiveUtils responsiveUtils;
final folderMailboxTree = MailboxTree(MailboxNode.root()).obs;
final defaultMailboxList = <PresentationMailbox>[].obs;
final folderMailboxNodeList = <MailboxNode>[].obs;
MailboxController(
this._getAllMailboxInteractor,
@@ -51,6 +52,10 @@ class MailboxController extends BaseController {
success is MarkAsMultipleEmailReadAllSuccess ||
success is MarkAsMultipleEmailReadHasSomeEmailFailure) {
refreshGetAllMailboxAction();
} else if (success is MoveMultipleEmailToMailboxAllSuccess
|| success is MoveMultipleEmailToMailboxHasSomeEmailFailure) {
mailboxDashBoardController.clearState();
refreshGetAllMailboxAction();
}
});
});
@@ -97,7 +102,20 @@ class MailboxController extends BaseController {
void onError(error) {}
void _buildTree(List<PresentationMailbox> folderMailboxList) async {
folderMailboxTree.value = await _treeBuilder.generateMailboxTree(folderMailboxList);
final _folderMailboxTree = await _treeBuilder.generateMailboxTree(folderMailboxList);
folderMailboxNodeList.value = _folderMailboxTree.root.childrenItems ?? [];
}
void toggleMailboxFolder(MailboxNode mailboxNode) {
final newExpandMode = mailboxNode.expandMode == ExpandMode.COLLAPSE
? ExpandMode.EXPAND
: ExpandMode.COLLAPSE;
final newMailboxNodeList = folderMailboxNodeList.updateNode(
mailboxNode.item.id,
mailboxNode.copyWith(newExpandMode: newExpandMode)) ?? [];
folderMailboxNodeList.value = newMailboxNodeList;
}
void _setUpMapMailboxIdDefault(List<PresentationMailbox> defaultMailboxList) {
@@ -1,7 +1,4 @@
import 'package:core/core.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
@@ -174,7 +171,7 @@ class MailboxView extends GetWidget<MailboxController> {
}
Widget _buildFolderMailbox(BuildContext context) {
return Obx(() => controller.folderMailboxTree.value.root.hasChildren()
return Obx(() => controller.folderMailboxNodeList.isNotEmpty
? Transform(
transform: Matrix4.translationValues(responsiveUtils.isMobile(context) ? 0.0 : -15.0, 0.0, 0.0),
child: Padding(
@@ -182,7 +179,7 @@ class MailboxView extends GetWidget<MailboxController> {
child: TreeView(
startExpanded: false,
key: Key('folder_mailbox_list'),
children: _buildListChildTileWidget(context, controller.folderMailboxTree.value.root.childrenItems!)))
children: _buildListChildTileWidget(context, controller.folderMailboxNodeList)))
)
: SizedBox.shrink()
);
@@ -192,24 +189,27 @@ class MailboxView extends GetWidget<MailboxController> {
return listMailboxNode
.map((mailboxNode) => mailboxNode.hasChildren()
? Padding(
padding: EdgeInsets.only(left: 16),
padding: EdgeInsets.only(left: 20),
child: TreeViewChild(
key: Key('children_tree_mailbox_child'),
parent: Obx(() => MailBoxFolderTileBuilder(
context,
imagePaths,
responsiveUtils,
mailboxNode,
selectMode: controller.getSelectMode(
mailboxNode.item,
controller.mailboxDashBoardController.selectedMailbox.value))
.build()),
children: _buildListChildTileWidget(context, mailboxNode.childrenItems!)))
context,
key: Key('children_tree_mailbox_child'),
isExpanded: mailboxNode.expandMode == ExpandMode.EXPAND,
parent: Obx(() => (MailBoxFolderTileBuilder(
context,
imagePaths,
responsiveUtils,
mailboxNode,
selectMode: controller.getSelectMode(
mailboxNode.item,
controller.mailboxDashBoardController.selectedMailbox.value))
..addOnSelectMailboxFolderClick((mailboxNode) => controller.selectMailbox(context, mailboxNode.item))
..addOnExpandFolderActionClick((mailboxNode) => controller.toggleMailboxFolder(mailboxNode)))
.build()),
children: _buildListChildTileWidget(context, mailboxNode.childrenItems!)
).build())
: Padding(
padding: EdgeInsets.only(left: 16),
child: Obx(() => GestureDetector(
onTap: () => controller.selectMailbox(context, mailboxNode.item),
child: MailBoxFolderTileBuilder(
padding: EdgeInsets.only(left: 20),
child: Obx(() => (MailBoxFolderTileBuilder(
context,
imagePaths,
responsiveUtils,
@@ -217,8 +217,9 @@ class MailboxView extends GetWidget<MailboxController> {
selectMode: controller.getSelectMode(
mailboxNode.item,
controller.mailboxDashBoardController.selectedMailbox.value))
.build(),
))))
..addOnSelectMailboxFolderClick((mailboxNode) => controller.selectMailbox(context, mailboxNode.item)))
.build(),
)))
.toList();
}
@@ -1,19 +1,24 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/mailbox/expand_mode.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree.dart';
class MailboxNode with EquatableMixin{
static PresentationMailbox _root = PresentationMailbox(MailboxId(Id('root')));
PresentationMailbox item;
List<MailboxNode>? childrenItems;
ExpandMode expandMode;
factory MailboxNode.root() => MailboxNode(_root);
static PresentationMailbox rootItem() => _root;
bool hasChildren() => childrenItems?.isNotEmpty ?? false;
MailboxNode(this.item, {this.childrenItems});
MailboxNode(this.item, {this.childrenItems, this.expandMode = ExpandMode.COLLAPSE});
void addChildNode(MailboxNode node) {
if (childrenItems == null) {
@@ -22,6 +27,60 @@ class MailboxNode with EquatableMixin{
childrenItems?.add(node);
}
String getPathMailboxNode(MailboxTree mailboxTree, List<PresentationMailbox> defaultMailboxList) {
String path = '${item.name?.name}';
var parentId = item.parentId;
while(parentId != null) {
var parent = mailboxTree.findNode(parentId);
if (parent == null) {
try {
final parentItem = defaultMailboxList.firstWhere((mailbox) => mailbox.id == parentId);
parent = MailboxNode(parentItem);
} catch(e) {}
}
if (parent != null) {
path = '${parent.item.name?.name}/$path';
parentId = parent.item.parentId;
} else {
break;
}
}
return path;
}
List<MailboxNode>? updateNode(MailboxId mailboxId, MailboxNode newNode, {MailboxNode? parent}) {
List<MailboxNode>? _children = parent == null ? this.childrenItems : parent.childrenItems;
return _children?.map((MailboxNode child) {
if (child.item.id == mailboxId) {
return newNode;
} else {
if (child.hasChildren()) {
return child.copyWith(
mailboxNodes: updateNode(
mailboxId,
newNode,
parent: child,
),
);
}
return child;
}
}).toList();
}
@override
List<Object?> get props => [item, childrenItems];
}
}
extension MailboxNodeExtension on MailboxNode {
MailboxNode copyWith({ExpandMode? newExpandMode, List<MailboxNode>? mailboxNodes}) {
return MailboxNode(
item,
childrenItems: mailboxNodes ?? childrenItems,
expandMode: newExpandMode ?? expandMode);
}
}
@@ -1,5 +1,8 @@
import 'dart:collection';
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'mailbox_node.dart';
@@ -7,6 +10,23 @@ class MailboxTree with EquatableMixin {
MailboxNode root;
MailboxTree(this.root);
MailboxNode? findNode(MailboxId? mailboxId) {
var result;
final queue = ListQueue<MailboxNode>();
queue.addLast(root);
while (queue.isNotEmpty && mailboxId != null) {
final currentNode = queue.removeFirst();
if (mailboxId == currentNode.item.id) {
result = currentNode;
break;
}
currentNode.childrenItems?.forEach((child) {
queue.addLast(child);
});
}
return result;
}
@override
List<Object?> get props => [root];
}
@@ -3,9 +3,13 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:model/mailbox/select_mode.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_displayed.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
typedef OnExpandFolderActionClick = void Function(MailboxNode mailboxNode);
typedef OnSelectMailboxFolderClick = void Function(MailboxNode mailboxNode);
class MailBoxFolderTileBuilder {
final MailboxNode _mailboxNode;
@@ -15,6 +19,9 @@ class MailBoxFolderTileBuilder {
final SelectMode selectMode;
final MailboxDisplayed mailboxDisplayed;
OnExpandFolderActionClick? _onExpandFolderActionClick;
OnSelectMailboxFolderClick? _onSelectMailboxFolderClick;
MailBoxFolderTileBuilder(
this._context,
this._imagePaths,
@@ -26,6 +33,14 @@ class MailBoxFolderTileBuilder {
}
);
void addOnExpandFolderActionClick(OnExpandFolderActionClick onExpandFolderActionClick) {
this._onExpandFolderActionClick = onExpandFolderActionClick;
}
void addOnSelectMailboxFolderClick(OnSelectMailboxFolderClick onSelectMailboxFolderClick) {
this._onSelectMailboxFolderClick = onSelectMailboxFolderClick;
}
Widget build() {
return Container(
key: Key('mailbox_folder_tile'),
@@ -39,6 +54,11 @@ class MailBoxFolderTileBuilder {
child: MediaQuery(
data: MediaQueryData(padding: EdgeInsets.zero),
child: ListTile(
onTap: () {
if (_onSelectMailboxFolderClick != null) {
_onSelectMailboxFolderClick!(_mailboxNode);
}
},
contentPadding: EdgeInsets.zero,
leading: Padding(
padding: EdgeInsets.only(left: mailboxDisplayed == MailboxDisplayed.mailbox
@@ -72,14 +92,21 @@ class MailBoxFolderTileBuilder {
: 36,
left: mailboxDisplayed == MailboxDisplayed.mailbox ? 16 : 16),
child: _mailboxNode.hasChildren()
? SvgPicture.asset(
_imagePaths.icFolderArrow,
width: 12,
height: 12,
color: selectMode == SelectMode.ACTIVE
? AppColor.mailboxSelectedIconColor
: AppColor.mailboxIconColor,
fit: BoxFit.fill)
? GestureDetector(
onTap: () {
if (_onExpandFolderActionClick != null) {
_onExpandFolderActionClick!(_mailboxNode);
}
},
child: SvgPicture.asset(
_mailboxNode.expandMode == ExpandMode.EXPAND ? _imagePaths.icExpandFolder : _imagePaths.icFolderArrow,
width: _mailboxNode.expandMode == ExpandMode.EXPAND ? 8 : 12,
height: _mailboxNode.expandMode == ExpandMode.EXPAND ? 8 : 12,
color: selectMode == SelectMode.ACTIVE
? AppColor.mailboxSelectedIconColor
: AppColor.mailboxIconColor,
fit: BoxFit.fill),
)
: SizedBox.shrink())
)
)
@@ -1,35 +1,33 @@
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:model/email/read_actions.dart';
class MarkAsMultipleEmailReadAllSuccess extends UIState {
final List<Either<Failure, Success>> resultList;
final int countMarkAsReadSuccess;
final ReadActions readActions;
MarkAsMultipleEmailReadAllSuccess(this.resultList, this.readActions);
MarkAsMultipleEmailReadAllSuccess(this.countMarkAsReadSuccess, this.readActions);
@override
List<Object?> get props => [resultList, readActions];
List<Object?> get props => [countMarkAsReadSuccess, readActions];
}
class MarkAsMultipleEmailReadAllFailure extends FeatureFailure {
final List<Either<Failure, Success>> resultList;
final ReadActions readActions;
MarkAsMultipleEmailReadAllFailure(this.resultList, this.readActions);
MarkAsMultipleEmailReadAllFailure(this.readActions);
@override
List<Object> get props => [resultList, readActions];
List<Object> get props => [readActions];
}
class MarkAsMultipleEmailReadHasSomeEmailFailure extends UIState {
final List<Either<Failure, Success>> resultList;
final int countMarkAsReadSuccess;
final ReadActions readActions;
MarkAsMultipleEmailReadHasSomeEmailFailure(this.resultList, this.readActions);
MarkAsMultipleEmailReadHasSomeEmailFailure(this.countMarkAsReadSuccess, this.readActions);
@override
List<Object> get props => [resultList, readActions];
List<Object> get props => [countMarkAsReadSuccess, readActions];
}
class MarkAsMultipleEmailReadFailure extends FeatureFailure {
@@ -0,0 +1,69 @@
import 'package:core/core.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
class MoveMultipleEmailToMailboxAllSuccess extends UIState {
final List<EmailId> movedListEmailId;
final MailboxId currentMailboxId;
final MailboxId destinationMailboxId;
final MoveAction moveAction;
final String? destinationPath;
MoveMultipleEmailToMailboxAllSuccess(
this.movedListEmailId,
this.currentMailboxId,
this.destinationMailboxId,
this.moveAction,
this.destinationPath);
@override
List<Object?> get props => [
movedListEmailId,
currentMailboxId,
destinationMailboxId,
moveAction,
destinationPath];
}
class MoveMultipleEmailToMailboxAllFailure extends FeatureFailure {
final MoveAction moveAction;
MoveMultipleEmailToMailboxAllFailure(this.moveAction);
@override
List<Object> get props => [moveAction];
}
class MoveMultipleEmailToMailboxHasSomeEmailFailure extends UIState {
final List<EmailId> movedListEmailId;
final MailboxId currentMailboxId;
final MailboxId destinationMailboxId;
final MoveAction moveAction;
final String? destinationPath;
MoveMultipleEmailToMailboxHasSomeEmailFailure(
this.movedListEmailId,
this.currentMailboxId,
this.destinationMailboxId,
this.moveAction,
this.destinationPath);
@override
List<Object?> get props => [
movedListEmailId,
currentMailboxId,
destinationMailboxId,
moveAction,
destinationPath];
}
class MoveMultipleEmailToMailboxFailure extends FeatureFailure {
final exception;
final MoveAction moveAction;
MoveMultipleEmailToMailboxFailure(this.exception, this.moveAction);
@override
List<Object> get props => [exception, moveAction];
}
@@ -2,40 +2,37 @@ import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/read_actions.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart';
class MarkAsMultipleEmailReadInteractor {
final MarkAsEmailReadInteractor markAsEmailReadInteractor;
final EmailRepository _emailRepository;
MarkAsMultipleEmailReadInteractor(this.markAsEmailReadInteractor);
MarkAsMultipleEmailReadInteractor(this._emailRepository);
Stream<Either<Failure, Success>> execute(
AccountId accountId,
List<EmailId> listEmailId,
List<Email> emails,
ReadActions readAction
) async* {
try {
final listResult = await Future.wait(listEmailId.map((emailId) async {
final result = await markAsEmailReadInteractor.execute(accountId, emailId, readAction).toList();
return result.first;
}));
if (listResult.length == 1) {
yield listResult.first;
yield Right<Failure, Success>(LoadingState());
final listEmailNeedMarkAsRead = emails
.where((email) => readAction == ReadActions.markAsUnread ? email.isReadEmail() : email.isUnReadEmail())
.toList();
final result = await _emailRepository.markAsRead(accountId, listEmailNeedMarkAsRead, readAction);
if (listEmailNeedMarkAsRead.length == result.length) {
final countMarkAsReadSuccess = emails.length;
yield Right(MarkAsMultipleEmailReadAllSuccess(countMarkAsReadSuccess, readAction));
} else if (result.isEmpty) {
yield Left(MarkAsMultipleEmailReadAllFailure(readAction));
} else {
var failedFileCount = 0;
listResult.forEach((element) {
if (element is Left) {
failedFileCount++;
}
});
if (failedFileCount == 0) {
yield Right(MarkAsMultipleEmailReadAllSuccess(listResult, readAction));
} else if (failedFileCount == listResult.length) {
yield Left(MarkAsMultipleEmailReadAllFailure(listResult, readAction));
}
yield Right(MarkAsMultipleEmailReadHasSomeEmailFailure(listResult, readAction));
final countMarkAsReadSuccess = emails.length - (listEmailNeedMarkAsRead.length - result.length);
yield Right(MarkAsMultipleEmailReadHasSomeEmailFailure(countMarkAsReadSuccess, readAction));
}
} catch (e) {
yield Left(MarkAsMultipleEmailReadFailure(e, readAction));
@@ -0,0 +1,42 @@
import 'dart:async';
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_request.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart';
class MoveMultipleEmailToMailboxInteractor {
final EmailRepository _emailRepository;
MoveMultipleEmailToMailboxInteractor(this._emailRepository);
Stream<Either<Failure, Success>> execute(AccountId accountId, MoveRequest moveRequest) async* {
try {
yield Right<Failure, Success>(LoadingState());
final result = await _emailRepository.moveToMailbox(accountId, moveRequest);
if (moveRequest.emailIds.length == result.length) {
yield Right(MoveMultipleEmailToMailboxAllSuccess(
result,
moveRequest.currentMailboxId,
moveRequest.destinationMailboxId,
moveRequest.moveAction,
moveRequest.destinationPath));
} else if (result.isEmpty) {
yield Left(MoveMultipleEmailToMailboxAllFailure(moveRequest.moveAction));
} else {
yield Right(MoveMultipleEmailToMailboxHasSomeEmailFailure(
result,
moveRequest.currentMailboxId,
moveRequest.destinationMailboxId,
moveRequest.moveAction,
moveRequest.destinationPath));
}
} catch (e) {
yield Left(MoveMultipleEmailToMailboxFailure(e, moveRequest.moveAction));
}
}
}
@@ -8,6 +8,7 @@ import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart';
import 'package:tmail_ui_user/features/thread/data/datasource_impl/thread_datasource_impl.dart';
import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart';
@@ -15,6 +16,7 @@ import 'package:tmail_ui_user/features/thread/data/repository/thread_repository_
import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart';
class ThreadBindings extends Bindings {
@@ -31,13 +33,16 @@ class ThreadBindings extends Bindings {
Get.lazyPut(() => EmailRepositoryImpl(Get.find<EmailDataSource>()));
Get.lazyPut<EmailRepository>(() => Get.find<EmailRepositoryImpl>());
Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MarkAsMultipleEmailReadInteractor(Get.find<MarkAsEmailReadInteractor>()));
Get.lazyPut(() => MarkAsMultipleEmailReadInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MoveToMailboxInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => MoveMultipleEmailToMailboxInteractor(Get.find<EmailRepository>()));
Get.put(ThreadController(
Get.find<ResponsiveUtils>(),
Get.find<GetEmailsInMailboxInteractor>(),
Get.find<ScrollController>(),
Get.find<MarkAsMultipleEmailReadInteractor>(),
Get.find<AppToast>(),
Get.find<MoveMultipleEmailToMailboxInteractor>(),
));
}
}
@@ -16,6 +16,7 @@ import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_request.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
@@ -23,8 +24,10 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_da
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/load_more_state.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
@@ -38,6 +41,7 @@ class ThreadController extends BaseController {
final AppToast _appToast;
final ResponsiveUtils responsiveUtils;
final ScrollController listEmailController;
final MoveMultipleEmailToMailboxInteractor _moveMultipleEmailToMailboxInteractor;
final _properties = Properties({
'id', 'subject', 'from', 'to', 'cc', 'bcc', 'keywords', 'receivedAt',
@@ -58,6 +62,7 @@ class ThreadController extends BaseController {
this.listEmailController,
this._markAsMultipleEmailReadInteractor,
this._appToast,
this._moveMultipleEmailToMailboxInteractor,
);
@override
@@ -73,20 +78,17 @@ class ThreadController extends BaseController {
if (_currentMailboxId != selectedMailbox?.id) {
_currentMailboxId = selectedMailbox?.id;
refreshGetAllEmailAction();
} else {
mailboxDashBoardController.viewState.value.map((success) {
if (success is MarkAsEmailReadSuccess ||
success is MarkAsMultipleEmailReadAllSuccess ||
success is MarkAsMultipleEmailReadHasSomeEmailFailure) {
_refreshListEmail();
}
});
}
});
mailboxDashBoardController.viewState.listen((state) {
state.map((success) {
if (success is MoveToMailboxSuccess) {
if (success is MarkAsEmailReadSuccess
|| success is MarkAsMultipleEmailReadAllSuccess
|| success is MarkAsMultipleEmailReadHasSomeEmailFailure) {
_refreshListEmail();
mailboxDashBoardController.clearState();
} else if (success is MoveToMailboxSuccess) {
_refreshListEmail();
mailboxDashBoardController.clearState();
}
@@ -108,19 +110,20 @@ class ThreadController extends BaseController {
(failure) {
if (failure is GetAllEmailFailure) {
_resetPositionCurrentAndLoadMoreState();
} else if (failure is MarkAsEmailReadFailure ||
failure is MarkAsMultipleEmailReadAllFailure ||
failure is MarkAsMultipleEmailReadFailure) {
} else if (failure is MarkAsMultipleEmailReadAllFailure
|| failure is MarkAsMultipleEmailReadFailure) {
_markAsSelectedEmailReadFailure(failure);
}
},
(success) {
if (success is GetAllEmailSuccess) {
_getAllEmailSuccess(success);
} else if (success is MarkAsEmailReadSuccess ||
success is MarkAsMultipleEmailReadAllSuccess ||
success is MarkAsMultipleEmailReadHasSomeEmailFailure) {
} else if (success is MarkAsMultipleEmailReadAllSuccess
|| success is MarkAsMultipleEmailReadHasSomeEmailFailure) {
_markAsSelectedEmailReadSuccess(success);
} else if (success is MoveMultipleEmailToMailboxAllSuccess
|| success is MoveMultipleEmailToMailboxHasSomeEmailFailure) {
_moveSelectedMultipleEmailToMailboxSuccess(success);
}
}
);
@@ -177,7 +180,8 @@ class ThreadController extends BaseController {
void refreshGetAllEmailAction() {
loadMoreState.value = LoadMoreState.IDLE;
positionCurrent = 0;
emailList.clear();
dispatchState(Right(LoadingState()));
emailList.value = <PresentationEmail>[];
final accountId = mailboxDashBoardController.accountId.value;
@@ -245,9 +249,11 @@ class ThreadController extends BaseController {
}
void _refreshListEmail() {
currentSelectMode.value = SelectMode.INACTIVE;
final newLimit = emailList.isNotEmpty ? UnsignedInt(emailList.length) : ThreadConstants.defaultLimit;
loadMoreState.value = LoadMoreState.IDLE;
emailList.clear();
dispatchState(Right(LoadingState()));
emailList.value = <PresentationEmail>[];
final accountId = mailboxDashBoardController.accountId.value;
@@ -262,61 +268,42 @@ class ThreadController extends BaseController {
}
}
void markAsSelectedEmailRead(List<PresentationEmail> listEmail, {bool fromContextMenuAction = false}) {
void markAsSelectedEmailRead(List<PresentationEmail> listPresentationEmail, {bool fromContextMenuAction = false}) {
if (fromContextMenuAction) {
popBack();
}
final readAction = isEmailAllRead(listEmail) ? ReadActions.markAsUnread : ReadActions.markAsRead;
final listEmailId = listEmail
.where((email) => readAction == ReadActions.markAsUnread ? email.isReadEmail() : email.isUnReadEmail())
.map((email) => email.id)
.toList();
final readAction = isEmailAllRead(listPresentationEmail) ? ReadActions.markAsUnread : ReadActions.markAsRead;
final accountId = mailboxDashBoardController.accountId.value;
final mailboxCurrent = mailboxDashBoardController.selectedMailbox.value;
if (accountId != null && mailboxCurrent != null) {
consumeState(_markAsMultipleEmailReadInteractor.execute(accountId, listEmailId, readAction));
final listEmail = listPresentationEmail.map((presentationEmail) => presentationEmail.toEmail()).toList();
consumeState(_markAsMultipleEmailReadInteractor.execute(accountId, listEmail, readAction));
}
}
void _markAsSelectedEmailReadSuccess(Success success) {
cancelSelectEmail();
List<EmailId> listEmailId = <EmailId>[];
ReadActions? readActions;
mailboxDashBoardController.dispatchState(Right(success));
if (success is MarkAsEmailReadSuccess) {
listEmailId.add(success.emailId);
readActions = success.readActions;
} else if (success is MarkAsMultipleEmailReadAllSuccess) {
success.resultList.forEach((either) {
either.map((success) {
if (success is MarkAsEmailReadSuccess) {
listEmailId.add(success.emailId);
}
});
});
ReadActions? readActions;
int countMarkAsReadSuccess = 0;
if (success is MarkAsMultipleEmailReadAllSuccess) {
readActions = success.readActions;
countMarkAsReadSuccess = success.countMarkAsReadSuccess;
} else if (success is MarkAsMultipleEmailReadHasSomeEmailFailure) {
success.resultList.forEach((either) {
either.map((success) {
if (success is MarkAsEmailReadSuccess) {
listEmailId.add(success.emailId);
}
});
});
readActions = success.readActions;
countMarkAsReadSuccess = success.countMarkAsReadSuccess;
}
if (Get.context != null && readActions != null) {
_appToast.showSuccessToast(readActions == ReadActions.markAsUnread
? AppLocalizations.of(Get.context!).marked_multiple_item_as_unread(listEmailId.length)
: AppLocalizations.of(Get.context!).marked_multiple_item_as_read(listEmailId.length));
? AppLocalizations.of(Get.context!).marked_multiple_item_as_unread(countMarkAsReadSuccess)
: AppLocalizations.of(Get.context!).marked_multiple_item_as_read(countMarkAsReadSuccess));
}
mailboxDashBoardController.dispatchState(Right(success));
}
void _markAsSelectedEmailReadFailure(Failure failure) {
@@ -330,17 +317,87 @@ class ThreadController extends BaseController {
.build();
}
void moveSelectedMultipleEmailToMailboxAction(List<PresentationEmail> listEmail) {
void moveSelectedMultipleEmailToMailboxAction(List<PresentationEmail> listEmail) async {
final currentMailbox = mailboxDashBoardController.selectedMailbox.value;
final accountId = mailboxDashBoardController.accountId.value;
if (currentMailbox != null && accountId != null) {
popBack();
final listEmailIds = listEmail.map((email) => email.id).toList();
push(
final destinationMailbox = await push(
AppRoutes.DESTINATION_PICKER,
arguments: DestinationPickerArguments(accountId, listEmailIds, currentMailbox)
);
if (destinationMailbox != null && destinationMailbox is PresentationMailbox) {
_moveSelectedEmailMultipleToMailbox(
accountId,
MoveRequest(
listEmailIds,
currentMailbox.id,
destinationMailbox.id,
MoveAction.moveTo,
destinationPath: destinationMailbox.mailboxPath));
}
}
}
void _moveSelectedEmailMultipleToMailbox(AccountId accountId, MoveRequest moveRequest) {
consumeState(_moveMultipleEmailToMailboxInteractor.execute(accountId, moveRequest));
}
void _moveSelectedMultipleEmailToMailboxSuccess(Success success) {
mailboxDashBoardController.dispatchState(Right(success));
String? destinationPath;
List<EmailId> movedEmailIds = [];
MailboxId? currentMailboxId;
MailboxId? destinationMailboxId;
MoveAction? moveAction;
if (success is MoveMultipleEmailToMailboxAllSuccess) {
destinationPath = success.destinationPath;
movedEmailIds = success.movedListEmailId;
currentMailboxId = success.currentMailboxId;
destinationMailboxId = success.destinationMailboxId;
moveAction = success.moveAction;
} else if (success is MoveMultipleEmailToMailboxHasSomeEmailFailure) {
destinationPath = success.destinationPath;
movedEmailIds = success.movedListEmailId;
currentMailboxId = success.currentMailboxId;
destinationMailboxId = success.destinationMailboxId;
moveAction = success.moveAction;
}
if (Get.context != null && Get.overlayContext != null
&& destinationPath != null && moveAction == MoveAction.moveTo) {
_appToast.showToastWithAction(
Get.overlayContext!,
AppLocalizations.of(Get.context!).moved_to_mailbox(destinationPath),
AppLocalizations.of(Get.context!).undo_action,
() {
final newCurrentMailboxId = destinationMailboxId;
final newDestinationMailboxId = currentMailboxId;
if (newCurrentMailboxId != null && newDestinationMailboxId != null) {
_undoMoveSelectedMultipleEmailToMailbox(MoveRequest(
movedEmailIds,
newCurrentMailboxId,
newDestinationMailboxId,
MoveAction.undo,
destinationPath: destinationPath));
}
}
);
}
_refreshListEmail();
}
void _undoMoveSelectedMultipleEmailToMailbox(MoveRequest moveRequest) {
final accountId = mailboxDashBoardController.accountId.value;
if (accountId != null) {
_moveSelectedEmailMultipleToMailbox(accountId, moveRequest);
}
}
+55 -118
View File
@@ -106,73 +106,14 @@ class ThreadView extends GetWidget<ThreadController> {
.build();
}
Widget _moveToTrashAction(BuildContext context, List<PresentationEmail> listEmail) {
return (EmailContextMenuActionBuilder(
Key('move_to_trash_context_menu_action'),
SvgPicture.asset(imagePaths.icTrash, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).move_to_trash, listEmail)
..onActionClick((data) => {}))
.build();
}
Widget _moveToMailboxAction(BuildContext context, List<PresentationEmail> listEmail) {
return (EmailContextMenuActionBuilder(
Key('move_to_mailbox_context_menu_action'),
SvgPicture.asset(imagePaths.icFolder, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).move_to_mailbox, listEmail)
..onActionClick((data) => {}))
.build();
}
Widget _markAsFlagAction(BuildContext context, List<PresentationEmail> listEmail) {
return (EmailContextMenuActionBuilder(
Key('mark_as_flag_context_menu_action'),
SvgPicture.asset(imagePaths.icFlag, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).mark_as_flag, listEmail)
..onActionClick((data) => {}))
.build();
}
Widget _moveToSpamAction(BuildContext context, List<PresentationEmail> listEmail) {
return (EmailContextMenuActionBuilder(
Key('move_to_spam_context_menu_action'),
SvgPicture.asset(imagePaths.icMailboxSpam, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).move_to_spam, listEmail)
..onActionClick((data) => {}))
.build();
}
List<Widget> _contextMenuActionList(BuildContext context, List<PresentationEmail> listEmail) {
return [
_moveToTrashAction(context, listEmail),
_moveToMailboxAction(context, listEmail),
_markAsReadAction(context, listEmail),
_markAsFlagAction(context, listEmail),
_moveToSpamAction(context, listEmail),
SizedBox(height: 30),
];
}
Widget _markAsReadAction(BuildContext context, List<PresentationEmail> listEmail) {
return (EmailContextMenuActionBuilder(
Key('mark_as_read_context_menu_action'),
SvgPicture.asset(imagePaths.icEyeDisable, width: 24, height: 24, fit: BoxFit.fill),
controller.isEmailAllRead(listEmail)
? AppLocalizations.of(context).mark_as_unread
: AppLocalizations.of(context).mark_as_read,
listEmail)
..onActionClick((data) => controller.markAsSelectedEmailRead(data, fromContextMenuAction: true)))
.build();
}
Widget _moveToTrashAction(BuildContext context, List<PresentationEmail> listEmail) {
return (EmailContextMenuActionBuilder(
Key('move_to_trash_context_menu_action'),
SvgPicture.asset(imagePaths.icTrash, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).move_to_trash,
listEmail)
..onActionClick((data) => {}))
.build();
..onActionClick((data) => {}))
.build();
}
Widget _moveToMailboxAction(BuildContext context, List<PresentationEmail> listEmail) {
@@ -181,8 +122,8 @@ class ThreadView extends GetWidget<ThreadController> {
SvgPicture.asset(imagePaths.icFolder, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).move_to_mailbox,
listEmail)
..onActionClick((emails) => controller.moveSelectedMultipleEmailToMailboxAction(emails)))
.build();
..onActionClick((data) => controller.moveSelectedMultipleEmailToMailboxAction(data)))
.build();
}
Widget _markAsFlagAction(BuildContext context, List<PresentationEmail> listEmail) {
@@ -191,8 +132,8 @@ class ThreadView extends GetWidget<ThreadController> {
SvgPicture.asset(imagePaths.icFlag, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).mark_as_flag,
listEmail)
..onActionClick((data) => {}))
.build();
..onActionClick((data) => {}))
.build();
}
Widget _moveToSpamAction(BuildContext context, List<PresentationEmail> listEmail) {
@@ -201,8 +142,8 @@ class ThreadView extends GetWidget<ThreadController> {
SvgPicture.asset(imagePaths.icMailboxSpam, width: 24, height: 24, fit: BoxFit.fill),
AppLocalizations.of(context).move_to_spam,
listEmail)
..onActionClick((data) => {}))
.build();
..onActionClick((data) => {}))
.build();
}
Widget _buildLoadingView() {
@@ -238,63 +179,59 @@ class ThreadView extends GetWidget<ThreadController> {
padding: EdgeInsets.zero,
color: responsiveUtils.isMobile(context) ? AppColor.bgMailboxListMail : Colors.white,
child: Obx(() => controller.currentSelectMode.value == SelectMode.INACTIVE
? RefreshIndicator(
color: AppColor.primaryColor,
onRefresh: () async => controller.refreshGetAllEmailAction(),
child: _buildListEmailBody(context))
: _buildListEmailBody(context)));
? controller.emailList.isNotEmpty
? RefreshIndicator(
color: AppColor.primaryColor,
onRefresh: () async => controller.refreshGetAllEmailAction(),
child: _buildListEmailBody(context, controller.emailList))
: RefreshIndicator(
color: AppColor.primaryColor,
onRefresh: () async => controller.refreshGetAllEmailAction(),
child: _buildEmptyEmail(context))
: controller.emailList.isNotEmpty
? _buildListEmailBody(context, controller.emailList)
: _buildEmptyEmail(context)));
}
Widget _buildListEmailBody(BuildContext context) {
return controller.emailList.isNotEmpty
? _buildListEmailSuccess(context, controller.emailList)
: _buildEmptyEmail(context);
}
Widget _buildListEmailSuccess(BuildContext context, List<PresentationEmail> listPresentationEmail) {
if (listPresentationEmail.isEmpty) {
return _buildEmptyEmail(context);
} else {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (controller.loadMoreState.value == LoadMoreState.IDLE
&& scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
controller.loadMoreEmailAction();
return true;
}
return false;
},
child: ListView.builder(
controller: controller.listEmailController,
padding: EdgeInsets.only(top: 16),
key: Key('presentation_email_list'),
itemCount: listPresentationEmail.length,
itemBuilder: (context, index) =>
Obx(() => (EmailTileBuilder(
context,
imagePaths,
controller.getSelectMode(listPresentationEmail[index], controller.mailboxDashBoardController.selectedEmail.value),
listPresentationEmail[index],
responsiveUtils,
controller.currentSelectMode.value)
..onOpenEmailAction((selectedEmail) => controller.previewEmail(context, selectedEmail))
..onSelectEmailAction((selectedEmail) => controller.selectEmail(context, selectedEmail)))
.build()),
));
}
Widget _buildListEmailBody(BuildContext context, List<PresentationEmail> listPresentationEmail) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo is ScrollEndNotification
&& controller.loadMoreState.value == LoadMoreState.IDLE
&& scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
controller.loadMoreEmailAction();
}
return false;
},
child: ListView.builder(
controller: controller.listEmailController,
physics: AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 16),
key: Key('presentation_email_list'),
itemCount: listPresentationEmail.length,
itemBuilder: (context, index) => Obx(() => (EmailTileBuilder(
context,
imagePaths,
controller.getSelectMode(listPresentationEmail[index], controller.mailboxDashBoardController.selectedEmail.value),
listPresentationEmail[index],
responsiveUtils,
controller.currentSelectMode.value)
..onOpenEmailAction((selectedEmail) => controller.previewEmail(context, selectedEmail))
..onSelectEmailAction((selectedEmail) => controller.selectEmail(context, selectedEmail)))
.build()),
));
}
Widget _buildEmptyEmail(BuildContext context) {
return Obx(() => controller.viewState.value.fold(
(failure) => Text(
AppLocalizations.of(context).no_emails,
maxLines: 1,
style: TextStyle(fontSize: 25, color: AppColor.mailboxTextColor, fontWeight: FontWeight.bold)),
(success) => success is LoadingState
? SizedBox.shrink()
: Text(
AppLocalizations.of(context).no_emails,
maxLines: 1,
style: TextStyle(fontSize: 25, color: AppColor.mailboxTextColor, fontWeight: FontWeight.bold)))
(failure) => SizedBox.shrink(),
(success) => !(success is LoadingState)
? (BackgroundWidgetBuilder(context)
..key(Key('empty_email_background'))
..image(SvgPicture.asset(imagePaths.icEmptyImageDefault, width: 120, height: 120, fit: BoxFit.fill))
..text(AppLocalizations.of(context).no_emails))
.build()
: SizedBox.shrink())
);
}
}