Compare commits
4 commits
f81b6556c4
...
7406d74e6a
Author | SHA1 | Date | |
---|---|---|---|
7406d74e6a | |||
54642131b0 | |||
2506eb7f4a | |||
c5646cff15 |
19
README.md
19
README.md
|
@ -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/>.
|
||||||
```
|
```
|
|
@ -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
|
||||||
|
|
|
@ -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/"));
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
60
lib/util/widgets/warning.dart
Normal file
60
lib/util/widgets/warning.dart
Normal 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),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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});
|
||||||
|
|
||||||
|
|
72
pubspec.lock
72
pubspec.lock
|
@ -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"
|
||||||
|
|
12
pubspec.yaml
12
pubspec.yaml
|
@ -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
|
Loading…
Reference in a new issue