diff --git a/labels/lib/converter/keyword_identifier_nullable_converter.dart b/labels/lib/converter/keyword_identifier_nullable_converter.dart new file mode 100644 index 000000000..7c86f9f6b --- /dev/null +++ b/labels/lib/converter/keyword_identifier_nullable_converter.dart @@ -0,0 +1,14 @@ +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; +import 'package:json_annotation/json_annotation.dart'; + +class KeywordIdentifierNullableConverter + implements JsonConverter { + const KeywordIdentifierNullableConverter(); + + @override + KeyWordIdentifier? fromJson(String? json) => + json != null ? KeyWordIdentifier(json) : null; + + @override + String? toJson(KeyWordIdentifier? object) => object?.value; +} diff --git a/labels/lib/extensions/label_extension.dart b/labels/lib/extensions/label_extension.dart index 91becde41..49e3c4a7c 100644 --- a/labels/lib/extensions/label_extension.dart +++ b/labels/lib/extensions/label_extension.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/hex_color_extension.dart'; import 'package:flutter/material.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:labels/model/hex_color.dart'; import 'package:labels/model/label.dart'; @@ -28,7 +29,7 @@ extension LabelExtension on Label { Label copyWith({ Id? id, - String? keyword, + KeyWordIdentifier? keyword, String? displayName, HexColor? color, }) { diff --git a/labels/lib/model/label.dart b/labels/lib/model/label.dart index 3189cdf19..6eed79996 100644 --- a/labels/lib/model/label.dart +++ b/labels/lib/model/label.dart @@ -1,8 +1,10 @@ import 'package:equatable/equatable.dart'; import 'package:jmap_dart_client/http/converter/id_converter.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:labels/converter/hex_color_nullable_converter.dart'; +import 'package:labels/converter/keyword_identifier_nullable_converter.dart'; import 'package:labels/model/hex_color.dart'; part 'label.g.dart'; @@ -13,11 +15,12 @@ part 'label.g.dart'; converters: [ IdConverter(), HexColorNullableConverter(), + KeywordIdentifierNullableConverter(), ], ) class Label with EquatableMixin { final Id? id; - final String? keyword; + final KeyWordIdentifier? keyword; final String? displayName; final HexColor? color; diff --git a/labels/test/method/get/get_label_method_test.dart b/labels/test/method/get/get_label_method_test.dart index c9f36b603..7277de527 100644 --- a/labels/test/method/get/get_label_method_test.dart +++ b/labels/test/method/get/get_label_method_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http_mock_adapter/http_mock_adapter.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:labels/labels.dart'; import '../method_fixtures.dart'; @@ -41,13 +42,13 @@ void main() { final labelA = Label( id: Id('A'), - keyword: 'labelA', + keyword: KeyWordIdentifier('labelA'), displayName: 'Label A', color: HexColor('#111111'), ); final labelB = Label( id: Id('B'), - keyword: 'labelB', + keyword: KeyWordIdentifier('labelB'), displayName: 'Label B', color: HexColor('#222222'), ); diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index dec9c104c..a4544dac7 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -11,6 +11,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/account/account_request.dart'; import 'package:model/email/attachment.dart'; import 'package:model/email/mark_star_action.dart'; @@ -203,4 +204,10 @@ abstract class EmailDataSource { ); Future generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest); + + Future addLabelToEmail( + 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 c7b75fb91..a67d2da51 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -22,6 +22,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; @@ -552,4 +553,15 @@ class EmailDataSourceImpl extends EmailDataSource { Future generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest) { throw UnimplementedError(); } + + @override + Future addLabelToEmail( + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ) { + return Future.sync(() async { + return await emailAPI.addLabelToEmail(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 4297ac76d..8f1ef68ea 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 @@ -575,4 +575,9 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { Future generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest) { throw UnimplementedError(); } + + @override + Future addLabelToEmail(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 e32b5abc1..cf2608816 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 @@ -21,6 +21,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/account/account_request.dart'; import 'package:model/email/attachment.dart'; import 'package:model/email/mark_star_action.dart'; @@ -350,4 +351,9 @@ class EmailLocalStorageDataSourceImpl extends EmailDataSource { }) { throw UnimplementedError(); } + + @override + Future addLabelToEmail(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 1c6030111..6b30fd585 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 @@ -13,6 +13,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/account/account_request.dart'; import 'package:model/email/attachment.dart'; import 'package:model/email/mark_star_action.dart'; @@ -264,4 +265,9 @@ class EmailSessionStorageDatasourceImpl extends EmailDataSource { }) { throw UnimplementedError(); } + + @override + Future addLabelToEmail(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 d55063308..5589149b9 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -936,4 +936,30 @@ class EmailAPI with HandleSetErrorMixin, MailAPIMixin { throw NotParsableBlobIdToEmailException(); } } + + Future addLabelToEmail( + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ) async { + final method = SetEmailMethod(accountId) + ..addUpdates({emailId.id: labelKeyword.generateLabelActionPath()}); + + final builder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + final invocation = builder.invocation(method); + + final result = + await (builder..usings(method.requiredCapabilities)).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 dd6691d35..c4ba82573 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -13,6 +13,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/email/email_content.dart'; import 'package:model/email/mark_star_action.dart'; import 'package:model/email/read_actions.dart'; @@ -479,4 +480,17 @@ class EmailRepositoryImpl extends EmailRepository { Future generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest) { return emailDataSource[DataSourceType.local]!.generateEntireMessageAsDocument(entireMessageRequest); } + + @override + Future addLabelToEmail( + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ) { + return emailDataSource[DataSourceType.network]!.addLabelToEmail( + 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 2407cdb1c..ee8ab592e 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -11,6 +11,7 @@ import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/email/email_content.dart'; import 'package:model/email/mark_star_action.dart'; import 'package:model/email/read_actions.dart'; @@ -160,4 +161,10 @@ abstract class EmailRepository { Future printEmail(EmailPrint emailPrint); Future generateEntireMessageAsDocument(ViewEntireMessageRequest entireMessageRequest); + + Future addLabelToEmail( + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + ); } \ No newline at end of file diff --git a/lib/features/email/domain/state/add_a_label_to_an_email_state.dart b/lib/features/email/domain/state/add_a_label_to_an_email_state.dart new file mode 100644 index 000000000..6ca6df682 --- /dev/null +++ b/lib/features/email/domain/state/add_a_label_to_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 AddingALabelToAnEmail extends LoadingState {} + +class AddALabelToAnEmailSuccess extends UIState { + final EmailId emailId; + final KeyWordIdentifier labelKeyword; + final String labelDisplay; + + AddALabelToAnEmailSuccess(this.emailId, this.labelKeyword, this.labelDisplay); + + @override + List get props => [emailId, labelKeyword, labelDisplay]; +} + +class AddALabelToAnEmailFailure extends FeatureFailure { + final String labelDisplay; + + AddALabelToAnEmailFailure({ + 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_an_email_interactor.dart b/lib/features/email/domain/usecases/add_a_label_to_an_email_interactor.dart new file mode 100644 index 000000000..a583013a8 --- /dev/null +++ b/lib/features/email/domain/usecases/add_a_label_to_an_email_interactor.dart @@ -0,0 +1,40 @@ +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/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_email_state.dart'; + +class AddALabelToAnEmailInteractor { + final EmailRepository _emailRepository; + + AddALabelToAnEmailInteractor(this._emailRepository); + + Stream> execute( + AccountId accountId, + EmailId emailId, + KeyWordIdentifier labelKeyword, + String labelDisplay, + ) async* { + try { + yield Right(AddingALabelToAnEmail()); + await _emailRepository.addLabelToEmail( + accountId, + emailId, + labelKeyword, + ); + yield Right(AddALabelToAnEmailSuccess( + emailId, + labelKeyword, + labelDisplay, + )); + } catch (e) { + yield Left(AddALabelToAnEmailFailure( + exception: e, + labelDisplay: labelDisplay, + )); + } + } +} diff --git a/lib/features/email/presentation/bindings/email_interactor_bindings.dart b/lib/features/email/presentation/bindings/email_interactor_bindings.dart index e689a5e74..6c4e2d1ba 100644 --- a/lib/features/email/presentation/bindings/email_interactor_bindings.dart +++ b/lib/features/email/presentation/bindings/email_interactor_bindings.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; 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/add_a_label_to_an_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_entire_message_as_document_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_stored_email_state_interactor.dart'; @@ -117,6 +118,9 @@ class EmailInteractorBindings extends InteractorsBindings { Get.find(), )); } + Get.lazyPut( + () => AddALabelToAnEmailInteractor(Get.find()), + ); } @override diff --git a/lib/features/email/presentation/extensions/presentation_email_extension.dart b/lib/features/email/presentation/extensions/presentation_email_extension.dart index 7204f03b8..240adfaf1 100644 --- a/lib/features/email/presentation/extensions/presentation_email_extension.dart +++ b/lib/features/email/presentation/extensions/presentation_email_extension.dart @@ -4,7 +4,6 @@ import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/list_email_address_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; -import 'package:tmail_ui_user/features/thread/data/extensions/list_keyword_identifier_extension.dart'; import 'package:tmail_ui_user/features/thread/data/extensions/map_keywords_extension.dart'; extension PresentationEmailExtension on PresentationEmail { @@ -160,15 +159,13 @@ extension PresentationEmailExtension on PresentationEmail { List