diff --git a/core/test/utils/string_convert_test.dart b/core/test/utils/string_convert_test.dart new file mode 100644 index 0000000000..9821d6b3c3 --- /dev/null +++ b/core/test/utils/string_convert_test.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; + +import 'package:core/domain/exceptions/string_exception.dart'; +import 'package:core/utils/string_convert.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('string convert test:', () { + const testText = 'Hello'; + test( + 'should use utf8 decoder ' + 'when isHtml is true', + () { + // arrange + final bytes = utf8.encode(testText); + + // act + final result = StringConvert.decodeFromBytes(bytes, charset: null, isHtml: true); + + // assert + expect(result, testText); + }); + + test( + 'should use utf8 decoder ' + 'when charset contains utf-8', + () { + // arrange + final bytes = utf8.encode(testText); + + // act + final result = StringConvert.decodeFromBytes(bytes, charset: 'utf-8'); + + // assert + expect(result, testText); + }); + + test( + 'should use latin1 decoder ' + 'when charset contains latin-1', + () { + // arrange + final bytes = latin1.encode(testText); + + // act + final result = StringConvert.decodeFromBytes(bytes, charset: 'latin-1'); + + // assert + expect(result, testText); + }); + + test( + 'should use ascii decoder ' + 'when charset contains ascii', + () { + // arrange + final bytes = ascii.encode(testText); + + // act + final result = StringConvert.decodeFromBytes(bytes, charset: 'ascii'); + + // assert + expect(result, testText); + }); + + test( + 'should throw NullCharsetException ' + 'when charset is null', + () { + // arrange + final bytes = utf8.encode(testText); + + // assert + expect( + () => StringConvert.decodeFromBytes(bytes, charset: null), + throwsA(isA()), + ); + }); + + test( + 'should throw UnsupportedCharsetException ' + 'when charset is unsupported', + () { + // arrange + final bytes = utf8.encode(testText); + + // assert + expect( + () => StringConvert.decodeFromBytes(bytes, charset: 'unsupported'), + throwsA(isA()), + ); + }); + }); +} \ No newline at end of file diff --git a/lib/features/email/domain/state/get_html_content_from_attachment_state.dart b/lib/features/email/domain/state/get_html_content_from_attachment_state.dart index 633fcaed55..381fe57d92 100644 --- a/lib/features/email/domain/state/get_html_content_from_attachment_state.dart +++ b/lib/features/email/domain/state/get_html_content_from_attachment_state.dart @@ -1,5 +1,6 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:model/email/attachment.dart'; class GettingHtmlContentFromAttachment extends LoadingState {} @@ -7,13 +8,19 @@ class GetHtmlContentFromAttachmentSuccess extends UIState { GetHtmlContentFromAttachmentSuccess({ required this.sanitizedHtmlContent, required this.htmlAttachmentTitle, + required this.attachment, }); final String sanitizedHtmlContent; final String htmlAttachmentTitle; + final Attachment attachment; @override - List get props => [sanitizedHtmlContent, htmlAttachmentTitle]; + List get props => [ + sanitizedHtmlContent, + htmlAttachmentTitle, + attachment, + ]; } class GetHtmlContentFromAttachmentFailure extends FeatureFailure { diff --git a/lib/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart b/lib/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart index 72b92a8069..b8828617b8 100644 --- a/lib/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart +++ b/lib/features/email/domain/usecases/get_html_content_from_attachment_interactor.dart @@ -52,20 +52,11 @@ class GetHtmlContentFromAttachmentInteractor { charset: success.attachment.charset, isHtml: true, ); - try { - final sanitizedHtmlContent = await _downloadAttachmentForWebInteractor - .emailRepository - .sanitizeHtmlContent( - htmlContent, - transformConfiguration, - ); - sanitizeState = Right(GetHtmlContentFromAttachmentSuccess( - sanitizedHtmlContent: sanitizedHtmlContent, - htmlAttachmentTitle: attachment.generateFileName(), - )); - } catch (e) { - sanitizeState = Left(GetHtmlContentFromAttachmentFailure(exception: e)); - } + sanitizeState = await _sanitizeHtmlContent( + htmlContent, + transformConfiguration, + attachment, + ); } }, ); @@ -83,4 +74,26 @@ class GetHtmlContentFromAttachmentInteractor { yield Left(GetHtmlContentFromAttachmentFailure(exception: e)); } } + + Future?> _sanitizeHtmlContent( + String htmlContent, + TransformConfiguration transformConfiguration, + Attachment attachment, + ) async { + try { + final sanitizedHtmlContent = await _downloadAttachmentForWebInteractor + .emailRepository + .sanitizeHtmlContent( + htmlContent, + transformConfiguration, + ); + return Right(GetHtmlContentFromAttachmentSuccess( + sanitizedHtmlContent: sanitizedHtmlContent, + htmlAttachmentTitle: attachment.generateFileName(), + attachment: attachment, + )); + } catch (e) { + return Left(GetHtmlContentFromAttachmentFailure(exception: e)); + } + } } \ 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 1d03dc766c..6493e33438 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -246,6 +246,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { Get.dialog(HtmlAttachmentPreviewer( title: success.htmlAttachmentTitle, htmlContent: success.sanitizedHtmlContent, + mailToClicked: openMailToLink, + downloadAttachmentClicked: () { + downloadAttachmentForWeb(success.attachment); + }, )); } } diff --git a/lib/features/email/presentation/widgets/html_attachment_previewer.dart b/lib/features/email/presentation/widgets/html_attachment_previewer.dart index ae44029b0a..cd66b9e7cb 100644 --- a/lib/features/email/presentation/widgets/html_attachment_previewer.dart +++ b/lib/features/email/presentation/widgets/html_attachment_previewer.dart @@ -1,90 +1,87 @@ -import 'dart:convert'; -import 'dart:math'; import 'package:core/presentation/utils/responsive_utils.dart'; -import 'package:core/presentation/utils/shims/dart_ui.dart'; +import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart'; import 'package:core/presentation/views/responsive/responsive_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/pdf_viewer/top_bar_attachment_viewer.dart'; -import 'package:universal_html/html.dart'; +import 'package:tmail_ui_user/main/utils/app_utils.dart'; -class HtmlAttachmentPreviewer extends StatefulWidget { +class HtmlAttachmentPreviewer extends StatelessWidget { const HtmlAttachmentPreviewer({ super.key, required this.htmlContent, required this.title, + required this.mailToClicked, + required this.downloadAttachmentClicked, }); final String title; final String htmlContent; + final void Function(Uri? mailToUri) mailToClicked; + final VoidCallback downloadAttachmentClicked; - @override - State createState() => _HtmlAttachmentPreviewerState(); -} - -class _HtmlAttachmentPreviewerState extends State { - late final IFrameElement _iframeElement; - late final String _viewId; - - @override - void initState() { - super.initState(); - _iframeElement = IFrameElement() - ..srcdoc = widget.htmlContent - ..style.border = 'none' - ..style.overflow = 'hidden' - ..style.width = '100%' - ..style.height = '100%'; - _viewId = _getRandString(10); - - platformViewRegistry.registerViewFactory(_viewId, (int viewId) => _iframeElement); - } - + static const double _verticalMargin = 16; @override Widget build(BuildContext context) { return Column( children: [ TopBarAttachmentViewer( - title: widget.title, - closeAction: () { - if (!mounted) return; - Get.back(); - }, + title: title, + closeAction: Get.back, + downloadAction: downloadAttachmentClicked, ), Expanded( - child: Center( - child: ResponsiveWidget( - responsiveUtils: ResponsiveUtils(), - desktop: Container( - width: MediaQuery.sizeOf(context).width * 0.4, - color: Colors.white, - margin: const EdgeInsets.symmetric(vertical: 16), - child: HtmlElementView(viewType: _viewId), - ), - tablet: Container( - width: MediaQuery.sizeOf(context).width * 0.8, - color: Colors.white, - margin: const EdgeInsets.symmetric(vertical: 16), - child: HtmlElementView(viewType: _viewId), - ), - mobile: Container( - width: MediaQuery.sizeOf(context).width, - color: Colors.white, - margin: const EdgeInsets.symmetric(vertical: 16), - child: HtmlElementView(viewType: _viewId), - ), - ), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: Center( + child: Container( + margin: const EdgeInsets.symmetric(vertical: _verticalMargin), + color: Colors.white, + child: ResponsiveWidget( + responsiveUtils: ResponsiveUtils(), + desktop: _buildHtmlViewerWith( + context, + width: constraints.maxWidth * 0.4, + height: constraints.maxHeight - _verticalMargin * 2 + ), + tablet: _buildHtmlViewerWith( + context, + width: constraints.maxWidth * 0.8, + height: constraints.maxHeight - _verticalMargin * 2 + ), + mobile: _buildHtmlViewerWith( + context, + width: constraints.maxWidth, + height: constraints.maxHeight - _verticalMargin * 2 + ), + ), + ), + ), + ); + } ), ), ], ); } - String _getRandString(int len) { - var random = Random.secure(); - var values = List.generate(len, (i) => random.nextInt(255)); - return base64UrlEncode(values); + HtmlContentViewerOnWeb _buildHtmlViewerWith( + BuildContext context, { + required double width, + required double height, + }) { + return HtmlContentViewerOnWeb( + contentHtml: htmlContent, + widthContent: width, + heightContent: height, + direction: AppUtils.getCurrentDirection(context), + mailtoDelegate: (uri) { + Get.back(); + mailToClicked(uri); + }, + ); } } \ No newline at end of file