diff --git a/lib/features/composer/domain/exceptions/compose_email_exception.dart b/lib/features/composer/domain/exceptions/compose_email_exception.dart new file mode 100644 index 000000000..f1116fe42 --- /dev/null +++ b/lib/features/composer/domain/exceptions/compose_email_exception.dart @@ -0,0 +1 @@ +class SendingEmailCanceledException implements Exception {} \ No newline at end of file diff --git a/lib/features/composer/domain/state/send_email_state.dart b/lib/features/composer/domain/state/send_email_state.dart index 01312e80f..814e6c3cb 100644 --- a/lib/features/composer/domain/state/send_email_state.dart +++ b/lib/features/composer/domain/state/send_email_state.dart @@ -53,4 +53,6 @@ class SendEmailFailure extends FeatureFailure { mailboxRequest, sendingEmailActionType, ]; -} \ No newline at end of file +} + +class CancelSendingEmail extends LoadingState {} \ No newline at end of file diff --git a/lib/features/composer/domain/usecases/create_new_and_send_email_interactor.dart b/lib/features/composer/domain/usecases/create_new_and_send_email_interactor.dart index 2c21547d9..cbff76b4d 100644 --- a/lib/features/composer/domain/usecases/create_new_and_send_email_interactor.dart +++ b/lib/features/composer/domain/usecases/create_new_and_send_email_interactor.dart @@ -2,10 +2,12 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart' as dartz; +import 'package:dio/dio.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/core/state.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:tmail_ui_user/features/composer/domain/exceptions/compose_email_exception.dart'; import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart'; import 'package:tmail_ui_user/features/composer/domain/state/generate_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; @@ -15,6 +17,7 @@ import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions. import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart'; +import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; class CreateNewAndSendEmailInteractor { final EmailRepository _emailRepository; @@ -27,7 +30,10 @@ class CreateNewAndSendEmailInteractor { this._composerRepository, ); - Stream> execute(CreateEmailRequest createEmailRequest) async* { + Stream> execute({ + required CreateEmailRequest createEmailRequest, + CancelToken? cancelToken + }) async* { SendingEmailArguments? sendingEmailArguments; try { yield dartz.Right(GenerateEmailLoading()); @@ -46,14 +52,16 @@ class CreateNewAndSendEmailInteractor { sendingEmailArguments.session, sendingEmailArguments.accountId, sendingEmailArguments.emailRequest, - mailboxRequest: sendingEmailArguments.mailboxRequest + mailboxRequest: sendingEmailArguments.mailboxRequest, + cancelToken: cancelToken ); if (sendingEmailArguments.emailRequest.emailIdDestroyed != null) { await _deleteOldDraftsEmail( session: sendingEmailArguments.session, accountId: sendingEmailArguments.accountId, - draftEmailId: sendingEmailArguments.emailRequest.emailIdDestroyed! + draftEmailId: sendingEmailArguments.emailRequest.emailIdDestroyed!, + cancelToken: cancelToken ); } @@ -69,13 +77,23 @@ class CreateNewAndSendEmailInteractor { } } catch (e) { logError('CreateNewAndSendEmailInteractor::execute: Exception: $e'); - yield dartz.Left(SendEmailFailure( - exception: e, - session: sendingEmailArguments?.session, - accountId: sendingEmailArguments?.accountId, - emailRequest: sendingEmailArguments?.emailRequest, - mailboxRequest: sendingEmailArguments?.mailboxRequest, - )); + if (e is UnknownError && e.message is List) { + yield dartz.Left(SendEmailFailure( + exception: SendingEmailCanceledException(), + session: sendingEmailArguments?.session, + accountId: sendingEmailArguments?.accountId, + emailRequest: sendingEmailArguments?.emailRequest, + mailboxRequest: sendingEmailArguments?.mailboxRequest, + )); + } else { + yield dartz.Left(SendEmailFailure( + exception: e, + session: sendingEmailArguments?.session, + accountId: sendingEmailArguments?.accountId, + emailRequest: sendingEmailArguments?.emailRequest, + mailboxRequest: sendingEmailArguments?.mailboxRequest, + )); + } } } @@ -113,13 +131,15 @@ class CreateNewAndSendEmailInteractor { Future _deleteOldDraftsEmail({ required Session session, required AccountId accountId, - required EmailId draftEmailId + required EmailId draftEmailId, + CancelToken? cancelToken }) async { try { await _emailRepository.deleteEmailPermanently( session, accountId, - draftEmailId + draftEmailId, + cancelToken: cancelToken ); } catch (e) { logError('CreateNewAndSendEmailInteractor::_deleteOldDraftsEmail: Exception: $e'); diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 4ca93a9e9..d1cfb15ff 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -7,6 +7,7 @@ import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:dio/dio.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:file_picker/file_picker.dart'; import 'package:filesize/filesize.dart'; @@ -29,6 +30,7 @@ import 'package:super_tag_editor/tag_editor.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/state/base_ui_state.dart'; import 'package:tmail_ui_user/features/base/state/button_state.dart'; +import 'package:tmail_ui_user/features/composer/domain/exceptions/compose_email_exception.dart'; import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart'; import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/generate_email_state.dart'; @@ -802,12 +804,17 @@ class ComposerController extends BaseController with DragDropFileMixin { } final emailContent = await _getContentInEditor(); - - final resultState = await _showSendingMessageDialog(emailContent: emailContent); - + final cancelToken = CancelToken(); + final resultState = await _showSendingMessageDialog( + emailContent: emailContent, + cancelToken: cancelToken + ); + log('ComposerController::_handleSendMessages: resultState = $resultState'); if (resultState is SendEmailSuccess) { _sendButtonState = ButtonState.enabled; _closeComposerAction(result: resultState); + } else if (resultState is SendEmailFailure && resultState.exception is SendingEmailCanceledException) { + _sendButtonState = ButtonState.enabled; } else if ((resultState is SendEmailFailure || resultState is GenerateEmailFailure) && context.mounted) { _showConfirmDialogWhenSendMessageFailure( context: context, @@ -818,7 +825,10 @@ class ComposerController extends BaseController with DragDropFileMixin { } } - Future _showSendingMessageDialog({required String emailContent}) { + Future _showSendingMessageDialog({ + required String emailContent, + CancelToken? cancelToken + }) { return Get.dialog( PointerInterceptor( child: SendingMessageDialogView( @@ -847,13 +857,19 @@ class ComposerController extends BaseController with DragDropFileMixin { references: composerArguments.value!.references, emailSendingQueue: composerArguments.value!.sendingEmail ), - createNewAndSendEmailInteractor: _createNewAndSendEmailInteractor + createNewAndSendEmailInteractor: _createNewAndSendEmailInteractor, + onCancelSendingEmailAction: _handleCancelSendingMessage, + cancelToken: cancelToken, ), ), barrierColor: AppColor.colorDefaultCupertinoActionSheet, ); } + void _handleCancelSendingMessage({CancelToken? cancelToken}) { + cancelToken?.cancel([SendingEmailCanceledException()]); + } + void _showConfirmDialogWhenSendMessageFailure({ required BuildContext context, required FeatureFailure failure diff --git a/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart b/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart index d59cc9f33..f74979398 100644 --- a/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart +++ b/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart @@ -5,26 +5,36 @@ import 'package:core/presentation/extensions/capitalize_extension.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart' as dartz; +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/composer/domain/exceptions/compose_email_exception.dart'; import 'package:tmail_ui_user/features/composer/domain/state/generate_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart'; +import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; +typedef OnCancelSendingEmailAction = Function({CancelToken? cancelToken}); + class SendingMessageDialogView extends StatefulWidget { final CreateEmailRequest createEmailRequest; final CreateNewAndSendEmailInteractor createNewAndSendEmailInteractor; + final OnCancelSendingEmailAction? onCancelSendingEmailAction; + final CancelToken? cancelToken; const SendingMessageDialogView({ super.key, required this.createEmailRequest, required this.createNewAndSendEmailInteractor, + this.onCancelSendingEmailAction, + this.cancelToken, }); @override @@ -40,7 +50,10 @@ class _SendingMessageDialogViewState extends State { void initState() { super.initState(); _streamSubscription = widget.createNewAndSendEmailInteractor - .execute(widget.createEmailRequest) + .execute( + createEmailRequest: widget.createEmailRequest, + cancelToken: widget.cancelToken + ) .listen( _handleDataStream, onError: _handleErrorStream @@ -66,7 +79,11 @@ class _SendingMessageDialogViewState extends State { void _handleErrorStream(Object error, StackTrace stackTrace) { logError('_SendingMessageDialogViewState::_handleErrorStream: Exception = $error'); - popBack(result: SendEmailFailure(exception: error)); + if (error is UnknownError && error.message is List) { + popBack(result: SendEmailFailure(exception: SendingEmailCanceledException())); + } else { + popBack(result: SendEmailFailure(exception: error)); + } } @override @@ -142,6 +159,14 @@ class _SendingMessageDialogViewState extends State { fontSize: 14 ), ); + } else if (success is CancelSendingEmail) { + return Text( + '${AppLocalizations.of(context).canceling}...', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppColor.labelColor, + fontSize: 14 + ), + ); } else { return Text( '${AppLocalizations.of(context).sendingMessage}...', @@ -167,7 +192,7 @@ class _SendingMessageDialogViewState extends State { ), ), Padding( - padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 4, bottom: 24), + padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 4, bottom: 16), child: Row( children: [ Text( @@ -188,7 +213,24 @@ class _SendingMessageDialogViewState extends State { ) ], ), - ) + ), + if (widget.onCancelSendingEmailAction != null) + Align( + alignment: AlignmentDirectional.centerEnd, + child: TMailButtonWidget.fromText( + text: AppLocalizations.of(context).cancel, + textStyle: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.black87, + fontSize: 15 + ), + padding: const EdgeInsetsDirectional.symmetric(horizontal: 20, vertical: 8), + margin: const EdgeInsetsDirectional.only(start: 12, end: 12, bottom: 16), + onTapActionCallback: () { + _viewStateNotifier.value = dartz.Right(CancelSendingEmail()); + widget.onCancelSendingEmailAction!(cancelToken: widget.cancelToken); + }, + ), + ) ], ) ], diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index 9255b5fe2..943448579 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -32,7 +32,10 @@ abstract class EmailDataSource { Session session, AccountId accountId, EmailRequest emailRequest, - {CreateNewMailboxRequest? mailboxRequest} + { + CreateNewMailboxRequest? mailboxRequest, + CancelToken? cancelToken + } ); Future> markAsRead(Session session, AccountId accountId, List emails, ReadActions readActions); @@ -79,7 +82,12 @@ abstract class EmailDataSource { Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds); - Future deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId); + Future deleteEmailPermanently( + Session session, + AccountId accountId, + EmailId emailId, + {CancelToken? cancelToken} + ); Future storeDetailedNewEmail(Session session, AccountId accountId, DetailedEmail detailedEmail); 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 36fa298aa..f2dc68290 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -46,10 +46,19 @@ class EmailDataSourceImpl extends EmailDataSource { Session session, AccountId accountId, EmailRequest emailRequest, - {CreateNewMailboxRequest? mailboxRequest} + { + CreateNewMailboxRequest? mailboxRequest, + CancelToken? cancelToken, + } ) async { try { - return await emailAPI.sendEmail(session, accountId, emailRequest, mailboxRequest: mailboxRequest); + return await emailAPI.sendEmail( + session, + accountId, + emailRequest, + mailboxRequest: mailboxRequest, + cancelToken: cancelToken + ); } catch (error, stackTrace) { return await _sendEmailExceptionThrower.throwException(error, stackTrace); } @@ -157,9 +166,19 @@ class EmailDataSourceImpl extends EmailDataSource { } @override - Future deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId) { + Future deleteEmailPermanently( + Session session, + AccountId accountId, + EmailId emailId, + {CancelToken? cancelToken} + ) { return Future.sync(() async { - return await emailAPI.deleteEmailPermanently(session, accountId, emailId); + return await emailAPI.deleteEmailPermanently( + session, + accountId, + emailId, + cancelToken: cancelToken + ); }).catchError(_exceptionThrower.throwException); } 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 ae2c3d25f..b19e06747 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 @@ -71,7 +71,12 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { ); @override - Future deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId) { + Future deleteEmailPermanently( + Session session, + AccountId accountId, + EmailId emailId, + {CancelToken? cancelToken} + ) { throw UnimplementedError(); } @@ -134,7 +139,15 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { } @override - Future sendEmail(Session session, AccountId accountId, EmailRequest emailRequest, {CreateNewMailboxRequest? mailboxRequest}) { + Future sendEmail( + Session session, + AccountId accountId, + EmailRequest emailRequest, + { + CreateNewMailboxRequest? mailboxRequest, + CancelToken? cancelToken + } + ) { throw UnimplementedError(); } diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 55dadc49a..85f6b9d38 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -117,7 +117,10 @@ class EmailAPI with HandleSetErrorMixin { Session session, AccountId accountId, EmailRequest emailRequest, - {CreateNewMailboxRequest? mailboxRequest} + { + CreateNewMailboxRequest? mailboxRequest, + CancelToken? cancelToken, + } ) async { final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); @@ -199,7 +202,7 @@ class EmailAPI with HandleSetErrorMixin { final response = await (requestBuilder ..usings(capabilities)) .build() - .execute(); + .execute(cancelToken: cancelToken); final setEmailResponse = response.parse( setEmailInvocation.methodCallId, @@ -606,7 +609,12 @@ class EmailAPI with HandleSetErrorMixin { return List.empty(); } - Future deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId) async { + Future deleteEmailPermanently( + Session session, + AccountId accountId, + EmailId emailId, + {CancelToken? cancelToken} + ) async { final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); final setEmailMethod = SetEmailMethod(accountId) ..addDestroy({emailId.id}); @@ -619,7 +627,7 @@ class EmailAPI with HandleSetErrorMixin { final response = await (requestBuilder ..usings(capabilities)) .build() - .execute(); + .execute(cancelToken: cancelToken); final setEmailResponse = response.parse( setEmailInvocation.methodCallId, diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 422682ca2..9e1f00042 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -58,9 +58,18 @@ class EmailRepositoryImpl extends EmailRepository { Session session, AccountId accountId, EmailRequest emailRequest, - {CreateNewMailboxRequest? mailboxRequest} + { + CreateNewMailboxRequest? mailboxRequest, + CancelToken? cancelToken + } ) { - return emailDataSource[DataSourceType.network]!.sendEmail(session, accountId, emailRequest, mailboxRequest: mailboxRequest); + return emailDataSource[DataSourceType.network]!.sendEmail( + session, + accountId, + emailRequest, + mailboxRequest: mailboxRequest, + cancelToken: cancelToken, + ); } @override @@ -172,8 +181,18 @@ class EmailRepositoryImpl extends EmailRepository { } @override - Future deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId) { - return emailDataSource[DataSourceType.network]!.deleteEmailPermanently(session, accountId, emailId); + Future deleteEmailPermanently( + Session session, + AccountId accountId, + EmailId emailId, + {CancelToken? cancelToken} + ) { + return emailDataSource[DataSourceType.network]!.deleteEmailPermanently( + session, + accountId, + emailId, + cancelToken: cancelToken + ); } @override diff --git a/lib/features/email/domain/repository/email_repository.dart b/lib/features/email/domain/repository/email_repository.dart index a2906e0af..533b82960 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -34,7 +34,10 @@ abstract class EmailRepository { Session session, AccountId accountId, EmailRequest emailRequest, - {CreateNewMailboxRequest? mailboxRequest} + { + CreateNewMailboxRequest? mailboxRequest, + CancelToken? cancelToken + } ); Future> markAsRead(Session session, AccountId accountId, List emails, ReadActions readActions); @@ -87,7 +90,12 @@ abstract class EmailRepository { Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds); - Future deleteEmailPermanently(Session session, AccountId accountId, EmailId emailId); + Future deleteEmailPermanently( + Session session, + AccountId accountId, + EmailId emailId, + {CancelToken? cancelToken} + ); Future getEmailState(Session session, AccountId accountId); diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 32edf5aef..a5861163e 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -415,10 +415,10 @@ class ThreadController extends BaseController with EmailActionController { } _getAllEmailAction(); } else if (error is MethodLevelErrors) { - if (currentOverlayContext != null && error.message?.isNotEmpty == true) { + if (currentOverlayContext != null && error.message != null) { appToast.showToastErrorMessage( currentOverlayContext!, - error.message! + error.message?.toString() ?? '' ); } clearState(); diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 6d2997da9..e71195ee2 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-03-06T02:46:19.947772", + "@@last_modified": "2024-03-12T12:50:35.525381", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3845,5 +3845,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "canceling": "Canceling", + "@canceling": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/exceptions/remote_exception.dart b/lib/main/exceptions/remote_exception.dart index 69a8732f8..235be7a93 100644 --- a/lib/main/exceptions/remote_exception.dart +++ b/lib/main/exceptions/remote_exception.dart @@ -11,7 +11,7 @@ abstract class RemoteException with EquatableMixin implements Exception { static const badCredentials = 'Bad credentials'; static const socketException = 'Socket exception'; - final String? message; + final Object? message; final int? code; const RemoteException({this.code, this.message}); @@ -25,7 +25,7 @@ class BadCredentialsException extends RemoteException { } class UnknownError extends RemoteException { - const UnknownError({int? code, String? message}) : super(code: code, message: message); + const UnknownError({int? code, Object? message}) : super(code: code, message: message); @override List get props => [code, message]; diff --git a/lib/main/exceptions/remote_exception_thrower.dart b/lib/main/exceptions/remote_exception_thrower.dart index 1a37115aa..885133b92 100644 --- a/lib/main/exceptions/remote_exception_thrower.dart +++ b/lib/main/exceptions/remote_exception_thrower.dart @@ -53,7 +53,7 @@ class RemoteExceptionThrower extends ExceptionThrower { if (error.error is SocketException) { throw const SocketError(); } else if (error.error != null) { - throw UnknownError(message: error.error!.toString()); + throw UnknownError(message: error.error); } else { throw const UnknownError(); } diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 222f04fdb..73c17632b 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4016,4 +4016,11 @@ class AppLocalizations { 'Saving of the message to drafts folder failed.\nAn error occurred while saving mail.', name: 'warningMessageWhenSaveEmailToDraftsFailure'); } + + String get canceling { + return Intl.message( + 'Canceling', + name: 'canceling' + ); + } } \ No newline at end of file