Compare commits

...

4 commits

Author SHA1 Message Date
Matyáš Caras 7406d74e6a docs: license 2023-03-28 20:10:46 +02:00
Matyáš Caras 54642131b0 feat: add zoompinch and render as class 2023-03-28 20:03:21 +02:00
Matyáš Caras 2506eb7f4a fix: license and search FAB 2023-03-28 18:08:26 +02:00
Matyáš Caras c5646cff15 feat: render warnings and fix text render 2023-03-28 18:05:05 +02:00
14 changed files with 692 additions and 240 deletions

View file

@ -4,6 +4,7 @@ Access [WikiVoyage](https://en.wikivoyage.org) conveniently from your phone!
## Roadmap ## Roadmap
*(In no particular order)* *(In no particular order)*
- [ ] Render articles *completely* using native widgets
- [ ] Navigation in articles by heading - [ ] Navigation in articles by heading
- [ ] Settings for reader - [ ] Settings for reader
- [ ] Translate UI - [ ] Translate UI
@ -38,4 +39,22 @@ cd voyagehandbook
```sh ```sh
./flutterw run ./flutterw run
```
## License
```
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
``` ```

View file

@ -6,8 +6,8 @@ part 'classes.g.dart';
Copyright (C) 2023 Matyáš Caras Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation, either version 3 of the License. the Free Software Foundation.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of

View file

@ -3,6 +3,23 @@ import 'dart:convert';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:voyagehandbook/api/classes.dart'; import 'package:voyagehandbook/api/classes.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class WikiApi { class WikiApi {
static final _dio = Dio( static final _dio = Dio(
BaseOptions(baseUrl: "https://api.wikimedia.org/core/v1/wikivoyage/en/")); BaseOptions(baseUrl: "https://api.wikimedia.org/core/v1/wikivoyage/en/"));

View file

@ -8,8 +8,8 @@ import 'package:voyagehandbook/views/home.dart';
Copyright (C) 2023 Matyáš Caras Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation, either version 3 of the License. the Free Software Foundation.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of

View file

@ -3,6 +3,23 @@ import 'package:voyagehandbook/views/home.dart';
import '../views/search.dart'; import '../views/search.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Used to generate the drawer /// Used to generate the drawer
/// ///
/// Use `0` in `page` if you don't want any tile selected /// Use `0` in `page` if you don't want any tile selected

View file

@ -5,251 +5,396 @@ 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';
import 'package:zoom_pinch_overlay/zoom_pinch_overlay.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
final _ignoredTags = ["style", "script"]; final _ignoredTags = ["style", "script"];
/// Used to create Widgets from raw HTML from WM API class PageRenderer {
ListView renderFromPageHTML(RawPage page, double height, double width) { final ColorScheme scheme;
var out = <Widget>[ final double height;
const SizedBox( final double width;
height: 10,
),
Text(
page.title,
textAlign: TextAlign.center,
style: PageStyles.h1,
),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("From"),
const SizedBox(
width: 5,
),
GestureDetector(
onTap: () => launchUrl(
Uri.parse("https://en.wikivoyage.org/wiki/${page.key}"),
mode: LaunchMode.externalApplication),
child: const Text(
"WikiVoyage",
style: TextStyle(
decoration: TextDecoration.underline,
fontWeight: FontWeight.bold,
),
),
),
],
),
Row(
children: [
const Text("under"),
const SizedBox(
width: 5,
),
GestureDetector(
onTap: () => launchUrl(Uri.parse(page.license.url),
mode: LaunchMode.externalApplication),
child: Text(
page.license.title,
style: const TextStyle(
decoration: TextDecoration.underline,
),
),
)
],
)
];
var document = parse(page.html);
var sections = document.body!.getElementsByTagName("section");
for (var sec in sections) {
if (sec.localName == "section") {
out.addAll(_renderSection(sec, height, width));
}
}
var l = ListView.builder(
padding: EdgeInsets.zero,
itemBuilder: (c, i) => out[i],
itemCount: out.length,
);
return l;
}
List<Widget> _renderSection(dom.Element sec, double height, double width) { PageRenderer(this.scheme, this.height, this.width);
var out = <Widget>[];
// Get Section Title /// Used to create Widgets from raw HTML from WM API
var headings = sec.children ListView renderFromPageHTML(RawPage page) {
.where((element) => ["h2", "h3", "h4", "h5"].contains(element.localName)); var out = <Widget>[
var sectionTitle = (headings.isNotEmpty) ? headings.first : null; const SizedBox(
if (sectionTitle != null) { height: 10,
switch (sectionTitle.localName) { ),
case "h2": Text(
out.add( page.title,
textAlign: TextAlign.center,
style: PageStyles.h1,
),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("From"),
const SizedBox( const SizedBox(
height: 10, width: 5,
), ),
); GestureDetector(
out.add(Text( onTap: () => launchUrl(
sectionTitle.text, Uri.parse("https://en.wikivoyage.org/wiki/${page.key}"),
style: PageStyles.h2, mode: LaunchMode.externalApplication),
textAlign: TextAlign.center, child: const Text(
)); "WikiVoyage",
break; style: TextStyle(
case "h3": decoration: TextDecoration.underline,
out.add( fontWeight: FontWeight.bold,
Align( ),
alignment: Alignment.centerLeft,
child: Text(
sectionTitle.text,
style: PageStyles.h3,
), ),
), ),
); ],
break; ),
case "h4": Row(
out.add( children: [
Align( const Text("under"),
alignment: Alignment.centerLeft, const SizedBox(
child: Text(sectionTitle.text, style: PageStyles.h4), width: 5,
), ),
); GestureDetector(
break; onTap: () => launchUrl(Uri.parse(page.license.url),
case "h5": mode: LaunchMode.externalApplication),
out.add( child: Text(
Align( page.license.title,
alignment: Alignment.centerLeft, style: const TextStyle(
child: Text(sectionTitle.text, style: PageStyles.h5), decoration: TextDecoration.underline,
), ),
); ),
break; )
default: ],
out.add( )
Align( ];
alignment: Alignment.centerLeft, var document = parse(page.html);
child: Text(sectionTitle.text), var sections = document.body!.getElementsByTagName("section");
), for (var sec in sections) {
); if (sec.localName == "section") {
break; out.addAll(_renderSection(sec));
}
// out.add(
// const SizedBox(
// height: 5,
// ),
// );
}
// create TextSpans from text content
for (var element in sec.children) {
// Go through all section's children
for (var t in _ignoredTags) {
var ignored = element.getElementsByTagName(t);
if (ignored.isNotEmpty) {
// Remove ignored tags
for (var element in ignored) {
element.remove();
}
} }
} }
var l = ListView.builder(
padding: EdgeInsets.zero,
itemBuilder: (c, i) => out[i],
itemCount: out.length,
);
return l;
}
switch (element.localName) { List<Widget> _renderSection(dom.Element sec) {
case "p": var out = <Widget>[];
case "link": // Get Section Title
var paraSpans = <TextSpan>[]; var headings = sec.children.where(
var input = element.innerHtml (element) => ["h2", "h3", "h4", "h5"].contains(element.localName));
.replaceAll(RegExp(r"<(?!(?:\/b)|(?:\/i)|b|i).+?>"), "") var sectionTitle = (headings.isNotEmpty) ? headings.first : null;
.replaceAll("\\n", "\r"); if (sectionTitle != null) {
var noFormatting = switch (sectionTitle.localName) {
input.split(RegExp(r"(?:<b .+?>.+?<\/b>)|<i .+?>.+?<\/i>")); case "h2":
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(
const SizedBox(
height: 5,
),
); // space paragraphs
break;
case "figure":
/// Image figure
var imgs = element.getElementsByTagName("img");
if (imgs.isEmpty) break;
var img = imgs.first; // get image element
if (img.attributes["src"] == null) break;
var figcap = element.getElementsByTagName("figcaption"); // get caption
String? caption;
if (figcap.isNotEmpty) {
caption = figcap.first.text; // TODO: handle links
}
out.add(const SizedBox(
height: 10,
));
out.add(
Container(
// TODO: add tap detector to open wikimedia page?
width: width * 0.8,
height: height * 0.3,
foregroundDecoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fitHeight,
image: CachedNetworkImageProvider(
img.attributes["src"]!.replaceAll("//", "https://"),
),
),
),
//height: height * 0.3,
),
); // load image
if (caption != null) {
// Add caption when available
out.add( out.add(
const SizedBox( const SizedBox(
height: 3, height: 10,
), ),
); );
out.add(Text( out.add(Text(
caption, sectionTitle.text,
style: PageStyles.h2,
textAlign: TextAlign.center, textAlign: TextAlign.center,
)); ));
} break;
out.add(const SizedBox( case "h3":
height: 10, out.add(
)); Align(
break; alignment: Alignment.centerLeft,
case "section": child: Text(
out.addAll(_renderSection(element, height, width)); sectionTitle.text,
break; style: PageStyles.h3,
default: ),
break; ),
);
break;
case "h4":
out.add(
Align(
alignment: Alignment.centerLeft,
child: Text(sectionTitle.text, style: PageStyles.h4),
),
);
break;
case "h5":
out.add(
Align(
alignment: Alignment.centerLeft,
child: Text(sectionTitle.text, style: PageStyles.h5),
),
);
break;
default:
out.add(
Align(
alignment: Alignment.centerLeft,
child: Text(sectionTitle.text),
),
);
break;
}
// out.add(
// const SizedBox(
// height: 5,
// ),
// );
} }
// create TextSpans from text content
for (var element in sec.children) {
// Go through all section's children
for (var t in _ignoredTags) {
var ignored = element.getElementsByTagName(t);
if (ignored.isNotEmpty) {
// Remove ignored tags
for (var element in ignored) {
element.remove();
}
}
}
switch (element.localName) {
case "p":
case "link":
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,
),
); // space paragraphs
break;
case "figure":
out.addAll(_renderImageFigure(element));
out.add(
const SizedBox(
height: 10,
),
);
break;
case "section":
out.addAll(_renderSection(element));
break;
case "div":
if (element.attributes["class"] != null &&
element.attributes["class"] == "pp_cautionbox") {
out.add(_renderWarning(element));
out.add(
const SizedBox(
height: 10,
),
);
} else if (element.id == "region_list") {
var inner = parse(
element.innerHtml.replaceAll(RegExp(r'<\/?span.+?>'), ""),
);
for (var e in inner.body!.children) {
if (e.localName == "figure") {
// render image
out.addAll(_renderImageFigure(e));
out.add(
const SizedBox(
height: 5,
),
);
} else if (e.localName == "table") {
Color? boxColor;
var text = <TextSpan>[];
for (var td in e
.getElementsByTagName("tr")
.first
.getElementsByTagName("td")) {
if (td.attributes["style"] != null) {
var colorMatch = RegExp(r'background-color:#(.+?);')
.firstMatch(td.attributes["style"]!);
boxColor = Color(int.parse("0xff${colorMatch!.group(1)}"));
} else {
text.addAll(_renderText(td.innerHtml));
}
}
out.add(
const SizedBox(
height: 10,
),
);
out.add(
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (boxColor != null)
Container(
color: boxColor,
width: 25,
height: 25,
),
const SizedBox(
width: 10,
),
Flexible(
child: RichText(
text: TextSpan(children: text),
),
)
],
),
);
}
}
out.add(
const SizedBox(
height: 10,
),
);
}
break;
default:
break;
}
element.remove();
}
out.add(
const SizedBox(
height: 5,
),
);
return out;
} }
out.add( List<Widget> _renderImageFigure(dom.Element element) {
const SizedBox( var out = <Widget>[];
height: 5,
), /// Image figure
); var imgs = element.getElementsByTagName("img");
return out; if (imgs.isEmpty) return [];
var img = imgs.first; // get image element
if (img.attributes["src"] == null || img.attributes["src"] == "") return [];
var figcap = element.getElementsByTagName("figcaption"); // get caption
String? caption;
if (figcap.isNotEmpty) {
caption = figcap.first.text; // TODO: handle links
}
out.add(const SizedBox(
height: 10,
));
out.add(
SizedBox(
// TODO: open wikimedia page?
width: width * 0.8,
height: height * 0.3,
child: ZoomOverlay(
child: CachedNetworkImage(
imageUrl: img.attributes["src"]!.replaceAll("//", "https://"),
progressIndicatorBuilder: (context, url, downloadProgress) =>
LinearProgressIndicator(value: downloadProgress.progress),
errorWidget: (context, url, error) => Icon(
Icons.error,
color: scheme.error,
),
),
),
),
); // load image
if (caption != null) {
// Add caption when available
out.add(
const SizedBox(
height: 3,
),
);
out.add(
Text(
caption,
textAlign: TextAlign.center,
),
);
}
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", "\n")
.replaceAll("<br>", "\n");
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

@ -3,6 +3,23 @@ import 'dart:io';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Used to ease up accessing local files /// Used to ease up accessing local files
class StorageAccess { class StorageAccess {
/// Get files in `recent` folder, which contains recently opened pages /// Get files in `recent` folder, which contains recently opened pages

View file

@ -1,9 +1,26 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class PageStyles { class PageStyles {
static const h1 = TextStyle(fontSize: 26, fontWeight: FontWeight.bold); static const h1 = TextStyle(fontSize: 26, fontWeight: FontWeight.bold);
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,60 @@
import 'package:flutter/material.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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

@ -3,6 +3,24 @@ import 'package:voyagehandbook/util/drawer.dart';
import 'package:voyagehandbook/util/storage.dart'; import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/util/styles.dart'; import 'package:voyagehandbook/util/styles.dart';
import 'package:voyagehandbook/views/pageview.dart'; import 'package:voyagehandbook/views/pageview.dart';
import 'package:voyagehandbook/views/search.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class HomeView extends StatefulWidget { class HomeView extends StatefulWidget {
const HomeView({super.key}); const HomeView({super.key});
@ -22,6 +40,14 @@ class _HomeViewState extends State<HomeView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const SearchView(),
),
),
child: const Icon(Icons.search),
),
appBar: AppBar(title: const Text("Home")), appBar: AppBar(title: const Text("Home")),
drawer: genDrawer(1, context), drawer: genDrawer(1, context),
body: Center( body: Center(

View file

@ -4,6 +4,24 @@ 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';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Renders a single WikiVoyage article /// Renders a single WikiVoyage article
class ArticleView extends StatefulWidget { class ArticleView extends StatefulWidget {
@ -59,16 +77,31 @@ class _ArticleViewState extends State<ArticleView> {
} }
void loadPage() async { void loadPage() async {
_content = [ var renderer = PageRenderer(Theme.of(context).colorScheme,
SizedBox( MediaQuery.of(context).size.height, MediaQuery.of(context).size.width);
width: MediaQuery.of(context).size.width * 0.9, try {
height: MediaQuery.of(context).size.height, _content = [
child: renderFromPageHTML( SizedBox(
await WikiApi.getRawPage(widget.pageKey), width: MediaQuery.of(context).size.width * 0.9,
MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height,
MediaQuery.of(context).size.width), child: renderer
) .renderFromPageHTML(await WikiApi.getRawPage(widget.pageKey)),
]; )
];
} 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

@ -5,6 +5,23 @@ import 'package:voyagehandbook/views/pageview.dart';
import '../util/drawer.dart'; import '../util/drawer.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as published by
the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
class SearchView extends StatefulWidget { class SearchView extends StatefulWidget {
const SearchView({super.key}); const SearchView({super.key});

View file

@ -17,6 +17,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.8.0" version: "5.8.0"
archive:
dependency: transitive
description:
name: archive
sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d
url: "https://pub.dev"
source: hosted
version: "3.3.6"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -145,6 +153,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.2"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
url: "https://pub.dev"
source: hosted
version: "0.3.5"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -278,6 +294,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0" version: "3.3.0"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "02dcaf49d405f652b7160e882bacfc02cb497041bb2eab2a49b1c393cf9aac12"
url: "https://pub.dev"
source: hosted
version: "0.12.0"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -328,6 +352,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:
@ -352,6 +384,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227"
url: "https://pub.dev"
source: hosted
version: "4.0.15"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -512,6 +552,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.1" version: "1.11.1"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
url: "https://pub.dev"
source: hosted
version: "5.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -528,6 +576,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c
url: "https://pub.dev"
source: hosted
version: "3.7.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -869,6 +925,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
xml:
dependency: transitive
description:
name: xml
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
url: "https://pub.dev"
source: hosted
version: "6.2.2"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
@ -877,6 +941,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
zoom_pinch_overlay:
dependency: "direct main"
description:
name: zoom_pinch_overlay
sha256: cad0aef0127953e3a2ad65aa51660e9c86fa11906e286297f9a70aab69163f64
url: "https://pub.dev"
source: hosted
version: "1.4.1+3"
sdks: sdks:
dart: ">=2.19.4 <3.0.0" dart: ">=2.19.4 <3.0.0"
flutter: ">=3.4.0-17.0.pre" flutter: ">=3.4.0-17.0.pre"

View file

@ -44,10 +44,13 @@ 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
zoom_pinch_overlay: ^1.4.1+3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_launcher_icons: ^0.12.0
# The "flutter_lints" package below contains a set of recommended lints to # The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is # encourage good coding practices. The lint set provided by the package is
@ -98,3 +101,12 @@ flutter:
# #
# For details regarding fonts from package dependencies, # For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages # see https://flutter.dev/custom-fonts/#from-packages
flutter_icons:
android: "launcher_icon"
ios: true
image_path: "assets/icon.png"
min_sdk_android: 21 # android min sdk min:16, default 21
adaptive_icon_background: "#FF9C432E"
remove_alpha_ios: true