109 lines
3.5 KiB
Dart
109 lines
3.5 KiB
Dart
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:html/dom.dart' as dom;
|
|
import 'package:surface/widgets/universal_image.dart';
|
|
import 'package:url_launcher/url_launcher_string.dart';
|
|
|
|
List<Widget> parseHtmlToWidgets(
|
|
BuildContext context, Iterable<dom.Element>? elements) {
|
|
if (elements == null) return [];
|
|
|
|
final List<Widget> widgets = [];
|
|
|
|
for (final node in elements) {
|
|
switch (node.localName) {
|
|
case 'h1':
|
|
case 'h2':
|
|
case 'h3':
|
|
case 'h4':
|
|
case 'h5':
|
|
case 'h6':
|
|
widgets.add(Text(node.text.trim(),
|
|
style: Theme.of(context).textTheme.titleMedium));
|
|
break;
|
|
case 'p':
|
|
if (node.text.trim().isEmpty) continue;
|
|
widgets.add(
|
|
Text.rich(
|
|
TextSpan(
|
|
text: node.text.trim(),
|
|
children: [
|
|
for (final child in node.children)
|
|
switch (child.localName) {
|
|
'a' => TextSpan(
|
|
text: child.text.trim(),
|
|
style: const TextStyle(
|
|
decoration: TextDecoration.underline),
|
|
recognizer: TapGestureRecognizer()
|
|
..onTap = () {
|
|
launchUrlString(child.attributes['href']!);
|
|
},
|
|
),
|
|
_ => TextSpan(text: child.text.trim()),
|
|
},
|
|
],
|
|
),
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
),
|
|
);
|
|
break;
|
|
case 'a':
|
|
// drop single link
|
|
break;
|
|
case 'div':
|
|
// ignore div text, normally it is not meaningful
|
|
widgets.addAll(parseHtmlToWidgets(context, node.children));
|
|
break;
|
|
case 'hr':
|
|
widgets.add(const Divider());
|
|
break;
|
|
case 'img':
|
|
var src = node.attributes['src'];
|
|
if (src == null) break;
|
|
final width = double.tryParse(node.attributes['width'] ?? 'null');
|
|
final height = double.tryParse(node.attributes['height'] ?? 'null');
|
|
final ratio = width != null && height != null ? width / height : 1.0;
|
|
if (src.startsWith('//')) {
|
|
src = 'https:$src';
|
|
} else if (!src.startsWith('http')) {
|
|
// final baseUri = Uri.parse(_article!.url);
|
|
// final baseUrl = '${baseUri.scheme}://${baseUri.host}';
|
|
src = src;
|
|
}
|
|
widgets.add(
|
|
AspectRatio(
|
|
aspectRatio: ratio,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
border: Border.all(
|
|
color: Theme.of(context).dividerColor,
|
|
width: 1,
|
|
),
|
|
),
|
|
height: height ?? double.infinity,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
|
child: Container(
|
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
|
child: AutoResizeUniversalImage(
|
|
src,
|
|
fit: width != null && height != null
|
|
? BoxFit.cover
|
|
: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
break;
|
|
default:
|
|
widgets.addAll(parseHtmlToWidgets(context, node.children));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return widgets;
|
|
}
|