From 36e573db23cf4d8700eb721e6524262b046a5599 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 9 Jan 2026 09:32:31 +0700 Subject: [PATCH] TF-4236 Add repository/interactor for remove a label from an email --- .../data/datasource/email_datasource.dart | 7 +++ .../email_datasource_impl.dart | 17 ++++++++ .../email_hive_cache_datasource_impl.dart | 5 +++ .../email_local_storage_datasource_impl.dart | 5 +++ ...email_session_storage_datasource_impl.dart | 5 +++ .../email/data/network/email_api.dart | 30 +++++++++++++ .../repository/email_repository_impl.dart | 15 +++++++ .../domain/repository/email_repository.dart | 7 +++ .../remove_a_label_from_an_email_state.dart | 29 +++++++++++++ ...move_a_label_from_an_email_interactor.dart | 43 +++++++++++++++++++ 10 files changed, 163 insertions(+) create mode 100644 lib/features/email/domain/state/remove_a_label_from_an_email_state.dart create mode 100644 lib/features/email/domain/usecases/remove_a_label_from_an_email_interactor.dart diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index 4391b1227..b10cb1586 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -218,4 +218,11 @@ abstract class EmailDataSource { List emailIds, KeyWordIdentifier labelKeyword, ); + + Future removeLabelFromEmail( + Session session, + AccountId accountId, + EmailId emailId, + 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 27d275e68..67748fb7b 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -587,4 +587,21 @@ class EmailDataSourceImpl extends EmailDataSource { ); }).catchError(_exceptionThrower.throwException); } + + @override + Future removeLabelFromEmail( + Session session, + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ) { + return Future.sync(() async { + return await emailAPI.removeLabelFromEmail( + session, + accountId, + emailId, + 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 1538da3f1..9d62611ed 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 @@ -585,4 +585,9 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { Future addLabelToThread(Session session, AccountId accountId, List emailIds, KeyWordIdentifier labelKeyword) { throw UnimplementedError(); } + + @override + Future removeLabelFromEmail(Session session, AccountId accountId, EmailId emailId, 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 98148bc90..a2217cef7 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 @@ -361,4 +361,9 @@ class EmailLocalStorageDataSourceImpl extends EmailDataSource { Future addLabelToThread(Session session, AccountId accountId, List emailIds, KeyWordIdentifier labelKeyword) { throw UnimplementedError(); } + + @override + Future removeLabelFromEmail(Session session, AccountId accountId, EmailId emailId, 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 187255972..9393ca526 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 @@ -275,4 +275,9 @@ class EmailSessionStorageDatasourceImpl extends EmailDataSource { Future addLabelToThread(Session session, AccountId accountId, List emailIds, KeyWordIdentifier labelKeyword) { throw UnimplementedError(); } + + @override + Future removeLabelFromEmail(Session session, AccountId accountId, EmailId emailId, 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 09f8089fe..3323265af 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -996,4 +996,34 @@ class EmailAPI with HandleSetErrorMixin, MailAPIMixin { } } } + + Future removeLabelFromEmail( + Session session, + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ) async { + final method = SetEmailMethod(accountId) + ..addUpdates({ + emailId.id: labelKeyword.generateLabelActionPath(remove: true), + }); + + 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 ?? []; + + if (emailIdsUpdated.isEmpty || !emailIdsUpdated.contains(emailId.id)) { + throw parseErrorForSetResponse(response, emailId.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 a03fba5bf..85aa09324 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -510,4 +510,19 @@ class EmailRepositoryImpl extends EmailRepository { labelKeyword, ); } + + @override + Future removeLabelFromEmail( + Session session, + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ) { + return emailDataSource[DataSourceType.network]!.removeLabelFromEmail( + session, + accountId, + emailId, + 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 0fad744e9..ca2fc0aa4 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -175,4 +175,11 @@ abstract class EmailRepository { List emailIds, KeyWordIdentifier labelKeyword, ); + + Future removeLabelFromEmail( + Session session, + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ); } \ No newline at end of file diff --git a/lib/features/email/domain/state/remove_a_label_from_an_email_state.dart b/lib/features/email/domain/state/remove_a_label_from_an_email_state.dart new file mode 100644 index 000000000..30af11409 --- /dev/null +++ b/lib/features/email/domain/state/remove_a_label_from_an_email_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 RemovingALabelFromAnEmail extends LoadingState {} + +class RemoveALabelFromAnEmailSuccess extends UIState { + final EmailId emailId; + final KeyWordIdentifier labelKeyword; + final String labelDisplay; + + RemoveALabelFromAnEmailSuccess(this.emailId, this.labelKeyword, this.labelDisplay); + + @override + List get props => [emailId, labelKeyword, labelDisplay]; +} + +class RemoveALabelFromAnEmailFailure extends FeatureFailure { + final String labelDisplay; + + RemoveALabelFromAnEmailFailure({ + dynamic exception, + required this.labelDisplay, + }) : super(exception: exception); + + @override + List get props => [...super.props, labelDisplay]; +} diff --git a/lib/features/email/domain/usecases/remove_a_label_from_an_email_interactor.dart b/lib/features/email/domain/usecases/remove_a_label_from_an_email_interactor.dart new file mode 100644 index 000000000..07c9cd063 --- /dev/null +++ b/lib/features/email/domain/usecases/remove_a_label_from_an_email_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/remove_a_label_from_an_email_state.dart'; + +class RemoveALabelFromAnEmailInteractor { + final EmailRepository _emailRepository; + + RemoveALabelFromAnEmailInteractor(this._emailRepository); + + Stream> execute( + Session session, + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + String labelDisplay, + ) async* { + try { + yield Right(RemovingALabelFromAnEmail()); + await _emailRepository.removeLabelFromEmail( + session, + accountId, + emailId, + labelKeyword, + ); + yield Right(RemoveALabelFromAnEmailSuccess( + emailId, + labelKeyword, + labelDisplay, + )); + } catch (e) { + yield Left(RemoveALabelFromAnEmailFailure( + exception: e, + labelDisplay: labelDisplay, + )); + } + } +}