git-touch-android-ios-app/lib/widgets/markdown_view.dart

215 lines
6.6 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
2020-01-20 03:32:27 +01:00
import 'package:git_touch/models/code.dart';
2019-09-30 14:49:22 +02:00
import 'package:git_touch/models/theme.dart';
2019-09-14 11:19:33 +02:00
import 'package:git_touch/utils/utils.dart';
2020-11-04 09:41:04 +01:00
import 'package:git_touch/widgets/html_view.dart';
2019-09-30 14:49:22 +02:00
import 'package:provider/provider.dart';
import 'package:uri/uri.dart';
2019-11-06 14:56:52 +01:00
import 'package:path/path.dart' as path;
class MarkdownViewData {
final Future<String> future;
MarkdownViewData(
BuildContext context, {
2021-05-16 09:16:35 +02:00
required Future<String> Function() md,
required Future<String> Function() html,
2020-11-14 09:39:00 +01:00
}) : future = context.read<ThemeModel>().shouldUseMarkdownFlutterView
? md()
: html();
}
class MarkdownView extends StatelessWidget {
2021-05-16 09:16:35 +02:00
final MarkdownViewData? data;
MarkdownView(this.data);
@override
Widget build(BuildContext context) {
final theme = Provider.of<ThemeModel>(context);
if (data?.future == null) return Container();
2020-11-14 09:39:00 +01:00
if (theme.shouldUseMarkdownFlutterView) {
return FutureBuilder<String>(
2021-05-16 09:16:35 +02:00
future: data!.future,
2020-11-14 09:39:00 +01:00
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container();
} else {
return MarkdownFlutterView(snapshot.data);
}
},
);
} else {
return FutureBuilder<String>(
2021-05-16 09:16:35 +02:00
future: data!.future,
2020-11-14 09:39:00 +01:00
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container();
} else {
return MarkdownWebView(snapshot.data);
}
},
);
}
}
}
2020-12-12 19:00:50 +01:00
// TODO: Safari table width
2020-11-04 09:41:04 +01:00
class MarkdownWebView extends StatelessWidget {
2021-05-16 09:16:35 +02:00
final String? html;
2020-11-04 09:41:04 +01:00
MarkdownWebView(this.html);
@override
Widget build(BuildContext context) {
final theme = Provider.of<ThemeModel>(context);
2021-05-16 09:16:35 +02:00
var css = theme.markdownCss ?? '';
2020-11-08 10:21:02 +01:00
if (theme.brightness == Brightness.dark) {
css += '''
html {
background-color: #000;
}
.markdown-body {
filter:invert(100%);
}
.markdown-body img {
filter:invert(100%);
2020-12-12 19:00:50 +01:00
}
2020-11-08 10:21:02 +01:00
''';
}
2021-05-16 09:16:35 +02:00
return HtmlView(html!, cssText: css);
2020-11-04 09:41:04 +01:00
}
}
class MarkdownFlutterView extends StatelessWidget {
2021-05-16 09:16:35 +02:00
final String? text;
final List<String>? basePaths;
2020-11-04 09:41:04 +01:00
final EdgeInsetsGeometry padding;
2020-11-04 09:41:04 +01:00
MarkdownFlutterView(
this.text, {
this.basePaths,
this.padding = const EdgeInsets.all(12),
});
2021-05-16 09:16:35 +02:00
static Map<String, String?>? matchPattern(String url, String pattern) {
2019-09-30 14:49:22 +02:00
var uri = Uri.parse(url);
return UriParser(UriTemplate(pattern)).match(uri)?.parameters;
}
@override
Widget build(BuildContext context) {
2019-11-08 12:56:49 +01:00
final theme = Provider.of<ThemeModel>(context);
2020-01-20 03:32:27 +01:00
final code = Provider.of<CodeModel>(context);
2020-01-27 08:11:51 +01:00
final _basicStyle =
TextStyle(fontSize: 16, color: theme.palette.text, height: 1.5);
2019-11-08 12:56:49 +01:00
final _hStyle =
_basicStyle.copyWith(fontWeight: FontWeight.w600, height: 1.25);
2020-11-04 09:41:04 +01:00
return Container(
padding: padding,
child: MarkdownBody(
2021-05-16 09:16:35 +02:00
data: text!,
2020-11-04 09:41:04 +01:00
selectable: true,
imageBuilder: (uri, title, alt) {
if (uri.scheme == 'http' || uri.scheme == 'https') {
// TODO: svg support
// if (uri.path.endsWith('.svg')) {
// return SvgPicture.network(uri.toString());
// }
return Image.network(uri.toString());
} else {
return Container(); // TODO: relative path image
}
},
2021-05-16 07:23:31 +02:00
onTapLink: (text, url, title) {
2020-11-04 09:41:04 +01:00
final theme = context.read<ThemeModel>();
2020-10-04 14:37:23 +02:00
2020-11-04 09:41:04 +01:00
if (basePaths != null &&
2021-05-16 09:16:35 +02:00
!url!.startsWith('https://') &&
2020-11-04 09:41:04 +01:00
!url.startsWith('http://')) {
// Treat as relative path
2019-11-06 14:56:52 +01:00
2021-05-16 09:16:35 +02:00
final x = basePaths!.sublist(3).join('/');
2020-11-04 09:41:04 +01:00
var y = path.join(x, url);
if (y.startsWith('/')) y = y.substring(1);
2019-11-06 14:56:52 +01:00
2020-11-04 09:41:04 +01:00
return theme.push(context,
2021-05-16 09:16:35 +02:00
'/${basePaths![0]}/${basePaths![1]}/${basePaths![2]}?path=${y.urlencode}');
2020-11-04 09:41:04 +01:00
}
2019-11-06 14:56:52 +01:00
2020-11-04 09:41:04 +01:00
// TODO: Relative paths
2021-05-16 09:16:35 +02:00
if (url!.startsWith('https://github.com')) {
2020-11-04 09:41:04 +01:00
const matchedPaths = [
'/{owner}/{name}/pull/{number}',
'/{owner}/{name}/issues/{number}',
'/{owner}/{name}',
'/{login}'
];
for (var p in matchedPaths) {
final m = matchPattern(url, p);
if (m != null) {
return theme.push(context,
url.replaceFirst(RegExp(r'https://github.com'), '/github'));
}
}
2019-09-30 14:49:22 +02:00
}
2020-11-04 09:41:04 +01:00
launchUrl(url);
},
styleSheet: MarkdownStyleSheet(
a: _basicStyle.copyWith(color: theme.palette.primary),
p: _basicStyle,
code: _basicStyle.copyWith(
backgroundColor: theme.palette.grayBackground,
fontSize: 16 * 0.85,
height: 1.45,
fontFamily: code.fontFamilyUsed,
),
h1: _hStyle.copyWith(fontSize: 32),
h2: _hStyle.copyWith(fontSize: 24),
h3: _hStyle.copyWith(fontSize: 20),
h4: _hStyle,
h5: _hStyle.copyWith(fontSize: 14),
h6: _hStyle.copyWith(
fontSize: 16 * 0.85, color: theme.palette.tertiaryText),
em: _basicStyle.copyWith(fontStyle: FontStyle.italic),
strong: _basicStyle.copyWith(fontWeight: FontWeight.w600),
del: const TextStyle(decoration: TextDecoration.lineThrough),
blockquote: _basicStyle.copyWith(color: theme.palette.tertiaryText),
img: _basicStyle,
checkbox: _basicStyle,
blockSpacing: 16,
listIndent: 32,
listBullet: _basicStyle,
// tableHead: _basicStyle,
tableBody: _basicStyle,
// tableHeadAlign: TextAlign.center,
// tableBorder: TableBorder.all(color: Colors.grey.shade300, width: 0),
// tableColumnWidth: const FlexColumnWidth(),
// tableCellsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
blockquotePadding: EdgeInsets.symmetric(horizontal: 16),
blockquoteDecoration: BoxDecoration(
border:
Border(left: BorderSide(color: Color(0xffdfe2e5), width: 4)),
),
codeblockPadding: EdgeInsets.all(16),
codeblockDecoration: BoxDecoration(
color: theme.palette.grayBackground,
borderRadius: BorderRadius.circular(3),
),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(
width: 4,
color: theme.palette.grayBackground,
),
2019-12-27 08:31:54 +01:00
),
2019-09-13 18:31:33 +02:00
),
),
2020-11-04 09:41:04 +01:00
// syntaxHighlighter: , // TODO:
),
);
}
}