Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TF-3416 Render EML attachment #3425

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contact/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -660,10 +660,10 @@ packages:
description:
path: "."
ref: main
resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199
resolved-ref: b0856586624c19e3e13e78e15bd4d15b27d27661
url: "https://github.com/linagora/jmap-dart-client.git"
source: git
version: "0.3.0"
version: "0.3.2"
js:
dependency: transitive
description:
Expand Down
3 changes: 2 additions & 1 deletion core/lib/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ export 'presentation/extensions/compare_string_extension.dart';
export 'presentation/extensions/compare_list_extensions.dart';
export 'presentation/extensions/string_extension.dart';
export 'presentation/extensions/tap_down_details_extension.dart';
export 'domain/extensions/media_type_extension.dart';
export 'presentation/extensions/map_extensions.dart';
export 'presentation/extensions/either_view_state_extension.dart';
export 'presentation/extensions/media_type_extension.dart';

// Exceptions
export 'domain/exceptions/download_file_exception.dart';
Expand Down Expand Up @@ -51,6 +51,7 @@ export 'utils/broadcast_channel/broadcast_channel.dart';
export 'utils/list_utils.dart';
export 'utils/mail/domain.dart';
export 'utils/mail/mail_address.dart';
export 'utils/preview_eml_file_utils.dart';

// Views
export 'presentation/views/text/slogan_builder.dart';
Expand Down
4 changes: 4 additions & 0 deletions core/lib/data/constants/constant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ class Constant {
static const imageType = 'image';
static const textVCardMimeType = 'text/x-vcard';
static const textPlainMimeType = 'text/plain';
static const emlMimeType = 'message/rfc822';
static const mailtoScheme = 'mailto';
static const attachmentScheme = 'attachment';
static const emlPreviewerScheme = 'eml-previewer';
}
24 changes: 24 additions & 0 deletions core/lib/data/model/preview_attachment.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

import 'package:equatable/equatable.dart';

class PreviewAttachment with EquatableMixin {
final String iconBase64Data;
final String name;
final String size;
final String? link;

PreviewAttachment({
required this.iconBase64Data,
required this.name,
required this.size,
this.link,
});

@override
List<Object?> get props => [
iconBase64Data,
name,
size,
link,
];
}
1 change: 1 addition & 0 deletions core/lib/data/model/source_type/data_source_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
enum DataSourceType {
network,
local,
session,
hiveCache;
}
2 changes: 2 additions & 0 deletions core/lib/domain/exceptions/web_session_exception.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ class SaveToWebSessionFailException with EquatableMixin implements Exception {
@override
List<Object> get props => [];
}

class CannotOpenNewWindowException implements Exception {}
23 changes: 0 additions & 23 deletions core/lib/domain/extensions/media_type_extension.dart

This file was deleted.

4 changes: 1 addition & 3 deletions core/lib/domain/preview/supported_preview_file_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ class SupportedPreviewFileTypes {
'application/msword',
'application/vnd.ms-works'];

static const pdfMimeTypes = [
'application/pdf',
'application/rtf'];
static const rtfMimeTypes = ['application/rtf'];

static const xlsMimeTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
Expand Down
50 changes: 50 additions & 0 deletions core/lib/presentation/extensions/media_type_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:core/data/constants/constant.dart';
import 'package:core/domain/preview/document_uti.dart';
import 'package:core/domain/preview/supported_preview_file_types.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:http_parser/http_parser.dart';

extension MediaTypeExtension on MediaType {
bool isAndroidSupportedPreview() => SupportedPreviewFileTypes.androidSupportedTypes.contains(mimeType);

bool isIOSSupportedPreview() => SupportedPreviewFileTypes.iOSSupportedTypes.containsKey(mimeType);

bool isImageFile() => SupportedPreviewFileTypes.imageMimeTypes.contains(mimeType);

bool isDocFile() => SupportedPreviewFileTypes.docMimeTypes.contains(mimeType);

bool isPowerPointFile() => SupportedPreviewFileTypes.pptMimeTypes.contains(mimeType);

bool isExcelFile() => SupportedPreviewFileTypes.xlsMimeTypes.contains(mimeType);

bool isZipFile() => SupportedPreviewFileTypes.zipMimeTypes.contains(mimeType);

bool isRtfFile() => SupportedPreviewFileTypes.rtfMimeTypes.contains(mimeType);

DocumentUti getDocumentUti() => DocumentUti(SupportedPreviewFileTypes.iOSSupportedTypes[mimeType]);

String getIcon(ImagePaths imagePaths, {String? fileName}) {
if (isPDFFile(fileName: fileName) == true || isRtfFile()) {
hoangdat marked this conversation as resolved.
Show resolved Hide resolved
return imagePaths.icFilePdf;
} else if (isDocFile()) {
return imagePaths.icFileDocx;
} else if (isExcelFile()) {
return imagePaths.icFileXlsx;
} else if (isPowerPointFile()) {
return imagePaths.icFilePptx;
} else if (isZipFile()) {
return imagePaths.icFileZip;
} else if (isImageFile()) {
return imagePaths.icFilePng;
} else {
return imagePaths.icFileEPup;
}
}

bool isPDFFile({required String? fileName}) =>
mimeType == Constant.pdfMimeType ||
(mimeType == Constant.octetStreamMimeType &&
fileName?.endsWith(Constant.pdfExtension) == true);

bool get isEMLFile => mimeType == Constant.emlMimeType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import 'package:core/utils/html/html_utils.dart';
import 'package:flutter/cupertino.dart';
import 'package:universal_html/html.dart' as html;

typedef OnClickHyperLinkAction = Function(Uri?);

class HtmlContentViewerOnWeb extends StatefulWidget {

final String contentHtml;
Expand All @@ -21,6 +23,8 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
/// Handler for mailto: links
final Function(Uri?)? mailtoDelegate;

final OnClickHyperLinkAction? onClickHyperLinkAction;

// if widthContent is bigger than width of htmlContent, set this to true let widget able to resize to width of htmlContent
final bool allowResizeToDocumentSize;

Expand All @@ -32,6 +36,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
this.allowResizeToDocumentSize = true,
this.mailtoDelegate,
this.direction,
this.onClickHyperLinkAction,
}) : super(key: key);

@override
Expand All @@ -55,6 +60,7 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
late final StreamSubscription<html.MessageEvent> sizeListener;
bool _iframeLoaded = false;
static const String iframeOnLoadMessage = 'iframeHasBeenLoaded';
static const String onClickHyperLinkName = 'onClickHyperLink';

@override
void initState() {
Expand Down Expand Up @@ -113,6 +119,13 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
}
}
}

if (data['type'] != null && data['type'].contains('toDart: $onClickHyperLinkName')) {
final link = data['url'] as String?;
if (link != null && mounted) {
widget.onClickHyperLinkAction?.call(Uri.parse(link));
}
}
});
}

Expand Down Expand Up @@ -173,21 +186,50 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
}
}

function handleOnClickEmailLink(e) {
var href = this.href;
window.parent.postMessage(JSON.stringify({"view": "$_createdViewId", "type": "toDart: OpenLink", "url": "" + href}), "*");
e.preventDefault();
}
${widget.mailtoDelegate != null
? '''
function handleOnClickEmailLink(e) {
var href = this.href;
window.parent.postMessage(JSON.stringify({"view": "$_createdViewId", "type": "toDart: OpenLink", "url": "" + href}), "*");
e.preventDefault();
}
'''
: ''}



${widget.onClickHyperLinkAction != null
? '''
function onClickHyperLink(e) {
var href = this.href;
window.parent.postMessage(JSON.stringify({"view": "$_createdViewId", "type": "toDart: $onClickHyperLinkName", "url": "" + href}), "*");
e.preventDefault();
}
'''
: ''}

function handleOnLoad() {
window.parent.postMessage(JSON.stringify({"view": "$_createdViewId", "message": "$iframeOnLoadMessage"}), "*");
window.parent.postMessage(JSON.stringify({"view": "$_createdViewId", "type": "toIframe: getHeight"}), "*");
window.parent.postMessage(JSON.stringify({"view": "$_createdViewId", "type": "toIframe: getWidth"}), "*");

var emailLinks = document.querySelectorAll('a[href^="mailto:"]');
for(var i=0; i < emailLinks.length; i++){
emailLinks[i].addEventListener('click', handleOnClickEmailLink);
}
${widget.onClickHyperLinkAction != null
? '''
var hyperLinks = document.querySelectorAll('a');
for (var i=0; i < hyperLinks.length; i++){
hyperLinks[i].addEventListener('click', onClickHyperLink);
}
'''
: ''}

${widget.mailtoDelegate != null
? '''
var emailLinks = document.querySelectorAll('a[href^="mailto:"]');
for (var i=0; i < emailLinks.length; i++){
emailLinks[i].addEventListener('click', handleOnClickEmailLink);
}
'''
: ''}
}
</script>
''';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math' as math;

import 'package:core/data/constants/constant.dart';
import 'package:core/presentation/views/loading/cupertino_loading_widget.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/html/html_interaction.dart';
Expand All @@ -16,6 +17,8 @@ import 'package:url_launcher/url_launcher_string.dart';
typedef OnScrollHorizontalEndAction = Function(bool leftDirection);
typedef OnLoadWidthHtmlViewerAction = Function(bool isScrollPageViewActivated);
typedef OnMailtoDelegateAction = Future<void> Function(Uri? uri);
typedef OnPreviewEMLDelegateAction = Future<void> Function(Uri? uri);
typedef OnDownloadAttachmentDelegateAction = Future<void> Function(Uri? uri);

class HtmlContentViewer extends StatefulWidget {

Expand All @@ -26,6 +29,8 @@ class HtmlContentViewer extends StatefulWidget {
final OnLoadWidthHtmlViewerAction? onLoadWidthHtmlViewer;
final OnMailtoDelegateAction? onMailtoDelegateAction;
final OnScrollHorizontalEndAction? onScrollHorizontalEnd;
final OnPreviewEMLDelegateAction? onPreviewEMLDelegateAction;
final OnDownloadAttachmentDelegateAction? onDownloadAttachmentDelegateAction;

const HtmlContentViewer({
Key? key,
Expand All @@ -34,7 +39,9 @@ class HtmlContentViewer extends StatefulWidget {
this.direction,
this.onLoadWidthHtmlViewer,
this.onMailtoDelegateAction,
this.onScrollHorizontalEnd
this.onScrollHorizontalEnd,
this.onPreviewEMLDelegateAction,
this.onDownloadAttachmentDelegateAction,
}) : super(key: key);

@override
Expand Down Expand Up @@ -246,7 +253,7 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
NavigationAction navigationAction
) async {
final url = navigationAction.request.url?.toString();

log('_HtmlContentViewState::_shouldOverrideUrlLoading: URL = $url');
if (url == null) {
return NavigationActionPolicy.CANCEL;
}
Expand All @@ -256,9 +263,21 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
}

final requestUri = Uri.parse(url);
final mailtoHandler = widget.onMailtoDelegateAction;
if (mailtoHandler != null && requestUri.isScheme('mailto')) {
await mailtoHandler(requestUri);
if (widget.onMailtoDelegateAction != null &&
requestUri.isScheme(Constant.mailtoScheme)) {
await widget.onMailtoDelegateAction?.call(requestUri);
return NavigationActionPolicy.CANCEL;
}

if (widget.onPreviewEMLDelegateAction != null &&
requestUri.isScheme(Constant.emlPreviewerScheme)) {
await widget.onPreviewEMLDelegateAction?.call(requestUri);
return NavigationActionPolicy.CANCEL;
}

if (widget.onDownloadAttachmentDelegateAction != null &&
requestUri.isScheme(Constant.attachmentScheme)) {
await widget.onDownloadAttachmentDelegateAction?.call(requestUri);
return NavigationActionPolicy.CANCEL;
}

Expand Down
Loading
Loading