mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-28 18:03:14 +08:00
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:
@ -64,5 +64,12 @@
|
||||
<array>
|
||||
<string>itms</string>
|
||||
</array>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
*/
|
92
lib/widgets/katex_widget.dart
Normal file
92
lib/widgets/katex_widget.dart
Normal 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));
|
||||
}
|
||||
}
|
10
pubspec.lock
10
pubspec.lock
@ -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:
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user