Add an experimental Katex rendering Widget

Related to #125

This still needs to be integrated into the Flutter Markdown renderer.
But the good news is that this works!

It currently requires network access to download the katex scripts. It
does NOT send the katex string to any server. The rendering is performed
in the app.
This commit is contained in:
Vishesh Handa
2020-05-19 18:36:16 +02:00
parent 89d23242a0
commit ce201aa390
5 changed files with 104 additions and 289 deletions

View File

@ -64,5 +64,12 @@
<array>
<string>itms</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
</dict>
</plist>

View File

@ -1,281 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
const kAndroidUserAgent =
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36';
String selectedUrl = 'https://gitjournal.io/test_katex.html';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final flutterWebViewPlugin = FlutterWebviewPlugin();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter WebView Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/': (_) => const MyHomePage(title: 'Flutter WebView Demo'),
'/widget': (_) {
return WebviewScaffold(
url: selectedUrl,
appBar: AppBar(
title: const Text('Widget WebView'),
),
withZoom: true,
withLocalStorage: true,
hidden: true,
initialChild: Container(
color: Colors.redAccent,
child: const Center(
child: Text('Waiting.....'),
),
),
bottomNavigationBar: BottomAppBar(
child: Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: flutterWebViewPlugin.goBack,
),
IconButton(
icon: const Icon(Icons.arrow_forward_ios),
onPressed: () {
print("");
flutterWebViewPlugin.goForward();
},
),
IconButton(
icon: const Icon(Icons.autorenew),
onPressed: () {
print("");
flutterWebViewPlugin.reload();
},
),
],
),
),
);
},
},
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// Instance of WebView plugin
final flutterWebViewPlugin = FlutterWebviewPlugin();
// On destroy stream
StreamSubscription _onDestroy;
// On urlChanged stream
StreamSubscription<String> _onUrlChanged;
// On urlChanged stream
StreamSubscription<WebViewStateChanged> _onStateChanged;
StreamSubscription<WebViewHttpError> _onHttpError;
StreamSubscription<double> _onProgressChanged;
StreamSubscription<double> _onScrollYChanged;
StreamSubscription<double> _onScrollXChanged;
final _urlCtrl = TextEditingController(text: selectedUrl);
final _codeCtrl = TextEditingController(text: 'window.navigator.userAgent');
final _scaffoldKey = GlobalKey<ScaffoldState>();
final _history = [];
Uint8List imageData = Uint8List(0);
@override
void initState() {
super.initState();
flutterWebViewPlugin.close();
_urlCtrl.addListener(() {
selectedUrl = _urlCtrl.text;
});
// Add a listener to on destroy WebView, so you can make came actions.
_onDestroy = flutterWebViewPlugin.onDestroy.listen((_) {
if (mounted) {
// Actions like show a info toast.
_scaffoldKey.currentState
.showSnackBar(const SnackBar(content: Text('Webview Destroyed')));
}
});
// Add a listener to on url changed
_onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((String url) {
if (mounted) {
setState(() {
_history.add('onUrlChanged: $url');
});
}
});
_onScrollYChanged =
flutterWebViewPlugin.onScrollYChanged.listen((double y) {
if (mounted) {
setState(() {
_history.add('Scroll in Y Direction: $y');
});
}
});
_onScrollXChanged =
flutterWebViewPlugin.onScrollXChanged.listen((double x) {
if (mounted) {
setState(() {
_history.add('Scroll in X Direction: $x');
});
}
});
_onStateChanged =
flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) {
if (mounted) {
setState(() {
_history.add('onStateChanged: ${state.type} ${state.url}');
});
}
});
_onHttpError =
flutterWebViewPlugin.onHttpError.listen((WebViewHttpError error) {
if (mounted) {
setState(() {
_history.add('onHttpError: ${error.code} ${error.url}');
});
}
});
}
@override
void dispose() {
// Every listener should be canceled, the same should be done with this stream.
_onDestroy.cancel();
_onUrlChanged.cancel();
_onStateChanged.cancel();
_onHttpError.cancel();
_onProgressChanged.cancel();
_onScrollXChanged.cancel();
_onScrollYChanged.cancel();
flutterWebViewPlugin.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (imageData.isNotEmpty) Image.memory(imageData),
Container(
padding: const EdgeInsets.all(24.0),
child: TextField(controller: _urlCtrl),
),
RaisedButton(
onPressed: () {
flutterWebViewPlugin.launch(selectedUrl, hidden: true);
},
child: const Text('Open "hidden" Webview'),
),
Container(
padding: const EdgeInsets.all(24.0),
child: TextField(controller: _codeCtrl),
),
RaisedButton(
onPressed: () {
var js =
"""katex.render("\\\\pm\\\\sqrt{a^2 + b^2}", document.body, {
throwOnError: false
});""";
final future = flutterWebViewPlugin.evalJavascript(js);
future.then((String result) async {
print("Eval done $result");
setState(() {
_history.add('eval: $result');
});
var _imageData = await flutterWebViewPlugin.takeScreenshot();
print("Got $_imageData");
setState(() {
imageData = _imageData;
});
//flutterWebViewPlugin.close();
});
},
child: const Text('Eval javascript katex'),
),
RaisedButton(
onPressed: () {
setState(() {
print("");
_history.clear();
});
flutterWebViewPlugin.close();
},
child: const Text('Close'),
),
RaisedButton(
onPressed: () {
flutterWebViewPlugin.getCookies().then((m) {
setState(() {
_history.add('cookies: $m');
});
});
},
child: const Text('Cookies'),
),
Text(_history.join('\n'))
],
),
),
);
}
}
/*
html2canvas(document.body).then(function(canvas) {
var img = canvas.toDataURL("image/png");
console.log(img);
});
*/

View File

@ -0,0 +1,92 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:path/path.dart' as p;
class KatexWidget extends StatefulWidget {
final String input;
KatexWidget(this.input, {Key key}) : super(key: key);
@override
_KatexWidgetState createState() => _KatexWidgetState();
}
class _KatexWidgetState extends State<KatexWidget> {
String imagePath;
JavascriptChannel jsChannel;
final flutterWebViewPlugin = FlutterWebviewPlugin();
final selectedUrl = 'https://gitjournal.io/test_katex.html';
@override
void initState() {
super.initState();
jsChannel = JavascriptChannel(
name: 'Print',
onMessageReceived: (JavascriptMessage message) {
print("-----JS CHANNEL ----");
print(message.message);
var uri = UriData.parse(message.message);
var tmpFile = p.join(Directory.systemTemp.path, "katex.png");
File(tmpFile).writeAsBytesSync(uri.contentAsBytes());
setState(() {
print("State has been set");
imagePath = tmpFile;
});
},
);
flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) {
if (!mounted) return;
if (state.type == WebViewState.finishLoad) {
_renderKatex();
}
});
flutterWebViewPlugin.close();
flutterWebViewPlugin.launch(
selectedUrl,
hidden: true,
javascriptChannels: {jsChannel},
withJavascript: true,
);
}
@override
void dispose() {
flutterWebViewPlugin.dispose();
super.dispose();
}
void _renderKatex() {
var katex = widget.input;
var js = """katex.render("$katex", document.body, {
throwOnError: false
});
html2canvas(document.body).then(function(canvas) {
var img = canvas.toDataURL("image/png");
Print.postMessage(img);
});
""";
flutterWebViewPlugin.evalJavascript(js);
}
@override
Widget build(BuildContext context) {
if (imagePath == null) {
return Container();
}
print("Building Network Image");
return Image.file(File(imagePath));
}
}

View File

@ -318,12 +318,10 @@ packages:
flutter_webview_plugin:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "045bd6357ce81162d05e367133a95a53ad291451"
url: "https://github.com/breez/flutter_webview_plugin.git"
source: git
version: "0.3.0+2"
name: flutter_webview_plugin
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.11"
font_awesome_flutter:
dependency: "direct main"
description:

View File

@ -50,8 +50,7 @@ dependencies:
ssh_key:
git: https://github.com/GitJournal/ssh_key.git
isolate: ^2.0.3
flutter_webview_plugin:
git: https://github.com/breez/flutter_webview_plugin.git
flutter_webview_plugin: ^0.3.11
image_picker: ^0.6.6+1
easy_localization: ^2.2.1
easy_localization_loader: ^0.0.2