feat: render warnings and fix text render

This commit is contained in:
Matyáš Caras 2023-03-28 18:05:05 +02:00
parent f81b6556c4
commit c5646cff15
6 changed files with 156 additions and 43 deletions

View file

@ -5,6 +5,8 @@ import 'package:html/dom.dart' as dom;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:voyagehandbook/api/classes.dart'; import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/util/styles.dart'; import 'package:voyagehandbook/util/styles.dart';
import 'package:voyagehandbook/util/widgets/warning.dart';
import 'package:html_unescape/html_unescape_small.dart';
final _ignoredTags = ["style", "script"]; final _ignoredTags = ["style", "script"];
@ -156,35 +158,12 @@ List<Widget> _renderSection(dom.Element sec, double height, double width) {
switch (element.localName) { switch (element.localName) {
case "p": case "p":
case "link": case "link":
var paraSpans = <TextSpan>[]; out.add(
var input = element.innerHtml RichText(
.replaceAll(RegExp(r"<(?!(?:\/b)|(?:\/i)|b|i).+?>"), "") text: TextSpan(children: _renderText(element.innerHtml)),
.replaceAll("\\n", "\r");
var noFormatting =
input.split(RegExp(r"(?:<b .+?>.+?<\/b>)|<i .+?>.+?<\/i>"));
var needToFormat = RegExp(r"(?:<b .+?>.+?<\/b>)|<i .+?>.+?<\/i>")
.allMatches(input)
.toList();
for (var s in needToFormat) {
paraSpans.add(TextSpan(
text: noFormatting[
needToFormat.indexOf(s)])); // add text before styled
var raw = s.group(0)!;
paraSpans.add(
TextSpan(
text: RegExp(r">(.+?)<").firstMatch(raw)!.group(1),
style: TextStyle(
fontWeight: (raw.contains("<b")) ? FontWeight.bold : null,
fontStyle: (raw.contains("<i")) ? FontStyle.italic : null,
),
),
); // add styled
}
paraSpans.add(TextSpan(text: noFormatting.last)); // add last
out.add(RichText(
text: TextSpan(children: paraSpans),
textAlign: TextAlign.justify, textAlign: TextAlign.justify,
)); // add paragraph spans as single rich text ),
); // add paragraph spans as single rich text
out.add( out.add(
const SizedBox( const SizedBox(
height: 5, height: 5,
@ -234,16 +213,30 @@ List<Widget> _renderSection(dom.Element sec, double height, double width) {
textAlign: TextAlign.center, textAlign: TextAlign.center,
)); ));
} }
out.add(const SizedBox( out.add(
const SizedBox(
height: 10, height: 10,
)); ),
);
break; break;
case "section": case "section":
out.addAll(_renderSection(element, height, width)); out.addAll(_renderSection(element, height, width));
break; break;
case "div":
if (element.attributes["class"] != null &&
element.attributes["class"] == "pp_cautionbox") {
out.add(_renderWarning(element));
out.add(
const SizedBox(
height: 10,
),
);
}
break;
default: default:
break; break;
} }
element.remove();
} }
out.add( out.add(
@ -253,3 +246,55 @@ List<Widget> _renderSection(dom.Element sec, double height, double width) {
); );
return out; return out;
} }
// used to render warning box
Widget _renderWarning(dom.Element element) {
var content = <TextSpan>[];
for (var tr in element
.getElementsByTagName("table")
.first
.getElementsByTagName("tbody")
.first
.getElementsByTagName("tr")) {
for (var e in tr.getElementsByTagName("td")) {
// Get to table data
content.addAll(
_renderText(
e.innerHtml.replaceAll(RegExp(r'<img.+?\/?>'), ""),
),
);
}
}
return Warning(content: content);
}
/// Used to render basic text with bold and italic formatting
List<TextSpan> _renderText(String innerHtml) {
var unescape = HtmlUnescape();
innerHtml = unescape.convert(innerHtml);
var content = <TextSpan>[];
var input = innerHtml
.replaceAll(RegExp(r"<(?!(?:\/b)|(?:\/i)|b|i).+?>", dotAll: true), "")
.replaceAll("\\n", "\r");
var noFormatting =
input.split(RegExp(r"(?:<b.*?>.+?<\/b>)|<i.*?>.+?<\/i>", dotAll: true));
var needToFormat = RegExp(r"(?:<b.*?>.+?<\/b>)|<i.*?>.+?<\/i>", dotAll: true)
.allMatches(input)
.toList();
for (var s in needToFormat) {
content.add(TextSpan(
text: noFormatting[needToFormat.indexOf(s)])); // add text before styled
var raw = s.group(0)!;
content.add(
TextSpan(
text: RegExp(r">(?!<)(.+?)<").firstMatch(raw)!.group(1),
style: TextStyle(
fontWeight: (raw.contains("<b")) ? FontWeight.bold : null,
fontStyle: (raw.contains("<i")) ? FontStyle.italic : null,
),
),
); // add styled
}
content.add(TextSpan(text: noFormatting.last)); // add last
return content;
}

View file

@ -5,5 +5,5 @@ class PageStyles {
static const h2 = TextStyle(fontSize: 22, fontWeight: FontWeight.bold); static const h2 = TextStyle(fontSize: 22, fontWeight: FontWeight.bold);
static const h3 = TextStyle(fontSize: 20); static const h3 = TextStyle(fontSize: 20);
static const h5 = TextStyle(fontSize: 18); static const h5 = TextStyle(fontSize: 18);
static const h4 = TextStyle(fontSize: 16); static const h4 = TextStyle(fontSize: 15, fontStyle: FontStyle.italic);
} }

View file

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
class Warning extends StatelessWidget {
const Warning({super.key, required this.content});
final List<TextSpan> content;
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color:
(MediaQuery.of(context).platformBrightness == Brightness.light)
? const Color.fromARGB(255, 151, 141, 48)
: Colors.yellow,
width: 2),
borderRadius: BorderRadius.circular(8),
color: (MediaQuery.of(context).platformBrightness == Brightness.dark)
? const Color.fromARGB(255, 151, 141, 48)
: Colors.yellow,
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
const Icon(
Icons.warning,
size: 50,
),
const SizedBox(
width: 10,
),
Flexible(
child: RichText(
text: TextSpan(children: content),
),
)
],
),
),
);
}
}

View file

@ -4,6 +4,7 @@ import 'package:voyagehandbook/api/wikimedia.dart';
import 'package:voyagehandbook/util/drawer.dart'; import 'package:voyagehandbook/util/drawer.dart';
import 'package:voyagehandbook/util/render.dart'; import 'package:voyagehandbook/util/render.dart';
import 'package:voyagehandbook/util/storage.dart'; import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/util/styles.dart';
/// Renders a single WikiVoyage article /// Renders a single WikiVoyage article
class ArticleView extends StatefulWidget { class ArticleView extends StatefulWidget {
@ -59,6 +60,7 @@ class _ArticleViewState extends State<ArticleView> {
} }
void loadPage() async { void loadPage() async {
try {
_content = [ _content = [
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width * 0.9, width: MediaQuery.of(context).size.width * 0.9,
@ -69,6 +71,20 @@ class _ArticleViewState extends State<ArticleView> {
MediaQuery.of(context).size.width), MediaQuery.of(context).size.width),
) )
]; ];
} catch (e) {
if (kDebugMode) print(e);
_content = [
const Text(
"Error while rendering:",
style: PageStyles.h1,
),
const SizedBox(
height: 10,
),
Text(e.toString())
];
}
setState(() {}); setState(() {});
} }

View file

@ -328,6 +328,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.2" version: "0.15.2"
html_unescape:
dependency: "direct main"
description:
name: html_unescape
sha256: "15362d7a18f19d7b742ef8dcb811f5fd2a2df98db9f80ea393c075189e0b61e3"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
http: http:
dependency: transitive dependency: transitive
description: description:

View file

@ -44,6 +44,7 @@ dependencies:
html: ^0.15.2 html: ^0.15.2
url_launcher: ^6.1.10 url_launcher: ^6.1.10
cached_network_image: ^3.2.3 cached_network_image: ^3.2.3
html_unescape: ^2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: