TF-4152 Fix tooltip stay out of screen in Email View
This commit is contained in:
@@ -111,6 +111,7 @@ export 'presentation/views/semantics/checkbox_semantics.dart';
|
|||||||
export 'presentation/views/semantics/text_field_semantics.dart';
|
export 'presentation/views/semantics/text_field_semantics.dart';
|
||||||
export 'presentation/views/semantics/icon_semantics.dart';
|
export 'presentation/views/semantics/icon_semantics.dart';
|
||||||
export 'presentation/views/shortcut/key_shortcut.dart';
|
export 'presentation/views/shortcut/key_shortcut.dart';
|
||||||
|
export 'presentation/views/tooltip/iframe_tooltip_overlay.dart';
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
export 'presentation/resources/assets_paths.dart';
|
export 'presentation/resources/assets_paths.dart';
|
||||||
|
|||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
import 'package:core/data/network/dio_client.dart';
|
|
||||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
|
||||||
import 'package:core/utils/app_logger.dart';
|
|
||||||
import 'package:core/utils/html/html_template.dart';
|
|
||||||
import 'package:html/dom.dart';
|
|
||||||
|
|
||||||
class RemoveTooltipLinkTransformer extends DomTransformer {
|
|
||||||
|
|
||||||
const RemoveTooltipLinkTransformer();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> process({
|
|
||||||
required Document document,
|
|
||||||
required DioClient dioClient,
|
|
||||||
Map<String, String>? mapUrlDownloadCID,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
final linkElements = document.querySelectorAll('a.${HtmlTemplate.nameClassToolTip}');
|
|
||||||
|
|
||||||
if (linkElements.isEmpty) return;
|
|
||||||
|
|
||||||
await Future.wait(linkElements.map((linkElement) async {
|
|
||||||
final classAttribute = linkElement.attributes['class'];
|
|
||||||
if (classAttribute != null) {
|
|
||||||
final newClassAttribute = classAttribute.replaceFirst(HtmlTemplate.nameClassToolTip, '');
|
|
||||||
linkElement.attributes['class'] = newClassAttribute;
|
|
||||||
}
|
|
||||||
final listSpanTag = linkElement.querySelectorAll('span.tooltiptext');
|
|
||||||
if (listSpanTag.isNotEmpty) {
|
|
||||||
for (var element in listSpanTag) {
|
|
||||||
element.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} catch (e) {
|
|
||||||
logError('$runtimeType::process:Exception = $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+1
-22
@@ -2,14 +2,12 @@ import 'package:core/data/network/dio_client.dart';
|
|||||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||||
import 'package:core/presentation/utils/html_transformer/sanitize_url.dart';
|
import 'package:core/presentation/utils/html_transformer/sanitize_url.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:core/utils/html/html_template.dart';
|
|
||||||
import 'package:html/dom.dart';
|
import 'package:html/dom.dart';
|
||||||
|
|
||||||
class SanitizeHyperLinkTagInHtmlTransformer extends DomTransformer {
|
class SanitizeHyperLinkTagInHtmlTransformer extends DomTransformer {
|
||||||
final _sanitizeUrl = SanitizeUrl();
|
final _sanitizeUrl = SanitizeUrl();
|
||||||
final bool useTooltip;
|
|
||||||
|
|
||||||
SanitizeHyperLinkTagInHtmlTransformer({this.useTooltip = false});
|
SanitizeHyperLinkTagInHtmlTransformer();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> process({
|
Future<void> process({
|
||||||
@@ -24,9 +22,6 @@ class SanitizeHyperLinkTagInHtmlTransformer extends DomTransformer {
|
|||||||
|
|
||||||
await Future.wait(elements.map((element) async {
|
await Future.wait(elements.map((element) async {
|
||||||
_sanitizeUrlResource(element);
|
_sanitizeUrlResource(element);
|
||||||
if (useTooltip) {
|
|
||||||
_addToolTipWhenHoverLink(element);
|
|
||||||
}
|
|
||||||
_addBlankForTargetProperty(element);
|
_addBlankForTargetProperty(element);
|
||||||
_addNoReferrerForRelProperty(element);
|
_addNoReferrerForRelProperty(element);
|
||||||
}));
|
}));
|
||||||
@@ -46,22 +41,6 @@ class SanitizeHyperLinkTagInHtmlTransformer extends DomTransformer {
|
|||||||
element.attributes['href'] = urlSanitized;
|
element.attributes['href'] = urlSanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addToolTipWhenHoverLink(Element element) {
|
|
||||||
final url = element.attributes['href'] ?? '';
|
|
||||||
final text = element.text;
|
|
||||||
final children = element.children;
|
|
||||||
if (children.isEmpty && text.isNotEmpty) {
|
|
||||||
final innerHtml = element.innerHtml;
|
|
||||||
final tagClass = element.attributes['class'];
|
|
||||||
if (tagClass != null) {
|
|
||||||
element.attributes['class'] = '$tagClass ${HtmlTemplate.nameClassToolTip}';
|
|
||||||
} else {
|
|
||||||
element.attributes['class'] = HtmlTemplate.nameClassToolTip;
|
|
||||||
}
|
|
||||||
element.innerHtml = '$innerHtml <span class="tooltiptext">$url</span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addBlankForTargetProperty(Element element) {
|
void _addBlankForTargetProperty(Element element) {
|
||||||
element.attributes['target'] = '_blank';
|
element.attributes['target'] = '_blank';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import 'package:core/presentation/utils/html_transformer/dom/remove_lazy_loading
|
|||||||
import 'package:core/presentation/utils/html_transformer/dom/remove_lazy_loading_image_transformers.dart';
|
import 'package:core/presentation/utils/html_transformer/dom/remove_lazy_loading_image_transformers.dart';
|
||||||
import 'package:core/presentation/utils/html_transformer/dom/remove_max_width_in_image_style_transformers.dart';
|
import 'package:core/presentation/utils/html_transformer/dom/remove_max_width_in_image_style_transformers.dart';
|
||||||
import 'package:core/presentation/utils/html_transformer/dom/remove_style_tag_outside_transformers.dart';
|
import 'package:core/presentation/utils/html_transformer/dom/remove_style_tag_outside_transformers.dart';
|
||||||
import 'package:core/presentation/utils/html_transformer/dom/remove_tooltip_link_transformers.dart';
|
|
||||||
import 'package:core/presentation/utils/html_transformer/dom/sanitize_hyper_link_tag_in_html_transformers.dart';
|
import 'package:core/presentation/utils/html_transformer/dom/sanitize_hyper_link_tag_in_html_transformers.dart';
|
||||||
import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart';
|
import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart';
|
||||||
import 'package:core/presentation/utils/html_transformer/dom/signature_transformers.dart';
|
import 'package:core/presentation/utils/html_transformer/dom/signature_transformers.dart';
|
||||||
@@ -43,8 +42,6 @@ class TransformConfiguration {
|
|||||||
) => TransformConfiguration([], textTransformers);
|
) => TransformConfiguration([], textTransformers);
|
||||||
|
|
||||||
factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.fromDomTransformers([
|
factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.fromDomTransformers([
|
||||||
if (PlatformInfo.isWeb)
|
|
||||||
const RemoveTooltipLinkTransformer(),
|
|
||||||
const SignatureTransformer(),
|
const SignatureTransformer(),
|
||||||
const RemoveCollapsedSignatureButtonTransformer(),
|
const RemoveCollapsedSignatureButtonTransformer(),
|
||||||
const NormalizeLineHeightInStyleTransformer(),
|
const NormalizeLineHeightInStyleTransformer(),
|
||||||
@@ -74,7 +71,7 @@ class TransformConfiguration {
|
|||||||
const RemoveScriptTransformer(),
|
const RemoveScriptTransformer(),
|
||||||
const BlockQuotedTransformer(),
|
const BlockQuotedTransformer(),
|
||||||
const BlockCodeTransformer(),
|
const BlockCodeTransformer(),
|
||||||
SanitizeHyperLinkTagInHtmlTransformer(useTooltip: true),
|
SanitizeHyperLinkTagInHtmlTransformer(),
|
||||||
const ImageTransformer(),
|
const ImageTransformer(),
|
||||||
const AddLazyLoadingForBackgroundImageTransformer(),
|
const AddLazyLoadingForBackgroundImageTransformer(),
|
||||||
const RemoveCollapsedSignatureButtonTransformer(),
|
const RemoveCollapsedSignatureButtonTransformer(),
|
||||||
@@ -89,8 +86,6 @@ class TransformConfiguration {
|
|||||||
);
|
);
|
||||||
|
|
||||||
factory TransformConfiguration.forPrintEmail() => TransformConfiguration.fromDomTransformers([
|
factory TransformConfiguration.forPrintEmail() => TransformConfiguration.fromDomTransformers([
|
||||||
if (PlatformInfo.isWeb)
|
|
||||||
const RemoveTooltipLinkTransformer(),
|
|
||||||
const RemoveLazyLoadingForBackgroundImageTransformer(),
|
const RemoveLazyLoadingForBackgroundImageTransformer(),
|
||||||
const RemoveLazyLoadingImageTransformer(),
|
const RemoveLazyLoadingImageTransformer(),
|
||||||
const RemoveCollapsedSignatureButtonTransformer(),
|
const RemoveCollapsedSignatureButtonTransformer(),
|
||||||
@@ -103,7 +98,7 @@ class TransformConfiguration {
|
|||||||
const RemoveScriptTransformer(),
|
const RemoveScriptTransformer(),
|
||||||
const BlockQuotedTransformer(),
|
const BlockQuotedTransformer(),
|
||||||
const BlockCodeTransformer(),
|
const BlockCodeTransformer(),
|
||||||
SanitizeHyperLinkTagInHtmlTransformer(useTooltip: PlatformInfo.isWeb),
|
SanitizeHyperLinkTagInHtmlTransformer(),
|
||||||
const ImageTransformer(),
|
const ImageTransformer(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -139,7 +134,7 @@ class TransformConfiguration {
|
|||||||
const RemoveScriptTransformer(),
|
const RemoveScriptTransformer(),
|
||||||
const BlockQuotedTransformer(),
|
const BlockQuotedTransformer(),
|
||||||
const BlockCodeTransformer(),
|
const BlockCodeTransformer(),
|
||||||
SanitizeHyperLinkTagInHtmlTransformer(useTooltip: PlatformInfo.isWeb),
|
SanitizeHyperLinkTagInHtmlTransformer(),
|
||||||
const ImageTransformer(),
|
const ImageTransformer(),
|
||||||
const AddLazyLoadingForBackgroundImageTransformer(),
|
const AddLazyLoadingForBackgroundImageTransformer(),
|
||||||
const RemoveCollapsedSignatureButtonTransformer(),
|
const RemoveCollapsedSignatureButtonTransformer(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'dart:math' as math;
|
|||||||
import 'package:core/presentation/constants/constants_ui.dart';
|
import 'package:core/presentation/constants/constants_ui.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/presentation/views/shortcut/key_shortcut.dart';
|
import 'package:core/presentation/views/shortcut/key_shortcut.dart';
|
||||||
|
import 'package:core/presentation/views/tooltip/iframe_tooltip_overlay.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:core/utils/html/html_interaction.dart';
|
import 'package:core/utils/html/html_interaction.dart';
|
||||||
import 'package:core/utils/html/html_template.dart';
|
import 'package:core/utils/html/html_template.dart';
|
||||||
@@ -51,6 +52,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
|
|||||||
final double? viewMaxHeight;
|
final double? viewMaxHeight;
|
||||||
final bool autoAdjustHeight;
|
final bool autoAdjustHeight;
|
||||||
final bool useLinkTooltipOverlay;
|
final bool useLinkTooltipOverlay;
|
||||||
|
final IframeTooltipOptions? iframeTooltipOptions;
|
||||||
|
|
||||||
const HtmlContentViewerOnWeb({
|
const HtmlContentViewerOnWeb({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -77,6 +79,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
|
|||||||
this.viewMaxHeight,
|
this.viewMaxHeight,
|
||||||
this.onIFrameKeyboardShortcutAction,
|
this.onIFrameKeyboardShortcutAction,
|
||||||
this.onIFrameClickAction,
|
this.onIFrameClickAction,
|
||||||
|
this.iframeTooltipOptions,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -103,12 +106,19 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
static const String onScrollChangedEvent = 'onScrollChanged';
|
static const String onScrollChangedEvent = 'onScrollChanged';
|
||||||
static const String onScrollEndEvent = 'onScrollEnd';
|
static const String onScrollEndEvent = 'onScrollEnd';
|
||||||
|
|
||||||
|
IframeTooltipOverlay? _tooltipOverlay;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_actualHeight = widget.heightContent;
|
_actualHeight = widget.heightContent;
|
||||||
_actualWidth = widget.widthContent;
|
_actualWidth = widget.widthContent;
|
||||||
minHeight = widget.htmlContentMinHeight;
|
minHeight = widget.htmlContentMinHeight;
|
||||||
|
if (PlatformInfo.isWebDesktop) {
|
||||||
|
_tooltipOverlay = IframeTooltipOverlay(
|
||||||
|
options: widget.iframeTooltipOptions ?? const IframeTooltipOptions(),
|
||||||
|
);
|
||||||
|
}
|
||||||
_setUpWeb();
|
_setUpWeb();
|
||||||
_onMessageSubscription = html.window.onMessage.listen(_handleMessageEvent);
|
_onMessageSubscription = html.window.onMessage.listen(_handleMessageEvent);
|
||||||
}
|
}
|
||||||
@@ -133,6 +143,12 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
} else if (_isIframeClickEventTriggered(type)) {
|
} else if (_isIframeClickEventTriggered(type)) {
|
||||||
_handleOnIFrameClickEvent(data);
|
_handleOnIFrameClickEvent(data);
|
||||||
return;
|
return;
|
||||||
|
} else if (_isIframeLinkHoverEventTriggered(type)) {
|
||||||
|
_handleOnIFrameLinkHoverEvent(data);
|
||||||
|
return;
|
||||||
|
} else if (_isIframeLinkOutEventTriggered(type)) {
|
||||||
|
_handleOnIFrameLinkOutEvent(data);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data['message'] == iframeOnLoadMessage) {
|
if (data['message'] == iframeOnLoadMessage) {
|
||||||
@@ -311,6 +327,46 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isIframeLinkHoverEventTriggered(String? type) {
|
||||||
|
return type?.contains('toDart: iframeLinkHover') == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isIframeLinkOutEventTriggered(String? type) {
|
||||||
|
return type?.contains('toDart: iframeLinkOut') == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleOnIFrameLinkHoverEvent(dynamic data) {
|
||||||
|
try {
|
||||||
|
log('$runtimeType::_handleOnIFrameLinkHoverEvent: $data');
|
||||||
|
final url = data['url'] ?? '';
|
||||||
|
final rectData = data['rect'];
|
||||||
|
|
||||||
|
if (rectData != null) {
|
||||||
|
final rect = Rect.fromLTWH(
|
||||||
|
rectData['x']?.toDouble() ?? 0,
|
||||||
|
rectData['y']?.toDouble() ?? 0,
|
||||||
|
rectData['width']?.toDouble() ?? 0,
|
||||||
|
rectData['height']?.toDouble() ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
_tooltipOverlay?.show(context, url, rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logError('$runtimeType::_handleOnIFrameLinkHoverEvent: Exception = $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleOnIFrameLinkOutEvent(dynamic data) {
|
||||||
|
try {
|
||||||
|
log('$runtimeType::_handleOnIFrameLinkOutEvent: $data');
|
||||||
|
_tooltipOverlay?.hide();
|
||||||
|
} catch (e) {
|
||||||
|
logError('$runtimeType::_handleOnIFrameLinkOutEvent: Exception = $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant HtmlContentViewerOnWeb oldWidget) {
|
void didUpdateWidget(covariant HtmlContentViewerOnWeb oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
@@ -430,7 +486,6 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
: content;
|
: content;
|
||||||
|
|
||||||
final combinedCss = [
|
final combinedCss = [
|
||||||
HtmlTemplate.tooltipLinkCss,
|
|
||||||
if (widget.enableQuoteToggle) HtmlUtils.quoteToggleStyle,
|
if (widget.enableQuoteToggle) HtmlUtils.quoteToggleStyle,
|
||||||
if (widget.disableScrolling) HtmlTemplate.disableScrollingStyleCSS,
|
if (widget.disableScrolling) HtmlTemplate.disableScrollingStyleCSS,
|
||||||
].join();
|
].join();
|
||||||
@@ -456,6 +511,8 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
HtmlInteraction.scriptHandleIframeKeyboardListener(_createdViewId),
|
HtmlInteraction.scriptHandleIframeKeyboardListener(_createdViewId),
|
||||||
if (widget.useLinkTooltipOverlay)
|
if (widget.useLinkTooltipOverlay)
|
||||||
HtmlInteraction.scriptsHandleIframeClickListener(_createdViewId),
|
HtmlInteraction.scriptsHandleIframeClickListener(_createdViewId),
|
||||||
|
if (PlatformInfo.isWebDesktop)
|
||||||
|
HtmlInteraction.scriptsHandleIframeLinkHoverListener(_createdViewId),
|
||||||
].join();
|
].join();
|
||||||
|
|
||||||
final htmlTemplate = HtmlUtils.generateHtmlDocument(
|
final htmlTemplate = HtmlUtils.generateHtmlDocument(
|
||||||
@@ -567,6 +624,10 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_htmlData = null;
|
_htmlData = null;
|
||||||
_onMessageSubscription.cancel();
|
_onMessageSubscription.cancel();
|
||||||
|
if (PlatformInfo.isWebDesktop) {
|
||||||
|
_tooltipOverlay?.hide();
|
||||||
|
_tooltipOverlay = null;
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||||
|
|
||||||
|
class IframeTooltipOptions {
|
||||||
|
final double tooltipBaseWidth;
|
||||||
|
final double tooltipHeight;
|
||||||
|
final double tooltipHorizontalMargin;
|
||||||
|
final double tooltipMarginTop;
|
||||||
|
final TextStyle? tooltipTextStyle;
|
||||||
|
|
||||||
|
const IframeTooltipOptions({
|
||||||
|
this.tooltipBaseWidth = 400,
|
||||||
|
this.tooltipHeight = 28,
|
||||||
|
this.tooltipHorizontalMargin = 12,
|
||||||
|
this.tooltipMarginTop = 4,
|
||||||
|
this.tooltipTextStyle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class IframeTooltipOverlay {
|
||||||
|
OverlayEntry? _entry;
|
||||||
|
|
||||||
|
final IframeTooltipOptions options;
|
||||||
|
|
||||||
|
IframeTooltipOverlay({
|
||||||
|
this.options = const IframeTooltipOptions(),
|
||||||
|
});
|
||||||
|
|
||||||
|
void show(
|
||||||
|
BuildContext context,
|
||||||
|
String url,
|
||||||
|
Rect rect,
|
||||||
|
) {
|
||||||
|
if (_entry != null) {
|
||||||
|
hide();
|
||||||
|
Future.microtask(() {
|
||||||
|
if (context.mounted) {
|
||||||
|
show(context, url, rect);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final overlay = Overlay.maybeOf(context);
|
||||||
|
if (overlay == null) return;
|
||||||
|
|
||||||
|
Offset containerOffset = Offset.zero;
|
||||||
|
final renderObject = context.findRenderObject();
|
||||||
|
if (renderObject is RenderBox) {
|
||||||
|
containerOffset = renderObject.localToGlobal(Offset.zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
final adjustedRect = rect.shift(containerOffset);
|
||||||
|
|
||||||
|
final viewportWidth = MediaQuery.sizeOf(context).width.toDouble();
|
||||||
|
final viewportHeight = MediaQuery.sizeOf(context).height.toDouble();
|
||||||
|
|
||||||
|
final tooltipWidth = viewportWidth < options.tooltipBaseWidth
|
||||||
|
? viewportWidth - options.tooltipHorizontalMargin * 2
|
||||||
|
: options.tooltipBaseWidth;
|
||||||
|
|
||||||
|
final showAbove = (adjustedRect.bottom +
|
||||||
|
options.tooltipHeight +
|
||||||
|
options.tooltipMarginTop) >
|
||||||
|
viewportHeight;
|
||||||
|
|
||||||
|
final tooltipTop = showAbove
|
||||||
|
? adjustedRect.top - options.tooltipHeight - options.tooltipMarginTop
|
||||||
|
: adjustedRect.bottom + options.tooltipMarginTop;
|
||||||
|
|
||||||
|
double tooltipLeft = adjustedRect.left;
|
||||||
|
|
||||||
|
if (tooltipLeft + tooltipWidth > viewportWidth) {
|
||||||
|
tooltipLeft =
|
||||||
|
viewportWidth - tooltipWidth - options.tooltipHorizontalMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooltipLeft < options.tooltipHorizontalMargin) {
|
||||||
|
tooltipLeft = options.tooltipHorizontalMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entry = OverlayEntry(
|
||||||
|
builder: (_) {
|
||||||
|
return TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 0, end: 1),
|
||||||
|
duration: const Duration(milliseconds: 130),
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
builder: (context, opacity, child) {
|
||||||
|
final slide = (showAbove ? -1 : 1) * (1 - opacity) * 8;
|
||||||
|
|
||||||
|
return Opacity(
|
||||||
|
opacity: opacity,
|
||||||
|
child: Transform.translate(
|
||||||
|
offset: Offset(0, slide),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: hide,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PositionedDirectional(
|
||||||
|
start: tooltipLeft,
|
||||||
|
top: tooltipTop,
|
||||||
|
child: PointerInterceptor(
|
||||||
|
child: Material(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: tooltipWidth,
|
||||||
|
minHeight: options.tooltipHeight,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(6),
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.15),
|
||||||
|
blurRadius: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
url,
|
||||||
|
style: options.tooltipTextStyle ??
|
||||||
|
const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
overlay.insert(_entry!);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hide() {
|
||||||
|
_entry?.remove();
|
||||||
|
_entry = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -419,4 +419,39 @@ class HtmlInteraction {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
''';
|
''';
|
||||||
|
|
||||||
|
static String scriptsHandleIframeLinkHoverListener(String viewId) => '''
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.addEventListener("mouseover", function (e) {
|
||||||
|
const target = e.target;
|
||||||
|
if (target.tagName.toLowerCase() === "a") {
|
||||||
|
const rect = target.getBoundingClientRect();
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
view: '$viewId',
|
||||||
|
type: 'toDart: iframeLinkHover',
|
||||||
|
url: target.href,
|
||||||
|
rect: {
|
||||||
|
x: rect.x,
|
||||||
|
y: rect.y,
|
||||||
|
width: rect.width,
|
||||||
|
height: rect.height
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.parent.postMessage(JSON.stringify(payload), "*");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("mouseout", function (e) {
|
||||||
|
const target = e.target;
|
||||||
|
if (target.tagName.toLowerCase() === "a") {
|
||||||
|
const payload = {
|
||||||
|
view: '$viewId',
|
||||||
|
type: 'toDart: iframeLinkOut'
|
||||||
|
};
|
||||||
|
window.parent.postMessage(JSON.stringify(payload), "*");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
''';
|
||||||
}
|
}
|
||||||
@@ -2,27 +2,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class HtmlTemplate {
|
class HtmlTemplate {
|
||||||
static const String nameClassToolTip = 'tmail-tooltip';
|
|
||||||
static const String tooltipLinkCss = '''
|
|
||||||
.$nameClassToolTip .tooltiptext {
|
|
||||||
visibility: hidden;
|
|
||||||
max-width: 400px;
|
|
||||||
background-color: black;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 5px 8px 5px 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.$nameClassToolTip:hover .tooltiptext {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
''';
|
|
||||||
|
|
||||||
static const String printDocumentCssStyle = '''
|
static const String printDocumentCssStyle = '''
|
||||||
<style>
|
<style>
|
||||||
$fontFaceStyle
|
$fontFaceStyle
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:core/presentation/constants/constants_ui.dart';
|
import 'package:core/presentation/constants/constants_ui.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/utils/theme_utils.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
||||||
|
import 'package:core/presentation/views/tooltip/iframe_tooltip_overlay.dart';
|
||||||
import 'package:core/utils/platform_info.dart';
|
import 'package:core/utils/platform_info.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -367,6 +369,11 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
fontSize: isMobileResponsive ? 16 : 14,
|
fontSize: isMobileResponsive ? 16 : 14,
|
||||||
onIFrameKeyboardShortcutAction: onIFrameKeyboardShortcutAction,
|
onIFrameKeyboardShortcutAction: onIFrameKeyboardShortcutAction,
|
||||||
onIFrameClickAction: controller.handleOnIFrameClick,
|
onIFrameClickAction: controller.handleOnIFrameClick,
|
||||||
|
iframeTooltipOptions: IframeTooltipOptions(
|
||||||
|
tooltipTextStyle: ThemeUtils.textStyleInter400.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (PlatformInfo.isIOS) {
|
} else if (PlatformInfo.isIOS) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:core/presentation/constants/constants_ui.dart';
|
import 'package:core/presentation/constants/constants_ui.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/presentation/resources/image_paths.dart';
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/utils/theme_utils.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
||||||
|
import 'package:core/presentation/views/tooltip/iframe_tooltip_overlay.dart';
|
||||||
import 'package:core/utils/platform_info.dart';
|
import 'package:core/utils/platform_info.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
@@ -58,6 +60,11 @@ class EventBodyContentWidget extends StatelessWidget {
|
|||||||
direction: AppUtils.getCurrentDirection(context),
|
direction: AppUtils.getCurrentDirection(context),
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
onIFrameClickAction: onIFrameClickAction,
|
onIFrameClickAction: onIFrameClickAction,
|
||||||
|
iframeTooltipOptions: IframeTooltipOptions(
|
||||||
|
tooltipTextStyle: ThemeUtils.textStyleInter400.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
|
import 'package:core/presentation/utils/theme_utils.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
||||||
import 'package:core/presentation/views/loading/cupertino_loading_widget.dart';
|
import 'package:core/presentation/views/loading/cupertino_loading_widget.dart';
|
||||||
|
import 'package:core/presentation/views/tooltip/iframe_tooltip_overlay.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:tmail_ui_user/features/download/domain/state/get_preview_email_eml_content_shared_state.dart';
|
import 'package:tmail_ui_user/features/download/domain/state/get_preview_email_eml_content_shared_state.dart';
|
||||||
@@ -61,6 +63,11 @@ class EmailPreviewerView extends GetWidget<EmailPreviewerController> {
|
|||||||
useDefaultFontStyle: true,
|
useDefaultFontStyle: true,
|
||||||
direction: AppUtils.getCurrentDirection(context),
|
direction: AppUtils.getCurrentDirection(context),
|
||||||
onClickHyperLinkAction: controller.onClickHyperLink,
|
onClickHyperLinkAction: controller.onClickHyperLink,
|
||||||
|
iframeTooltipOptions: IframeTooltipOptions(
|
||||||
|
tooltipTextStyle: ThemeUtils.textStyleInter400.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import 'package:core/presentation/utils/theme_utils.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_on_web_widget.dart';
|
||||||
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
import 'package:core/presentation/views/html_viewer/html_content_viewer_widget.dart';
|
||||||
|
import 'package:core/presentation/views/tooltip/iframe_tooltip_overlay.dart';
|
||||||
import 'package:core/utils/platform_info.dart';
|
import 'package:core/utils/platform_info.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/state_manager.dart';
|
import 'package:get/state_manager.dart';
|
||||||
@@ -55,6 +57,11 @@ class SignatureBuilder extends StatelessWidget {
|
|||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
disableScrolling: true,
|
disableScrolling: true,
|
||||||
autoAdjustHeight: true,
|
autoAdjustHeight: true,
|
||||||
|
iframeTooltipOptions: IframeTooltipOptions(
|
||||||
|
tooltipTextStyle: ThemeUtils.textStyleInter400.copyWith(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
|
|||||||
Reference in New Issue
Block a user