TF-105 Create HtmlContentViewer widget
This commit is contained in:
@@ -38,6 +38,7 @@ export 'presentation/views/context_menu/simple_context_menu_action_builder.dart'
|
|||||||
export 'presentation/views/dialog/loading_dialog_builder.dart';
|
export 'presentation/views/dialog/loading_dialog_builder.dart';
|
||||||
export 'presentation/views/dialog/downloading_file_dialog_builder.dart';
|
export 'presentation/views/dialog/downloading_file_dialog_builder.dart';
|
||||||
export 'presentation/views/background/background_widget_builder.dart';
|
export 'presentation/views/background/background_widget_builder.dart';
|
||||||
|
export 'presentation/views/html_viewer/html_content_viewer_widget.dart';
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
export 'presentation/resources/assets_paths.dart';
|
export 'presentation/resources/assets_paths.dart';
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
import 'package:core/core.dart';
|
||||||
|
import 'package:core/presentation/utils/html_transformer/html_transform.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
|
|
||||||
|
class _HtmlGenerationArguments {
|
||||||
|
final String message;
|
||||||
|
final bool blockExternalImages;
|
||||||
|
|
||||||
|
const _HtmlGenerationArguments(
|
||||||
|
this.message,
|
||||||
|
this.blockExternalImages,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HtmlGenerationResult {
|
||||||
|
final String? html;
|
||||||
|
final String? errorDetails;
|
||||||
|
|
||||||
|
const _HtmlGenerationResult.success(this.html) : errorDetails = null;
|
||||||
|
|
||||||
|
const _HtmlGenerationResult.error(this.errorDetails) : this.html = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HtmlContentViewer extends StatefulWidget {
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
final bool blockExternalImages;
|
||||||
|
|
||||||
|
/// Is notified about any errors that might occur
|
||||||
|
final void Function(Object? exception, StackTrace? stackTrace)? onError;
|
||||||
|
|
||||||
|
/// Register this callback if you want a reference to the [InAppWebViewController].
|
||||||
|
final void Function(InAppWebViewController controller)? onWebViewCreated;
|
||||||
|
|
||||||
|
const HtmlContentViewer({
|
||||||
|
Key? key,
|
||||||
|
required this.message,
|
||||||
|
this.blockExternalImages = false,
|
||||||
|
this.onError,
|
||||||
|
this.onWebViewCreated,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _HtmlContentViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HtmlContentViewState extends State<HtmlContentViewer> {
|
||||||
|
|
||||||
|
String? _htmlData;
|
||||||
|
bool? _wereExternalImagesBlocked;
|
||||||
|
bool _isGenerating = false;
|
||||||
|
double? _webViewContentHeight;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_generateHtml(widget.blockExternalImages);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _generateHtml(bool blockExternalImages) async {
|
||||||
|
_wereExternalImagesBlocked = blockExternalImages;
|
||||||
|
_isGenerating = true;
|
||||||
|
final args = _HtmlGenerationArguments(
|
||||||
|
widget.message,
|
||||||
|
blockExternalImages,
|
||||||
|
);
|
||||||
|
final result = await compute(_generateHtmlImpl, args);
|
||||||
|
_htmlData = result.html;
|
||||||
|
if (_htmlData == null) {
|
||||||
|
final onError = widget.onError;
|
||||||
|
if (onError != null) {
|
||||||
|
onError(result.errorDetails, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isGenerating = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static _HtmlGenerationResult _generateHtmlImpl(_HtmlGenerationArguments args) {
|
||||||
|
try {
|
||||||
|
final htmlTransform = HtmlTransform(args.message);
|
||||||
|
final html = htmlTransform.transformToHtml(
|
||||||
|
blockExternalImages: args.blockExternalImages,
|
||||||
|
);
|
||||||
|
return _HtmlGenerationResult.success(html);
|
||||||
|
} catch (e, s) {
|
||||||
|
String errorDetails = e.toString() + '\n\n' + s.toString();
|
||||||
|
return _HtmlGenerationResult.error(errorDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_isGenerating) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
child: CircularProgressIndicator(color: AppColor.primaryColor)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (widget.blockExternalImages != _wereExternalImagesBlocked) {
|
||||||
|
_generateHtml(widget.blockExternalImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
return SizedBox(
|
||||||
|
height: _webViewContentHeight ?? size.height,
|
||||||
|
width: size.width,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 16),
|
||||||
|
child: _buildWebView(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildWebView() {
|
||||||
|
if (_htmlData == null) {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
|
||||||
|
return InAppWebView(
|
||||||
|
key: ValueKey(_htmlData),
|
||||||
|
initialData: InAppWebViewInitialData(data: _htmlData!),
|
||||||
|
initialOptions: InAppWebViewGroupOptions(
|
||||||
|
crossPlatform: InAppWebViewOptions(
|
||||||
|
useShouldOverrideUrlLoading: true,
|
||||||
|
verticalScrollBarEnabled: false,
|
||||||
|
),
|
||||||
|
android: AndroidInAppWebViewOptions(
|
||||||
|
useHybridComposition: true,
|
||||||
|
),
|
||||||
|
ios: IOSInAppWebViewOptions(
|
||||||
|
allowsInlineMediaPlayback: true,
|
||||||
|
enableViewportScale: true,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
onWebViewCreated: widget.onWebViewCreated,
|
||||||
|
onLoadStop: (controller, uri) async {
|
||||||
|
var scrollHeight = (await controller.evaluateJavascript(source: 'document.body.scrollHeight'));
|
||||||
|
if (scrollHeight != null) {
|
||||||
|
final scrollWidth = (await controller.evaluateJavascript(source: 'document.body.scrollWidth'));
|
||||||
|
final size = MediaQuery.of(context).size;
|
||||||
|
if (scrollWidth > size.width) {
|
||||||
|
var scale = (size.width / scrollWidth);
|
||||||
|
if (scale < 0.2) {
|
||||||
|
scale = 0.2;
|
||||||
|
}
|
||||||
|
await controller.zoomBy(zoomFactor: scale, iosAnimated: true);
|
||||||
|
scrollHeight = (scrollHeight * scale).ceil();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_webViewContentHeight = double.tryParse('${scrollHeight + 10.0}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gestureRecognizers: {
|
||||||
|
Factory<LongPressGestureRecognizer>(() => LongPressGestureRecognizer()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,13 @@ dependencies:
|
|||||||
# getwidget
|
# getwidget
|
||||||
getwidget: 2.0.4
|
getwidget: 2.0.4
|
||||||
|
|
||||||
|
# Used to fix bugs in the official version of flutter_inappwebview: https://githubmemory.com/repo/pichillilorenzo/flutter_inappwebview/issues/873
|
||||||
|
# flutter_inappwebview
|
||||||
|
flutter_inappwebview:
|
||||||
|
git:
|
||||||
|
url: git://github.com/robert-virkus/flutter_inappwebview.git
|
||||||
|
ref: master
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Reference in New Issue
Block a user