feat: render warnings and fix text render
This commit is contained in:
parent
f81b6556c4
commit
c5646cff15
6 changed files with 156 additions and 43 deletions
|
@ -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),
|
||||
out.add(
|
||||
RichText(
|
||||
text: TextSpan(children: _renderText(element.innerHtml)),
|
||||
textAlign: TextAlign.justify,
|
||||
)); // add paragraph spans as single rich text
|
||||
),
|
||||
); // 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(
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
43
lib/util/widgets/warning.dart
Normal file
43
lib/util/widgets/warning.dart
Normal 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),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,6 +60,7 @@ class _ArticleViewState extends State<ArticleView> {
|
|||
}
|
||||
|
||||
void loadPage() async {
|
||||
try {
|
||||
_content = [
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.9,
|
||||
|
@ -69,6 +71,20 @@ class _ArticleViewState extends State<ArticleView> {
|
|||
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(() {});
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue