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:voyagehandbook/api/classes.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"];
@ -156,35 +158,12 @@ List<Widget> _renderSection(dom.Element sec, double height, double width) {
switch (element.localName) {
case "p":
case "link":
var paraSpans = <TextSpan>[];
var input = element.innerHtml
.replaceAll(RegExp(r"<(?!(?:\/b)|(?:\/i)|b|i).+?>"), "")
.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,
)); // add paragraph spans as single rich text
out.add(
RichText(
text: TextSpan(children: _renderText(element.innerHtml)),
textAlign: TextAlign.justify,
),
); // add paragraph spans as single rich text
out.add(
const SizedBox(
height: 5,
@ -234,16 +213,30 @@ List<Widget> _renderSection(dom.Element sec, double height, double width) {
textAlign: TextAlign.center,
));
}
out.add(const SizedBox(
height: 10,
));
out.add(
const SizedBox(
height: 10,
),
);
break;
case "section":
out.addAll(_renderSection(element, height, width));
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:
break;
}
element.remove();
}
out.add(
@ -253,3 +246,55 @@ List<Widget> _renderSection(dom.Element sec, double height, double width) {
);
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 h3 = TextStyle(fontSize: 20);
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/render.dart';
import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/util/styles.dart';
/// Renders a single WikiVoyage article
class ArticleView extends StatefulWidget {
@ -59,16 +60,31 @@ class _ArticleViewState extends State<ArticleView> {
}
void loadPage() async {
_content = [
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height,
child: renderFromPageHTML(
await WikiApi.getRawPage(widget.pageKey),
MediaQuery.of(context).size.height,
MediaQuery.of(context).size.width),
)
];
try {
_content = [
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height,
child: renderFromPageHTML(
await WikiApi.getRawPage(widget.pageKey),
MediaQuery.of(context).size.height,
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(() {});
}

View file

@ -328,6 +328,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View file

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