diff --git a/core/lib/presentation/extensions/uri_extension.dart b/core/lib/presentation/extensions/uri_extension.dart index 2492f2926..3668ea018 100644 --- a/core/lib/presentation/extensions/uri_extension.dart +++ b/core/lib/presentation/extensions/uri_extension.dart @@ -5,23 +5,20 @@ extension URIExtension on Uri { Uri toQualifiedUrl({required Uri baseUrl}) { log('SessionUtils::toQualifiedUrl():baseUrl: $baseUrl | sourceUrl: $this'); - if (toString().startsWith(baseUrl.toString())) { - final qualifiedUrl = toString().removeLastSlashOfUrl(); + if (hasOrigin) { + final qualifiedUrl = toString(); log('SessionUtils::toQualifiedUrl():qualifiedUrl: $qualifiedUrl'); return Uri.parse(qualifiedUrl); + } else if (toString().isEmpty) { + log('SessionUtils::toQualifiedUrl():qualifiedUrl: $baseUrl'); + return baseUrl; } else { - if (!hasOrigin) { - final baseUrlValid = baseUrl.toString().removeLastSlashOfUrl(); - final sourceUrlValid = toString().addFirstSlashOfUrl().removeLastSlashOfUrl(); - log('SessionUtils::toQualifiedUrl():baseUrlValid: $baseUrlValid | sourceUrlValid: $sourceUrlValid'); - final qualifiedUrl = baseUrlValid + sourceUrlValid; - log('SessionUtils::toQualifiedUrl():qualifiedUrl: $qualifiedUrl'); - return Uri.parse(qualifiedUrl); - } else { - final qualifiedUrl = toString().removeLastSlashOfUrl(); - log('SessionUtils::toQualifiedUrl():qualifiedUrl: $qualifiedUrl'); - return Uri.parse(qualifiedUrl); - } + final baseUrlValid = baseUrl.toString().removeLastSlashOfUrl(); + final sourceUrlValid = toString().addFirstSlashOfUrl(); + log('SessionUtils::toQualifiedUrl():baseUrlValid: $baseUrlValid | sourceUrlValid: $sourceUrlValid'); + final qualifiedUrl = baseUrlValid + sourceUrlValid; + log('SessionUtils::toQualifiedUrl():qualifiedUrl: $qualifiedUrl'); + return Uri.parse(qualifiedUrl); } } diff --git a/core/test/presentation/extensions/uri_extension_test.dart b/core/test/presentation/extensions/uri_extension_test.dart index 2748d0cad..8616c0d52 100644 --- a/core/test/presentation/extensions/uri_extension_test.dart +++ b/core/test/presentation/extensions/uri_extension_test.dart @@ -16,11 +16,21 @@ void main() { expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); }); + test('convertToQualifiedUrl() should return qualified url when baseUrl is `https://domain.com` and sourceUrl is `https://domain.com/jmap/`', () async { + final baseUrl = Uri.parse('https://domain.com'); + final sourceUrl = Uri.parse('https://domain.com/jmap/'); + + final qualifiedUrlExpected = Uri.parse('https://domain.com/jmap/'); + final qualifiedUrlResult = sourceUrl.toQualifiedUrl(baseUrl: baseUrl); + + expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); + }); + test('convertToQualifiedUrl() should return qualified url when baseUrl is `https://domain.com` and sourceUrl is `/jmap/`', () async { final baseUrl = Uri.parse('https://domain.com'); final sourceUrl = Uri.parse('/jmap/'); - final qualifiedUrlExpected = Uri.parse('https://domain.com/jmap'); + final qualifiedUrlExpected = Uri.parse('https://domain.com/jmap/'); final qualifiedUrlResult = sourceUrl.toQualifiedUrl(baseUrl: baseUrl); expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); @@ -50,7 +60,7 @@ void main() { final baseUrl = Uri.parse('https://domain.com/jmap'); final sourceUrl = Uri.parse('/'); - final qualifiedUrlExpected = Uri.parse('https://domain.com/jmap'); + final qualifiedUrlExpected = Uri.parse('https://domain.com/jmap/'); final qualifiedUrlResult = sourceUrl.toQualifiedUrl(baseUrl: baseUrl); expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); @@ -70,7 +80,7 @@ void main() { final baseUrl = Uri.parse('https://domain.com:2000/jmap'); final sourceUrl = Uri.parse('https://domain.com:2001/jmap/'); - final qualifiedUrlExpected = Uri.parse('https://domain.com:2001/jmap'); + final qualifiedUrlExpected = Uri.parse('https://domain.com:2001/jmap/'); final qualifiedUrlResult = sourceUrl.toQualifiedUrl(baseUrl: baseUrl); expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); @@ -86,7 +96,7 @@ void main() { expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); }); - test('convertToQualifiedUrl() should return qualified url when baseUrl is localhost and sourceUrl is `https://domain.com:2001/basiauth/jmap`', () async { + test('convertToQualifiedUrl() should return qualified url when baseUrl is localhost and sourceUrl is `https://domain.com:2001/basicauth/jmap`', () async { final baseUrl = Uri.parse('https://localhost:9080/basicauth'); final sourceUrl = Uri.parse('https://domain.com:2001/basicauth/jmap'); @@ -95,5 +105,15 @@ void main() { expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); }); + + test('convertToQualifiedUrl() should return qualified url when baseUrl is localhost and sourceUrl is `https://domain.com:2001/basicauth/jmap/`', () async { + final baseUrl = Uri.parse('https://localhost:9080/basicauth'); + final sourceUrl = Uri.parse('https://domain.com:2001/basicauth/jmap/'); + + final qualifiedUrlExpected = Uri.parse('https://domain.com:2001/basicauth/jmap/'); + final qualifiedUrlResult = sourceUrl.toQualifiedUrl(baseUrl: baseUrl); + + expect(qualifiedUrlResult, equals(qualifiedUrlExpected)); + }); }); } \ No newline at end of file diff --git a/integration_test/utils/scenario_utils_mixin.dart b/integration_test/utils/scenario_utils_mixin.dart index 6c6ac4bfe..0e9bbcfcb 100644 --- a/integration_test/utils/scenario_utils_mixin.dart +++ b/integration_test/utils/scenario_utils_mixin.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io' hide HttpClient; import 'package:collection/collection.dart'; +import 'package:core/utils/app_logger.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; @@ -142,33 +143,39 @@ mixin ScenarioUtilsMixin { filePath: path, fileSize: await file.length(), ); - final uploadUri = mailboxDashBoardController.sessionCurrent!.getUploadUri( - mailboxDashBoardController.accountId.value!, - jmapUrl: mailboxDashBoardController.dynamicUrlInterceptors.jmapUrl, - ); - - final uploadAttachmentInteractor = Get.find(); - final uploadAttachmentState = await uploadAttachmentInteractor - .execute(fileInfo, uploadUri) - .last; - final attachment = await uploadAttachmentState.fold( - (failure) => null, - (success) async { - if (success is UploadAttachmentSuccess) { - final uploadAttachment = await success.uploadAttachment.progressState.last; - return uploadAttachment.fold( - (failure) => null, - (success) { - if (success is! SuccessAttachmentUploadState) return null; - return success.attachment; - }, - ); - } - }, - ); - if (attachment != null) { - attachments.add(attachment); + try { + final uploadUri = mailboxDashBoardController.sessionCurrent!.getUploadUri( + mailboxDashBoardController.accountId.value!, + jmapUrl: mailboxDashBoardController.dynamicUrlInterceptors.jmapUrl, + ); + + final uploadAttachmentInteractor = Get.find(); + final uploadAttachmentState = await uploadAttachmentInteractor + .execute(fileInfo, uploadUri) + .last; + final attachment = await uploadAttachmentState.fold( + (failure) => null, + (success) async { + if (success is UploadAttachmentSuccess) { + final uploadAttachment = await success.uploadAttachment.progressState.last; + return uploadAttachment.fold( + (failure) => null, + (success) { + if (success is! SuccessAttachmentUploadState) return null; + + return success.attachment; + }, + ); + } + }, + ); + if (attachment != null) { + attachments.add(attachment); + } + } catch (e) { + logError('ScenarioUtilsMixin::uploadAttachments(): $e'); + return attachments; } } diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index a94e1fbe0..2cf14ef59 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -44,6 +44,7 @@ import 'package:tmail_ui_user/features/composer/domain/state/restore_email_inlin import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_as_base64_interactor.dart'; @@ -709,8 +710,15 @@ class ComposerController extends BaseController inlineImages: arguments.inlineImages); final accountId = mailboxDashBoardController.accountId.value; - final downloadUrl = mailboxDashBoardController.sessionCurrent - ?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); + dynamic downloadUrl; + try { + downloadUrl = mailboxDashBoardController.sessionCurrent + ?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); + } catch (e) { + logError('ComposerController::_initEmail(): $e'); + downloadUrl = null; + } + if (accountId == null || downloadUrl == null) return; _getEmailContentFromSessionStorageBrowser( htmlContent: arguments.emailContents ?? '', @@ -1226,15 +1234,21 @@ class ComposerController extends BaseController ); } + void _uploadAttachmentsAction({required List pickedFiles}) { final session = mailboxDashBoardController.sessionCurrent; final accountId = mailboxDashBoardController.accountId.value; if (session != null && accountId != null) { - final uploadUri = session.getUploadUri(accountId, jmapUrl: dynamicUrlInterceptors.jmapUrl); - uploadController.justUploadAttachmentsAction( - uploadFiles: pickedFiles, - uploadUri: uploadUri, - ); + try { + final uploadUri = session.getUploadUri(accountId, jmapUrl: dynamicUrlInterceptors.jmapUrl); + uploadController.justUploadAttachmentsAction( + uploadFiles: pickedFiles, + uploadUri: uploadUri, + ); + } catch (e) { + log('ComposerController::_uploadAttachmentsAction: $e'); + uploadController.consumeState(Stream.value(Left(UploadAttachmentFailure(e, pickedFiles[0])))); + } } else { log('ComposerController::_uploadAttachmentsAction: SESSION OR ACCOUNT_ID is NULL'); } @@ -1407,15 +1421,20 @@ class ComposerController extends BaseController return; } - consumeState(_getEmailContentInteractor.execute( - session, - accountId, - emailId, - mailboxDashBoardController.baseDownloadUrl, - TransformConfiguration.forEditDraftsEmail(), - additionalProperties: Properties({ - IndividualHeaderIdentifier.identityHeader.value}), - )); + try { + consumeState(_getEmailContentInteractor.execute( + session, + accountId, + emailId, + mailboxDashBoardController.baseDownloadUrl, + TransformConfiguration.forEditDraftsEmail(), + additionalProperties: Properties({ + IndividualHeaderIdentifier.identityHeader.value}), + )); + } catch (e) { + logError('ComposerController::_handleUploadInlineSuccess(): $e'); + consumeState(Stream.value(Left(GetEmailContentFailure(e)))); + } } void _getEmailContentOffLineSuccess(GetEmailContentFromCacheSuccess success) { @@ -1855,7 +1874,12 @@ class ComposerController extends BaseController void _handleUploadInlineSuccess(SuccessAttachmentUploadState uploadState) { uploadController.clearUploadInlineViewState(); - final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); + String? baseDownloadUrl; + try { + baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); + } catch (e) { + logError('ComposerController::_handleUploadInlineSuccess(): $e'); + } final accountId = mailboxDashBoardController.accountId.value; if (baseDownloadUrl != null && accountId != null) { @@ -1867,6 +1891,9 @@ class ComposerController extends BaseController uploadState.fileInfo, maxWidth: maxWithEditor, )); + } else { + log('ComposerController::_handleUploadInlineFailure(): baseDownloadUrl: $baseDownloadUrl, accountId: $accountId'); + consumeState(Stream.value(Left(DownloadImageAsBase64Failure(e)))); } } diff --git a/lib/features/email/domain/state/get_list_detailed_email_by_id_state.dart b/lib/features/email/domain/state/get_list_detailed_email_by_id_state.dart new file mode 100644 index 000000000..ac27e2e68 --- /dev/null +++ b/lib/features/email/domain/state/get_list_detailed_email_by_id_state.dart @@ -0,0 +1,5 @@ +import 'package:core/presentation/state/failure.dart'; + +class GetListDetailedEmailByIdFailure extends FeatureFailure { + GetListDetailedEmailByIdFailure({super.exception}); +} \ No newline at end of file diff --git a/lib/features/email/domain/state/preview_pdf_file_state.dart b/lib/features/email/domain/state/preview_pdf_file_state.dart new file mode 100644 index 000000000..c6d476522 --- /dev/null +++ b/lib/features/email/domain/state/preview_pdf_file_state.dart @@ -0,0 +1,6 @@ +import 'package:core/presentation/state/failure.dart'; + +class PreviewPDFFileFailure extends FeatureFailure { + + PreviewPDFFileFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index e3d85165d..524b4b65d 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -66,6 +66,7 @@ import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state. import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/parse_email_by_blob_id_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/preview_email_from_eml_file_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/preview_pdf_file_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/print_email_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/send_receipt_to_sender_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/store_event_attendance_status_state.dart'; @@ -314,6 +315,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _handlePreviewEmailFromEMLFileFailure(failure); } else if (failure is GetHtmlContentFromAttachmentFailure) { _handleGetHtmlContentFromAttachmentFailure(failure); + } else if (failure is PreviewPDFFileFailure) { + _handlePreviewPDFFileFailure(failure); } } @@ -530,18 +533,23 @@ class SingleEmailController extends BaseController with AppLoaderMixin { ))); } else { if (session != null && accountId != null) { - final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); - TransformConfiguration transformConfiguration = PlatformInfo.isWeb - ? TransformConfiguration.forPreviewEmailOnWeb() - : TransformConfiguration.forPreviewEmail(); + try { + final baseDownloadUrl = session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); + TransformConfiguration transformConfiguration = PlatformInfo.isWeb + ? TransformConfiguration.forPreviewEmailOnWeb() + : TransformConfiguration.forPreviewEmail(); - consumeState(_getEmailContentInteractor.execute( - session!, - accountId!, - emailId, - baseDownloadUrl, - transformConfiguration - )); + consumeState(_getEmailContentInteractor.execute( + session!, + accountId!, + emailId, + baseDownloadUrl, + transformConfiguration + )); + } catch (e) { + logError('SingleEmailController::_getEmailContentAction(): $e'); + consumeState(Stream.value(Left(GetEmailContentFailure(e)))); + } } } } @@ -767,14 +775,19 @@ class SingleEmailController extends BaseController with AppLoaderMixin { void _downloadAttachmentsAction(List attachments) { if (accountId != null && session != null) { - final baseDownloadUrl = session!.getDownloadUrl( - jmapUrl: dynamicUrlInterceptors.jmapUrl, - ); - consumeState(_downloadAttachmentsInteractor.execute( - attachments, - accountId!, - baseDownloadUrl, - )); + try { + final baseDownloadUrl = session!.getDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + consumeState(_downloadAttachmentsInteractor.execute( + attachments, + accountId!, + baseDownloadUrl, + )); + } catch (e) { + logError('SingleEmailController::_downloadAttachmentsAction(): $e'); + consumeState(Stream.value(Left(DownloadAttachmentsFailure(e)))); + } } } @@ -821,15 +834,20 @@ class SingleEmailController extends BaseController with AppLoaderMixin { void _exportAttachmentAction(Attachment attachment, CancelToken cancelToken) { if (accountId != null && session != null) { - final baseDownloadUrl = session!.getDownloadUrl( - jmapUrl: dynamicUrlInterceptors.jmapUrl, - ); - consumeState(_exportAttachmentInteractor.execute( - attachment, - accountId!, - baseDownloadUrl, - cancelToken, - )); + try { + final baseDownloadUrl = session!.getDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + consumeState(_exportAttachmentInteractor.execute( + attachment, + accountId!, + baseDownloadUrl, + cancelToken, + )); + } catch (e) { + logError('SingleEmailController::_exportAttachmentAction(): $e'); + consumeState(Stream.value(Left(ExportAttachmentFailure(e)))); + } } } @@ -873,16 +891,21 @@ class SingleEmailController extends BaseController with AppLoaderMixin { void downloadAttachmentForWeb(Attachment attachment) { if (accountId != null && session != null) { - final baseDownloadUrl = session!.getDownloadUrl( - jmapUrl: dynamicUrlInterceptors.jmapUrl, - ); final generateTaskId = DownloadTaskId(uuid.v4()); - consumeState(_downloadAttachmentForWebInteractor.execute( - generateTaskId, - attachment, - accountId!, - baseDownloadUrl, - _downloadProgressStateController)); + try { + final baseDownloadUrl = session!.getDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + consumeState(_downloadAttachmentForWebInteractor.execute( + generateTaskId, + attachment, + accountId!, + baseDownloadUrl, + _downloadProgressStateController)); + } catch (e) { + logError('SingleEmailController::downloadAttachmentForWeb(): $e'); + consumeState(Stream.value(Left(DownloadAttachmentForWebFailure(attachment: attachment, taskId: generateTaskId, exception: e)))); + } } else { consumeState(Stream.value( Left(DownloadAttachmentForWebFailure( @@ -2002,8 +2025,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin { Attachment attachment, ) { final accountId = mailboxDashBoardController.accountId.value; - final downloadUrl = mailboxDashBoardController.sessionCurrent + dynamic downloadUrl; + try { + downloadUrl = mailboxDashBoardController.sessionCurrent ?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl); + } catch (e) { + logError('SingleEmailController::_getEmailContentAction(): $e'); + downloadUrl = null; + } final blobId = attachment.blobId; if (accountId == null || downloadUrl == null || blobId == null) { @@ -2030,24 +2059,30 @@ class SingleEmailController extends BaseController with AppLoaderMixin { AppLocalizations.of(context).noPreviewAvailable); return; } - final downloadUrl = session!.getDownloadUrl( - jmapUrl: dynamicUrlInterceptors.jmapUrl, - ); - await Get.generalDialog( - barrierColor: Colors.black.withOpacity(0.8), - pageBuilder: (_, __, ___) { - return PointerInterceptor( - child: PDFViewer( - attachment: attachment, - accountId: accountId!, - downloadUrl: downloadUrl, - downloadAction: _downloadPDFFile, - printAction: _printPDFFile, - ) - ); - }, - ); + try { + final downloadUrl = session!.getDownloadUrl( + jmapUrl: dynamicUrlInterceptors.jmapUrl, + ); + + await Get.generalDialog( + barrierColor: Colors.black.withOpacity(0.8), + pageBuilder: (_, __, ___) { + return PointerInterceptor( + child: PDFViewer( + attachment: attachment, + accountId: accountId!, + downloadUrl: downloadUrl, + downloadAction: _downloadPDFFile, + printAction: _printPDFFile, + ) + ); + }, + ); + } catch (e) { + logError('SingleEmailController::previewPDFFileAction(): $e'); + consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(e)))); + } } void previewEMLFileAction(Id? blobId, AppLocalizations appLocalizations) { @@ -2085,17 +2120,22 @@ class SingleEmailController extends BaseController with AppLoaderMixin { return; } - consumeState(_previewEmailFromEmlFileInteractor.execute( - PreviewEmailEMLRequest( - accountId: accountId!, - userName: userName!, - blobId: success.blobId, - email: success.email, - locale: Localizations.localeOf(currentContext!), - appLocalizations: AppLocalizations.of(currentContext!), - baseDownloadUrl: session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), - ), - )); + try { + consumeState(_previewEmailFromEmlFileInteractor.execute( + PreviewEmailEMLRequest( + accountId: accountId!, + userName: userName!, + blobId: success.blobId, + email: success.email, + locale: Localizations.localeOf(currentContext!), + appLocalizations: AppLocalizations.of(currentContext!), + baseDownloadUrl: session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), + ), + )); + } catch (e) { + logError('SingleEmailController::_handleParseEmailByBlobIdSuccess(): $e'); + consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(e)))); + } } void _handleParseEmailByBlobIdFailure(ParseEmailByBlobIdFailure failure) { @@ -2103,6 +2143,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin { toastManager.showMessageFailure(failure); } + void _handlePreviewPDFFileFailure(PreviewPDFFileFailure failure) { + SmartDialog.dismiss(); + toastManager.showMessageFailure(failure); + } + void _handlePreviewEmailFromEMLFileFailure(PreviewEmailFromEmlFileFailure failure) { SmartDialog.dismiss(); toastManager.showMessageFailure(failure); diff --git a/lib/features/email_previewer/email_previewer_controller.dart b/lib/features/email_previewer/email_previewer_controller.dart index fcd12721c..863cce09a 100644 --- a/lib/features/email_previewer/email_previewer_controller.dart +++ b/lib/features/email_previewer/email_previewer_controller.dart @@ -267,18 +267,23 @@ class EmailPreviewerController extends ReloadableController { return; } - consumeState(_previewEmailFromEmlFileInteractor.execute( - PreviewEmailEMLRequest( - accountId: _accountId!, - userName: _session!.username, - blobId: success.blobId, - email: success.email, - locale: Localizations.localeOf(currentContext!), - appLocalizations: AppLocalizations.of(currentContext!), - baseDownloadUrl: _session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), - isShared: false, - ), - )); + try { + consumeState(_previewEmailFromEmlFileInteractor.execute( + PreviewEmailEMLRequest( + accountId: _accountId!, + userName: _session!.username, + blobId: success.blobId, + email: success.email, + locale: Localizations.localeOf(currentContext!), + appLocalizations: AppLocalizations.of(currentContext!), + baseDownloadUrl: _session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), + isShared: false, + ), + )); + } catch (e) { + logError('EmailPreviewerController::_handleParseEmailByBlobIdSuccess(): $e'); + consumeState(Stream.value(Left(PreviewEmailFromEmlFileFailure(e)))); + } } void _updateWindowBrowserTitle(String title) { @@ -308,14 +313,22 @@ class EmailPreviewerController extends ReloadableController { } void _startDownloadAttachment(Attachment attachment) { - _downloadInteractorStreamSubscription = _downloadAttachmentForWebInteractor - .execute( - attachment.downloadTaskId, - attachment, - _accountId!, - _session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), - _downloadAttachmentStreamController!) - .listen(_handleDownloadAttachmentViewState); + try { + _downloadInteractorStreamSubscription = _downloadAttachmentForWebInteractor + .execute( + attachment.downloadTaskId, + attachment, + _accountId!, + _session!.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl), + _downloadAttachmentStreamController!) + .listen(_handleDownloadAttachmentViewState); + } catch (e) { + logError('EmailPreviewerController::_handleParseEmailByBlobIdSuccess(): $e'); + consumeState(Stream.value(Left(DownloadAttachmentForWebFailure( + attachment: attachment, + taskId: attachment.downloadTaskId, + exception: e)))); + } } void _initialDownloadAttachmentStreamListener() { diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 1915e8863..684fd8bb7 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -2755,7 +2755,14 @@ class MailboxDashBoardController extends ReloadableController dispatchAction(SelectionAllEmailAction()); } - String get baseDownloadUrl => sessionCurrent?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl) ?? ''; + String get baseDownloadUrl { + try { + return sessionCurrent?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl) ?? ''; + } catch (e) { + logError('MailboxDashboardController::baseDownloadUrl(): $e'); + return ''; + } + } void redirectToInboxAction() { log('MailboxDashBoardController::redirectToInboxAction:'); diff --git a/lib/features/public_asset/presentation/public_asset_controller.dart b/lib/features/public_asset/presentation/public_asset_controller.dart index 60963fafb..c66208475 100644 --- a/lib/features/public_asset/presentation/public_asset_controller.dart +++ b/lib/features/public_asset/presentation/public_asset_controller.dart @@ -9,6 +9,7 @@ import 'package:core/utils/file_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; @@ -126,6 +127,8 @@ class PublicAssetController extends BaseController { _handleCreatePublicAssetFailureState(); } else if (failure is PublicAssetOverQuotaFailureState) { _handlePublicAssetOverQuotaFailureState(failure); + } else if (failure is UploadAttachmentFailure) { + _handleUploadAttachmentFailure(failure); } } @@ -156,6 +159,21 @@ class PublicAssetController extends BaseController { } } + void _handleUploadAttachmentFailure(UploadAttachmentFailure failure) { + if (currentContext != null && currentOverlayContext != null) { + appToast.showToastErrorMessage( + currentOverlayContext!, + failure.fileInfo.isInline == true + ? AppLocalizations.of(currentContext!).thisImageCannotBeAdded + : AppLocalizations.of(currentContext!).can_not_upload_this_file_as_attachments, + leadingSVGIconColor: Colors.white, + leadingSVGIcon: failure.fileInfo.isInline == true + ? imagePaths.icInsertImage + : imagePaths.icAttachment + ); + } + } + Future _handleUploadAttachmentSuccess(UploadAttachmentSuccess success) async { await _publicAssetStreamGroup.add(success.uploadAttachment.progressState); } @@ -206,8 +224,13 @@ class PublicAssetController extends BaseController { if (session == null || accountId == null) return; final fileInfo = platformFile.toFileInfo(); - final uploadUri = session!.getUploadUri(accountId!, jmapUrl: dynamicUrlInterceptors.jmapUrl); - consumeState(_uploadAttachmentInteractor.execute(fileInfo, uploadUri)); + try { + final uploadUri = session!.getUploadUri(accountId!, jmapUrl: dynamicUrlInterceptors.jmapUrl); + consumeState(_uploadAttachmentInteractor.execute(fileInfo, uploadUri)); + } catch (e) { + log('PublicAssetController::_uploadFileToBlobAction: $e'); + consumeState(Stream.value(Left(UploadAttachmentFailure(e, fileInfo)))); + } } void discardChanges() { diff --git a/lib/features/push_notification/presentation/listener/email_change_listener.dart b/lib/features/push_notification/presentation/listener/email_change_listener.dart index 5d3ef1175..d02fd45ec 100644 --- a/lib/features/push_notification/presentation/listener/email_change_listener.dart +++ b/lib/features/push_notification/presentation/listener/email_change_listener.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; +import 'package:dartz/dartz.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; @@ -334,13 +335,18 @@ class EmailChangeListener extends ChangeListener { if (_getListDetailedEmailByIdInteractor != null && _dynamicUrlInterceptors != null && session != null) { - final baseDownloadUrl = session.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors!.jmapUrl); - consumeState(_getListDetailedEmailByIdInteractor!.execute( - session, - accountId, - emailIds, - baseDownloadUrl - )); + try { + final baseDownloadUrl = session.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors!.jmapUrl); + consumeState(_getListDetailedEmailByIdInteractor!.execute( + session, + accountId, + emailIds, + baseDownloadUrl + )); + } catch (e) { + logError('EmailChangeListener::_getListDetailedEmailByIdAction(): $e'); + consumeState(Stream.value(Left(GetDetailedEmailByIdFailure(e)))); + } } } diff --git a/model/lib/error_type_handler/unknown_uri_exception.dart b/model/lib/error_type_handler/unknown_uri_exception.dart new file mode 100644 index 000000000..d368dd831 --- /dev/null +++ b/model/lib/error_type_handler/unknown_uri_exception.dart @@ -0,0 +1 @@ +class UnknownUriException implements Exception {} diff --git a/model/lib/extensions/session_extension.dart b/model/lib/extensions/session_extension.dart index f3661ca9b..b5a386109 100644 --- a/model/lib/extensions/session_extension.dart +++ b/model/lib/extensions/session_extension.dart @@ -9,15 +9,22 @@ import 'package:jmap_dart_client/jmap/core/capability/capability_properties.dart import 'package:jmap_dart_client/jmap/core/capability/empty_capability.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/error_type_handler/account_exception.dart'; +import 'package:model/error_type_handler/unknown_uri_exception.dart'; import 'package:model/model.dart'; import 'package:uri/uri.dart'; extension SessionExtension on Session { String getDownloadUrl({String? jmapUrl}) { - final downloadUrlValid = jmapUrl != null - ? downloadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)) - : downloadUrl; + final Uri downloadUrlValid; + if (jmapUrl != null) { + downloadUrlValid = downloadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)); + } else if (downloadUrl.hasOrigin) { + downloadUrlValid = downloadUrl; + } else { + throw UnknownUriException(); + } + var baseUrl = '${downloadUrlValid.origin}${downloadUrlValid.path}?${downloadUrlValid.query}'; if (baseUrl.endsWith('/')) { baseUrl = baseUrl.substring(0, baseUrl.length - 1); @@ -27,9 +34,15 @@ extension SessionExtension on Session { } Uri getUploadUri(AccountId accountId, {String? jmapUrl}) { - final uploadUrlValid = jmapUrl != null - ? uploadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)) - : uploadUrl; + final Uri uploadUrlValid; + if (jmapUrl != null) { + uploadUrlValid = uploadUrl.toQualifiedUrl(baseUrl: Uri.parse(jmapUrl)); + } else if (uploadUrl.hasOrigin) { + uploadUrlValid = uploadUrl; + } else { + throw UnknownUriException(); + } + final baseUrl = '${uploadUrlValid.origin}${uploadUrlValid.path}'; final uploadUriTemplate = UriTemplate(Uri.decodeFull(baseUrl)); final uploadUri = uploadUriTemplate.expand({