diff --git a/lib/features/base/widget/popup_menu/popup_submenu_controller.dart b/lib/features/base/widget/popup_menu/popup_submenu_controller.dart index f22df85c4..bbb9fde91 100644 --- a/lib/features/base/widget/popup_menu/popup_submenu_controller.dart +++ b/lib/features/base/widget/popup_menu/popup_submenu_controller.dart @@ -68,9 +68,9 @@ class PopupSubmenuController { shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(6)), ), - child: SizedBox( + child: Container( width: submenuWidth, - height: finalHeight, + constraints: BoxConstraints(maxHeight: finalHeight), child: submenu, ), ), diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index c305a3318..4391b1227 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -211,4 +211,11 @@ abstract class EmailDataSource { EmailId emailId, KeyWordIdentifier labelKeyword, ); + + Future addLabelToThread( + Session session, + AccountId accountId, + List emailIds, + KeyWordIdentifier labelKeyword, + ); } \ 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 17c9f5a5e..27d275e68 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -570,4 +570,21 @@ class EmailDataSourceImpl extends EmailDataSource { ); }).catchError(_exceptionThrower.throwException); } + + @override + Future addLabelToThread( + Session session, + AccountId accountId, + List emailIds, + KeyWordIdentifier labelKeyword, + ) { + return Future.sync(() async { + return await emailAPI.addLabelToThread( + session, + accountId, + emailIds, + labelKeyword, + ); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart index 1ffdecac2..1538da3f1 100644 --- a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart @@ -580,4 +580,9 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { Future addLabelToEmail(Session session, AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) { throw UnimplementedError(); } + + @override + Future addLabelToThread(Session session, AccountId accountId, List emailIds, KeyWordIdentifier labelKeyword) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart index 2038731d3..98148bc90 100644 --- a/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_local_storage_datasource_impl.dart @@ -356,4 +356,9 @@ class EmailLocalStorageDataSourceImpl extends EmailDataSource { Future addLabelToEmail(Session session, AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) { throw UnimplementedError(); } + + @override + Future addLabelToThread(Session session, AccountId accountId, List emailIds, KeyWordIdentifier labelKeyword) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart index 396f4ed76..187255972 100644 --- a/lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_session_storage_datasource_impl.dart @@ -270,4 +270,9 @@ class EmailSessionStorageDatasourceImpl extends EmailDataSource { Future addLabelToEmail(Session session, AccountId accountId, EmailId emailId, KeyWordIdentifier labelKeyword) { throw UnimplementedError(); } + + @override + Future addLabelToThread(Session session, AccountId accountId, List emailIds, KeyWordIdentifier labelKeyword) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 4c0968ed5..09f8089fe 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -964,4 +964,36 @@ class EmailAPI with HandleSetErrorMixin, MailAPIMixin { throw parseErrorForSetResponse(response, emailId.id); } } + + Future addLabelToThread( + Session session, + AccountId accountId, + List emailIds, + KeyWordIdentifier labelKeyword, + ) async { + final method = SetEmailMethod(accountId) + ..addUpdates(emailIds.generateMapUpdateObjectLabel(labelKeyword)); + + final builder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + final invocation = builder.invocation(method); + + final capabilities = method.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + final result = await (builder..usings(capabilities)).build().execute(); + + final response = result.parse( + invocation.methodCallId, + SetEmailResponse.deserialize, + ); + + final emailIdsUpdated = response?.updated?.keys ?? []; + final ids = emailIds.map((emailId) => emailId.id); + final isUpdated = emailIdsUpdated.every(ids.contains); + + if (emailIdsUpdated.isEmpty || !isUpdated) { + for (var id in emailIds) { + throw parseErrorForSetResponse(response, id.id); + } + } + } } \ No newline at end of file diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 66ce29367..a03fba5bf 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -495,4 +495,19 @@ class EmailRepositoryImpl extends EmailRepository { labelKeyword, ); } + + @override + Future addLabelToThread( + Session session, + AccountId accountId, + List emailIds, + KeyWordIdentifier labelKeyword, + ) { + return emailDataSource[DataSourceType.network]!.addLabelToThread( + session, + accountId, + emailIds, + labelKeyword, + ); + } } \ No newline at end of file diff --git a/lib/features/email/domain/repository/email_repository.dart b/lib/features/email/domain/repository/email_repository.dart index 552dcb81d..0fad744e9 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -168,4 +168,11 @@ abstract class EmailRepository { EmailId emailId, KeyWordIdentifier labelKeyword, ); + + Future addLabelToThread( + Session session, + AccountId accountId, + List emailIds, + KeyWordIdentifier labelKeyword, + ); } \ No newline at end of file diff --git a/lib/features/email/domain/state/add_a_label_to_an_thread_state.dart b/lib/features/email/domain/state/add_a_label_to_an_thread_state.dart new file mode 100644 index 000000000..0f3a0ad4d --- /dev/null +++ b/lib/features/email/domain/state/add_a_label_to_an_thread_state.dart @@ -0,0 +1,29 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; + +class AddingALabelToAThread extends LoadingState {} + +class AddALabelToAThreadSuccess extends UIState { + final List emailIds; + final KeyWordIdentifier labelKeyword; + final String labelDisplay; + + AddALabelToAThreadSuccess(this.emailIds, this.labelKeyword, this.labelDisplay); + + @override + List get props => [emailIds, labelKeyword, labelDisplay]; +} + +class AddALabelToAThreadFailure extends FeatureFailure { + final String labelDisplay; + + AddALabelToAThreadFailure({ + dynamic exception, + required this.labelDisplay, + }) : super(exception: exception); + + @override + List get props => [...super.props, labelDisplay]; +} diff --git a/lib/features/email/domain/usecases/add_a_label_to_a_thread_interactor.dart b/lib/features/email/domain/usecases/add_a_label_to_a_thread_interactor.dart new file mode 100644 index 000000000..d43fcb31e --- /dev/null +++ b/lib/features/email/domain/usecases/add_a_label_to_a_thread_interactor.dart @@ -0,0 +1,43 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/email/domain/state/add_a_label_to_an_thread_state.dart'; + +class AddALabelToAThreadInteractor { + final EmailRepository _emailRepository; + + AddALabelToAThreadInteractor(this._emailRepository); + + Stream> execute( + Session session, + AccountId accountId, + List emailIds, + KeyWordIdentifier labelKeyword, + String labelDisplay, + ) async* { + try { + yield Right(AddingALabelToAThread()); + await _emailRepository.addLabelToThread( + session, + accountId, + emailIds, + labelKeyword, + ); + yield Right(AddALabelToAThreadSuccess( + emailIds, + labelKeyword, + labelDisplay, + )); + } catch (e) { + yield Left(AddALabelToAThreadFailure( + exception: e, + labelDisplay: labelDisplay, + )); + } + } +} diff --git a/lib/features/email/presentation/extensions/handle_label_for_email_extension.dart b/lib/features/email/presentation/extensions/handle_label_for_email_extension.dart index 5caf995d3..40d632b18 100644 --- a/lib/features/email/presentation/extensions/handle_label_for_email_extension.dart +++ b/lib/features/email/presentation/extensions/handle_label_for_email_extension.dart @@ -1,6 +1,5 @@ import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; -import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; @@ -15,6 +14,7 @@ import 'package:tmail_ui_user/features/email/presentation/extensions/presentatio import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/labels/domain/exceptions/label_exceptions.dart'; import 'package:tmail_ui_user/features/labels/presentation/widgets/add_label_to_email_modal.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/labels/check_label_available_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/map_keywords_extension.dart'; import 'package:tmail_ui_user/features/thread/domain/extensions/presentation_email_map_extension.dart'; @@ -23,8 +23,7 @@ import 'package:tmail_ui_user/main/routes/dialog_router.dart'; extension HandleLabelForEmailExtension on SingleEmailController { bool get isLabelFeatureEnabled { - return mailboxDashBoardController.isLabelCapabilitySupported && - mailboxDashBoardController.labelController.isLabelSettingEnabled.isTrue; + return mailboxDashBoardController.isLabelFeatureEnabled; } void toggleLabelToEmail(EmailId emailId, Label label, bool isSelected) { @@ -180,8 +179,12 @@ extension HandleLabelForEmailExtension on SingleEmailController { child: AddLabelToEmailModal( labels: labels, emailLabels: emailLabels, - emailId: emailId, - onAddLabelToEmailCallback: toggleLabelToEmail, + emailIds: [emailId], + onAddLabelToEmailsCallback: (emailIds, label, isSelected) { + if (emailIds.length == 1) { + toggleLabelToEmail(emailIds.first, label, isSelected); + } + }, ), dialogLabel: 'add-label-to-email-modal', ); diff --git a/lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart b/lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart index 37ea76b2a..24b05b4d3 100644 --- a/lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart +++ b/lib/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart @@ -45,6 +45,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_i import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; +import 'package:tmail_ui_user/features/email/presentation/extensions/presentation_email_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart'; @@ -588,7 +589,6 @@ class EmailActionReactor { ); }).toList(); - final popupMenuWidget = PopupMenuActionGroupWidget( actions: popupMenuItemEmailActions, submenuController: submenuController, @@ -623,9 +623,12 @@ class EmailActionReactor { OnSelectLabelAction? onSelectLabelAction, }) { if (actionType == EmailActionType.labelAs && labels?.isNotEmpty == true) { + final listLabels = labels ?? []; + final emailLabels = presentationEmail.getLabelList(listLabels); + return LabelListContextMenu( - labelList: labels ?? [], - presentationEmail: presentationEmail, + labelList: listLabels, + emailLabels: emailLabels, imagePaths: imagePaths, onSelectLabelAction: (label, isSelected) => onSelectLabelAction?.call(label, isSelected), diff --git a/lib/features/labels/presentation/widgets/add_label_to_email_modal.dart b/lib/features/labels/presentation/widgets/add_label_to_email_modal.dart index 40a884df8..f882fce9a 100644 --- a/lib/features/labels/presentation/widgets/add_label_to_email_modal.dart +++ b/lib/features/labels/presentation/widgets/add_label_to_email_modal.dart @@ -12,8 +12,8 @@ import 'package:tmail_ui_user/features/labels/presentation/widgets/label_item_co import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; -typedef OnAddLabelToEmailCallback = void Function( - EmailId emailId, +typedef OnAddLabelToEmailsCallback = void Function( + List emailIds, Label label, bool isSelected, ); @@ -21,15 +21,15 @@ typedef OnAddLabelToEmailCallback = void Function( class AddLabelToEmailModal extends StatefulWidget { final List