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: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");
|
textAlign: TextAlign.justify,
|
||||||
var noFormatting =
|
),
|
||||||
input.split(RegExp(r"(?:<b .+?>.+?<\/b>)|<i .+?>.+?<\/i>"));
|
); // add paragraph spans as single rich text
|
||||||
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(
|
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(
|
||||||
height: 10,
|
const SizedBox(
|
||||||
));
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
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/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,16 +60,31 @@ class _ArticleViewState extends State<ArticleView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadPage() async {
|
void loadPage() async {
|
||||||
_content = [
|
try {
|
||||||
SizedBox(
|
_content = [
|
||||||
width: MediaQuery.of(context).size.width * 0.9,
|
SizedBox(
|
||||||
height: MediaQuery.of(context).size.height,
|
width: MediaQuery.of(context).size.width * 0.9,
|
||||||
child: renderFromPageHTML(
|
height: MediaQuery.of(context).size.height,
|
||||||
await WikiApi.getRawPage(widget.pageKey),
|
child: renderFromPageHTML(
|
||||||
MediaQuery.of(context).size.height,
|
await WikiApi.getRawPage(widget.pageKey),
|
||||||
MediaQuery.of(context).size.width),
|
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(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue