TF-330 Fix can not scroll email content in browser

This commit is contained in:
dab246
2022-03-17 16:24:40 +07:00
committed by Dat H. Pham
parent b945926b24
commit be6006510e
3 changed files with 94 additions and 65 deletions
@@ -6,20 +6,22 @@ import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/views/html_viewer/html_viewer_controller_for_web.dart'; import 'package:core/presentation/views/html_viewer/html_viewer_controller_for_web.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'dart:developer' as developer;
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;
import 'package:core/presentation/utils/shims/dart_ui.dart' as ui; import 'package:core/presentation/utils/shims/dart_ui.dart' as ui;
import 'package:core/utils/app_logger.dart' as logger;
class HtmlContentViewerOnWeb extends StatefulWidget { class HtmlContentViewerOnWeb extends StatefulWidget {
final String contentHtml; final String contentHtml;
final double widthContent; final double widthContent;
final double heightContent;
final HtmlViewerControllerForWeb controller; final HtmlViewerControllerForWeb controller;
const HtmlContentViewerOnWeb({ const HtmlContentViewerOnWeb({
Key? key, Key? key,
required this.contentHtml, required this.contentHtml,
required this.widthContent, required this.widthContent,
required this.heightContent,
required this.controller, required this.controller,
}) : super(key: key); }) : super(key: key);
@@ -33,16 +35,20 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
late String createdViewId; late String createdViewId;
/// The actual height of the editor, used to automatically set the height /// The actual height of the editor, used to automatically set the height
late double actualHeight; late double actualHeight;
/// The actual width of the editor, used to automatically set the width
late double actualWidth;
Future<bool>? webInit; Future<bool>? webInit;
String? _htmlData; String? _htmlData;
bool _isLoading = true; bool _isLoading = true;
int minHeight = 100; int minHeight = 100;
int minWidth = 300;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
actualHeight = 400; actualHeight = widget.heightContent;
actualWidth = widget.widthContent;
createdViewId = _getRandString(10); createdViewId = _getRandString(10);
widget.controller.viewId = createdViewId; widget.controller.viewId = createdViewId;
_setUpWeb(); _setUpWeb();
@@ -67,6 +73,10 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
var height = document.body.scrollHeight; var height = document.body.scrollHeight;
window.parent.postMessage(JSON.stringify({"view": "$createdViewId", "type": "toDart: htmlHeight", "height": height}), "*"); window.parent.postMessage(JSON.stringify({"view": "$createdViewId", "type": "toDart: htmlHeight", "height": height}), "*");
} }
if (data["type"].includes("getWidth")) {
var width = document.body.scrollWidth;
window.parent.postMessage(JSON.stringify({"view": "$createdViewId", "type": "toDart: htmlWidth", "width": width}), "*");
}
if (data["type"].includes("execCommand")) { if (data["type"].includes("execCommand")) {
if (data["argument"] === null) { if (data["argument"] === null) {
document.execCommand(data["command"], false); document.execCommand(data["command"], false);
@@ -90,7 +100,7 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
#editor { #editor {
outline: 0px solid transparent; outline: 0px solid transparent;
min-height: ${minHeight}px; min-height: ${minHeight}px;
min-width: 300px; min-width: ${minWidth}px;
color: #000000; color: #000000;
font-family: verdana; font-family: verdana;
} }
@@ -122,30 +132,30 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
_htmlData = _generateHtmlDocument(widget.contentHtml); _htmlData = _generateHtmlDocument(widget.contentHtml);
final iframe = html.IFrameElement() final iframe = html.IFrameElement()
..width = widget.widthContent.toString() ..width = actualWidth.toString()
..height = actualHeight.toString() ..height = actualHeight.toString()
..srcdoc = _htmlData ?? '' ..srcdoc = _htmlData ?? ''
..style.border = 'none' ..style.border = 'none'
..style.overflow = 'hidden' ..style.overflow = 'hidden'
..onLoad.listen((event) async { ..onLoad.listen((event) async {
var data = <String, Object>{'type': 'toIframe: getHeight'}; final dataGetHeight = <String, Object>{'type': 'toIframe: getHeight', 'view' : createdViewId};
data['view'] = createdViewId; final dataGetWidth = <String, Object>{'type': 'toIframe: getWidth', 'view' : createdViewId};
final jsonEncoder = JsonEncoder();
var jsonStr = jsonEncoder.convert(data);
html.window.postMessage(jsonStr, '*');
html.window.onBeforeUnload.listen((event) { final jsonEncoder = JsonEncoder();
event.preventDefault(); final jsonGetHeight = jsonEncoder.convert(dataGetHeight);
}); final jsonGetWidth = jsonEncoder.convert(dataGetWidth);
logger.log('HtmlContentViewerOnWeb() | onLoad | jsonGetHeight: $jsonGetHeight');
logger.log('HtmlContentViewerOnWeb() | onLoad | jsonGetWidth: $jsonGetWidth');
html.window.postMessage(jsonGetHeight, '*');
html.window.postMessage(jsonGetWidth, '*');
html.window.onMessage.listen((event) { html.window.onMessage.listen((event) {
var data = json.decode(event.data); var data = json.decode(event.data);
developer.log('onMessage(): data: $data', name: 'HtmlContentViewerOnWeb'); if (data['type'] != null && data['type'].contains('toDart: htmlHeight') && data['view'] == createdViewId) {
if (data['type'] != null &&
data['type'].contains('toDart: htmlHeight') &&
data['view'] == createdViewId) {
final docHeight = data['height'] ?? actualHeight; final docHeight = data['height'] ?? actualHeight;
developer.log('onMessage(): docHeight: $docHeight', name: 'HtmlContentViewerOnWeb'); logger.log('HtmlContentViewerOnWeb() | onMessage | docHeight: $docHeight');
if (docHeight != null && mounted) { if (docHeight != null && mounted) {
final scrollHeightWithBuffer = docHeight + 30.0; final scrollHeightWithBuffer = docHeight + 30.0;
if (scrollHeightWithBuffer > minHeight) { if (scrollHeightWithBuffer > minHeight) {
@@ -161,9 +171,20 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
}); });
} }
} }
if (data['type'] != null &&
data['type'].contains('toDart: onChangeContent') && if (data['type'] != null && data['type'].contains('toDart: htmlWidth') && data['view'] == createdViewId) {
data['view'] == createdViewId) { final docWidth = data['width'] ?? actualWidth;
logger.log('HtmlContentViewerOnWeb() | onMessage | docWidth: $docWidth');
if (docWidth != null && mounted) {
if (docWidth > minWidth) {
setState(() {
actualWidth = docWidth;
});
}
}
}
if (data['type'] != null && data['type'].contains('toDart: onChangeContent') && data['view'] == createdViewId) {
if (Scrollable.of(context) != null) { if (Scrollable.of(context) != null) {
Scrollable.of(context)!.position.ensureVisible( Scrollable.of(context)!.position.ensureVisible(
context.findRenderObject()!, context.findRenderObject()!,
@@ -174,9 +195,6 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
}); });
}); });
iframe.setAttribute('scrolling', 'no');
iframe.setAttribute('seamless', 'seamless');
ui.platformViewRegistry.registerViewFactory(createdViewId, (int viewId) => iframe); ui.platformViewRegistry.registerViewFactory(createdViewId, (int viewId) => iframe);
if (mounted) { if (mounted) {
@@ -193,9 +211,13 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
children: [ children: [
SizedBox( SizedBox(
height: actualHeight, height: actualHeight,
width: widget.widthContent, width: actualWidth,
child: _buildWebView(), child: _buildWebView(),
), ),
PointerInterceptor(child: SizedBox(
height: actualHeight,
width: actualWidth,
)),
if (_isLoading) _buildLoadingView() if (_isLoading) _buildLoadingView()
], ],
); );
@@ -222,10 +244,10 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb> {
future: webInit, future: webInit,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return PointerInterceptor(child: HtmlElementView( return HtmlElementView(
key: ValueKey(htmlData), key: ValueKey(htmlData),
viewType: createdViewId, viewType: createdViewId,
)); );
} else { } else {
return Container(); return Container();
} }
+34 -35
View File
@@ -143,30 +143,33 @@ class EmailView extends GetView {
padding: EdgeInsets.only(bottom: 16, left: 16, right: 16, top: 10), padding: EdgeInsets.only(bottom: 16, left: 16, right: 16, top: 10),
alignment: Alignment.center, alignment: Alignment.center,
color: Colors.white, color: Colors.white,
child: Column( child: LayoutBuilder(
mainAxisAlignment: MainAxisAlignment.center, builder: (BuildContext context, BoxConstraints constraints) {
crossAxisAlignment: CrossAxisAlignment.start, return Column(
mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center,
children: [ crossAxisAlignment: CrossAxisAlignment.start,
Row( mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.end, children: [
children: [ Row(
Expanded(child: _buildEmailSubject()), crossAxisAlignment: CrossAxisAlignment.end,
_buildEmailTime(context), children: [
]), Expanded(child: _buildEmailSubject()),
_buildDivider(edgeInsets: EdgeInsets.only(top: 16)), _buildEmailTime(context),
Obx(() => emailController.currentEmail != null ]),
? _buildEmailAddress( _buildDivider(edgeInsets: EdgeInsets.only(top: 16)),
context, Obx(() => emailController.currentEmail != null
emailController.currentEmail!, ? _buildEmailAddress(
emailController.emailAddressExpandMode.value, context,
emailController.isDisplayFullEmailAddress.value) emailController.currentEmail!,
: SizedBox.shrink()), emailController.emailAddressExpandMode.value,
_buildDivider(edgeInsets: EdgeInsets.only(top: 4)), emailController.isDisplayFullEmailAddress.value)
_buildAttachments(context), : SizedBox.shrink()),
_buildListEmailContent(), _buildDivider(edgeInsets: EdgeInsets.only(top: 4)),
], _buildAttachments(context),
) _buildListEmailContent(context, constraints),
],
);
})
); );
} }
@@ -445,7 +448,7 @@ class EmailView extends GetView {
); );
} }
Widget _buildListEmailContent() { Widget _buildListEmailContent(BuildContext context, BoxConstraints constraints) {
return Obx(() => emailController.viewState.value.fold( return Obx(() => emailController.viewState.value.fold(
(failure) => SizedBox.shrink(), (failure) => SizedBox.shrink(),
(success) { (success) {
@@ -453,18 +456,14 @@ class EmailView extends GetView {
return EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils); return EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils);
} else { } else {
if (emailController.emailContents.isNotEmpty) { if (emailController.emailContents.isNotEmpty) {
return ListView.builder( return Column(children: [
primary: false, ...emailController.emailContents.map((content) => EmailContentItemBuilder(
shrinkWrap: true,
padding: EdgeInsets.only(top: 10),
key: Key('list_email_content'),
itemCount: emailController.emailContents.length,
itemBuilder: (context, index) =>
EmailContentItemBuilder(
context, context,
emailController.emailContents[index], content,
constraints,
loadingWidget: EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils) loadingWidget: EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils)
).build()); ).build()).toList(),
]);
} else { } else {
return SizedBox.shrink(); return SizedBox.shrink();
} }
@@ -9,10 +9,12 @@ class EmailContentItemBuilder {
final BuildContext _context; final BuildContext _context;
final EmailContent _emailContent; final EmailContent _emailContent;
final Widget? loadingWidget; final Widget? loadingWidget;
final BoxConstraints _constraints;
EmailContentItemBuilder( EmailContentItemBuilder(
this._context, this._context,
this._emailContent, this._emailContent,
this._constraints,
{ {
this.loadingWidget this.loadingWidget
} }
@@ -31,13 +33,19 @@ class EmailContentItemBuilder {
} }
Widget _buildItem() { Widget _buildItem() {
log('EmailContentItemBuilder() | maxWidth: ${_constraints.maxWidth}');
log('EmailContentItemBuilder() | heightScreen: ${MediaQuery.of(_context).size.height}');
switch(_emailContent.type) { switch(_emailContent.type) {
case EmailContentType.textHtml: case EmailContentType.textHtml:
if (kIsWeb) { if (kIsWeb) {
return HtmlContentViewerOnWeb( return SingleChildScrollView(
widthContent: MediaQuery.of(_context).size.width, scrollDirection: Axis.horizontal,
contentHtml: _emailContent.content, child: HtmlContentViewerOnWeb(
controller: HtmlViewerControllerForWeb()); widthContent: _constraints.maxWidth,
heightContent: MediaQuery.of(_context).size.height,
contentHtml: _emailContent.content,
controller: HtmlViewerControllerForWeb()),
);
} else { } else {
return HtmlContentViewer( return HtmlContentViewer(
widthContent: MediaQuery.of(_context).size.width, widthContent: MediaQuery.of(_context).size.width,