Compare commits

...

10 commits

Author SHA1 Message Date
Matyáš Caras 42effc2908 docs: update changelog 2023-07-04 00:23:42 +02:00
Matyáš Caras 48a9bea2f7 fix(pagerenderer): 🐛 re-add offline image to map rendering 2023-07-04 00:20:46 +02:00
Matyáš Caras f5607abf0a chore: don't send update automatically 2023-07-04 00:17:38 +02:00
Matyáš Caras 76ec1883d8 Merge branch 'main' of https://git.mnau.xyz/hernik/voyagehandbook 2023-07-04 00:17:22 +02:00
Matyáš Caras 5bc1cfb70b chore: ⚗️ fastlane + changelog 2023-07-04 00:11:02 +02:00
Matyáš Caras 55bd0e56c4 feat(pagerenderer): render maps with markers using Flutter Maps 2023-07-04 00:04:06 +02:00
Matyáš Caras 74b795f00b chore: ⬆️ upgrade flutter 2023-07-04 00:02:38 +02:00
Matyáš Caras 56bd869cca fix: translate home in drawer 2023-04-07 13:04:03 +02:00
Matyáš Caras 33839b6b72 feat: complete download 2023-04-07 13:02:40 +02:00
Matyáš Caras f91ef2e68f feat: support l10n and work on offline downloads 2023-04-06 22:46:40 +02:00
37 changed files with 1336 additions and 291 deletions

@ -1 +1 @@
Subproject commit 2ad6cd72c040113b47ee9055e722606a490ef0da Subproject commit 796c8ef79279f9c774545b3771238c3098dbefab

17
.gitignore vendored
View file

@ -42,3 +42,20 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
# fastlane specific
**/fastlane/report.xml
# deliver temporary files
**/fastlane/Preview.html
# snapshot generated screenshots
**/fastlane/screenshots
# scan temporary files
**/fastlane/test_output
# Fastlane.swift runner binary
**/fastlane/FastlaneRunner
# End of https://www.toptal.com/developers/gitignore/api/fastlane

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"conventionalCommits.scopes": [
"pagerenderer"
]
}

17
CHANGELOG.md Normal file
View file

@ -0,0 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 1.0.0-alpha.2
- Added l10n support
- Added support for page downloads
- Added interactive maps through flutter_map
## 1.0.0-alpha.1
- First version

View file

@ -1,13 +1,17 @@
# Voyage Handbook # Voyage Handbook
Access [WikiVoyage](https://en.wikivoyage.org) conveniently from your phone! Access [WikiVoyage](https://en.wikivoyage.org) conveniently from your phone!
## Install
- [Google Play](https://play.google.com/store/apps/details?id=cafe.caras.voyagehandbook)
- F-Droid (soon)
## Roadmap ## Roadmap
*(In no particular order)* *(In no particular order)*
- [ ] Render articles *completely* using native widgets - [ ] Render articles *completely* using native widgets
- [ ] Navigation in articles by heading - [ ] Navigation in articles by heading
- [ ] Settings for reader - [ ] Settings for reader
- [ ] Translate UI - [X] Translate UI
- [ ] Support different WikiVoyage languages - [ ] Support different WikiVoyage languages
- [ ] Bookmark articles - [ ] Bookmark articles
- [ ] Download articles - [ ] Download articles
@ -35,7 +39,13 @@ cd voyagehandbook
./flutterw doctor ./flutterw doctor
``` ```
3. Run on your connected device 3. Generate locales
```sh
./flutterw gen-l10n
```
4. Run on your connected device
```sh ```sh
./flutterw run ./flutterw run

3
android/Gemfile Normal file
View file

@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

218
android/Gemfile.lock Normal file
View file

@ -0,0 +1,218 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
rexml
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.782.0)
aws-sdk-core (3.176.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.68.0)
aws-sdk-core (~> 3, >= 3.176.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.127.0)
aws-sdk-core (~> 3, >= 3.176.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
aws-sigv4 (1.6.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
colored (1.2)
colored2 (3.1.2)
commander (4.6.0)
highline (~> 2.0.0)
declarative (0.0.20)
digest-crc (0.6.4)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.8.1)
emoji_regex (3.2.3)
excon (0.100.0)
faraday (1.10.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.7)
fastlane (2.213.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.3, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander (~> 4.6)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-apis-androidpublisher_v3 (~> 0.3)
google-apis-playcustomapp_v1 (~> 0.1)
google-cloud-storage (~> 1.31)
highline (~> 2.0)
json (< 3.0.0)
jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
multipart-post (>= 2.0.0, < 3.0.0)
naturally (~> 2.2)
optparse (~> 0.1.1)
plist (>= 3.1.0, < 4.0.0)
rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.45.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-core (0.11.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-playcustomapp_v1 (0.13.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.19.0)
google-apis-core (>= 0.9.0, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.3.1)
google-cloud-storage (1.44.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.19.0)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.6.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
json (2.6.3)
jwt (2.7.1)
memoist (0.16.2)
mini_magick (4.12.0)
mini_mime (1.1.2)
multi_json (1.15.0)
multipart-post (2.3.0)
nanaimo (0.3.0)
naturally (2.2.1)
optparse (0.1.1)
os (1.1.4)
plist (3.7.0)
public_suffix (5.0.1)
rake (13.0.6)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
signet (0.17.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.10)
CFPropertyList
naturally
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
trailblazer-option (0.1.2)
tty-cursor (0.7.1)
tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.8.1)
word_wrap (1.0.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
xcpretty (~> 0.2, >= 0.0.7)
PLATFORMS
x86_64-linux
DEPENDENCIES
fastlane
BUNDLED WITH
2.4.13

View file

@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

2
android/fastlane/Appfile Normal file
View file

@ -0,0 +1,2 @@
json_key_file("/home/mates/fastlane.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("cafe.caras.voyagehandbook") # e.g. com.krausefx.app

28
android/fastlane/Fastfile Normal file
View file

@ -0,0 +1,28 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:android)
platform :android do
# desc "Runs all the tests"
# lane :test do
# gradle(task: "test")
# end
desc "Deploy a new version to the Google Play"
lane :deploy do
upload_to_play_store(track: "beta", aab: "../build/app/outputs/bundle/release/app-release.aab",changes_not_sent_for_review:true)
end
end

View file

@ -0,0 +1,32 @@
fastlane documentation
----
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```sh
xcode-select --install
```
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
# Available Actions
## Android
### android deploy
```sh
[bundle exec] fastlane android deploy
```
Deploy a new version to the Google Play
----
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

View file

@ -0,0 +1,10 @@
An unofficial app to browse WikiVoyage articles.
Currently you can:
* Search for articles
* Read articles natively in the app
* Navigate between pages
As the app is currently in development, features will be added as time goes by.
The app is released under GPL, anyone is open to contribute. It does not contain any trackers and all data is sent only directly to WikiMedia API. By using the app you are subject to the WikiMedia Terms of Service and Privacy Policy.

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 KiB

View file

@ -0,0 +1 @@
Read WikiVoyage articles comfortably from your phone

View file

@ -0,0 +1 @@
Voyage Handbook

3
l10n.yaml Normal file
View file

@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

View file

@ -25,8 +25,10 @@ class SearchResponse {
final String title; final String title;
final String excerpt; final String excerpt;
final String? description; final String? description;
const SearchResponse( @JsonKey(includeFromJson: false, includeToJson: false)
this.id, this.key, this.title, this.excerpt, this.description); bool downloaded;
SearchResponse(this.id, this.key, this.title, this.excerpt, this.description,
{this.downloaded = false});
/// Connect the generated function to the `fromJson` /// Connect the generated function to the `fromJson`
/// factory. /// factory.
@ -56,11 +58,10 @@ class RawPage {
final String title; final String title;
@JsonKey(fromJson: _editedFromJson, toJson: _editedToJson, name: "latest") @JsonKey(fromJson: _editedFromJson, toJson: _editedToJson, name: "latest")
final String edited; final String edited;
final String html; String html;
final LicenseAttribution license; final LicenseAttribution license;
const RawPage( RawPage(this.id, this.key, this.title, this.edited, this.html, this.license);
this.id, this.key, this.title, this.edited, this.html, this.license);
factory RawPage.fromJson(Map<String, dynamic> json) => factory RawPage.fromJson(Map<String, dynamic> json) =>
_$RawPageFromJson(json); _$RawPageFromJson(json);

View file

@ -2,6 +2,7 @@ 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';
import 'package:voyagehandbook/util/storage.dart';
/* /*
Voyage Handbook - The open-source WikiVoyage reader Voyage Handbook - The open-source WikiVoyage reader
@ -39,8 +40,14 @@ class WikiApi {
var r = await _getRequest("search/page?q=$q&limit=$limit"); var r = await _getRequest("search/page?q=$q&limit=$limit");
if (r.statusCode! > 399) return Future.error("API error ${r.statusCode}"); if (r.statusCode! > 399) return Future.error("API error ${r.statusCode}");
var json = jsonDecode(r.data)["pages"]; var json = jsonDecode(r.data)["pages"];
return List<SearchResponse>.generate( var list = List<SearchResponse>.generate(
json.length, (index) => SearchResponse.fromJson(json[index])); json.length, (index) => SearchResponse.fromJson(json[index]));
for (var item in list) {
if (await StorageAccess.isDownloaded(item.key)) {
list[list.indexOf(item)].downloaded = true;
}
}
return list;
} }
static Future<RawPage> getRawPage(String key) async { static Future<RawPage> getRawPage(String key) async {

26
lib/l10n/app_cs.arb Normal file
View file

@ -0,0 +1,26 @@
{
"home":"Domů",
"noRecents":"Nemáte žádné nedávno otevřené články, potáhněte doprava a začněte hledat.",
"recentPages":"Nedávné články",
"searchAppBarTitle":"Prohledat WikiVoyage",
"search":"Hledat",
"offlineTitle":"Offline stažení",
"offlineDialog":"Chcete stáhnout článek '{article}' pro zobrazení offline? Bude dostupný v sekci 'Stažené'.",
"renderError":"Chyba při renderování:",
"attrFrom":"Z",
"attrUnder":"pod licencí",
"downloadsTitle":"Stažené",
"noDownloads":"Nemáte žádné stažené články. Vyhledejte nějaké, a poté je dlouhým podržením stáhněte.",
"about":"O Aplikcai",
"sourceCode":"Zdrojový kód",
"creditsCreatedBy":"Vytvořil Matyáš Caras",
"creditsAffiliation":"Není nijak spojeno s WikiVoyage",
"yes":"Ano",
"no":"Ne",
"ok":"Ok",
"downloading":"Stahuji, vyčkejte prosím...",
"downloadComplete":"Stahování dokončeno, najdete ho v sekci 'Stažené'.",
"error":"Nastala chyba",
"offlineError":"Vypadá to, že jste offline a tento článek nemáte stažený.",
"imageError":"Chyba při stahování obrázku, zkontrolujte připojení."
}

72
lib/l10n/app_en.arb Normal file
View file

@ -0,0 +1,72 @@
{
"home":"Home",
"@home":{
"description":"As seen on the home page"
},
"noRecents":"You haven't opened anything recently, swipe right and start searching.",
"@noRecents":{
"description":"Shown when there are no recent pages"
},
"recentPages":"Recent articles",
"@recentPages":{
"description":"Title of the home page"
},
"searchAppBarTitle":"Search WikiVoyage",
"@searchAppBarTitle":{
"description":"Appbar title"
},
"search":"Search",
"offlineTitle":"Offline download",
"@offlineTitle":{
"description":"Title of the dialog that appears when downloading an article offline"
},
"offlineDialog":"Would you like to download the article '{article}' for offline viewing? It will be available in the 'Downloads' section.",
"@offlineDialog":{
"description":"Offline download dialog content text",
"placeholders":{
"article":{
"type":"String",
"example":"Rail travel in Japan",
"description":"WikiVoyage article name"
}
}
},
"renderError":"Error while rendering:",
"@renderError":{
"description":"Displayed when rendering of a page throws an error"
},
"attrFrom":"From",
"@pageFrom":{
"description":"The *From* part of 'From WikiVoyage'"
},
"attrUnder":"under",
"@attrUnder":{
"description":"The *under* part of license attribution, e.g. 'under CC BY-SA 3.0'"
},
"downloadsTitle":"Downloads",
"@downloadsTitle":{
"description":"The appbar title for the Downloads page"
},
"noDownloads":"You don't have any articles downloaded. Search for some and then long-press them to download them offline.",
"about":"About",
"sourceCode":"Source code",
"creditsCreatedBy":"Created by Matyáš Caras",
"creditsAffiliation":"Not affiliated with WikiVoyage",
"yes":"Yes",
"no":"No",
"ok":"Ok",
"downloading":"Downloading, please wait...",
"@downloading":{
"description":"Shown in a dialog when downloading something"
},
"downloadComplete":"Download complete, you will find it in the 'Downloads' section.",
"error":"An error occured",
"@error":{
"description":"Generic error dialog title"
},
"offlineError":"You seem to be offline and the curren article is not in your downloads.",
"@offlineError":{
"description":"Shown when trying to render a page offline"
},
"imageError":"Error downloading image, check network connection."
}

View file

@ -2,7 +2,7 @@ import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:voyagehandbook/util/color_schemes.g.dart'; import 'package:voyagehandbook/util/color_schemes.g.dart';
import 'package:voyagehandbook/views/home.dart'; import 'package:voyagehandbook/views/home.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
/* /*
Voyage Handbook - The open-source WikiVoyage reader Voyage Handbook - The open-source WikiVoyage reader
Copyright (C) 2023 Matyáš Caras Copyright (C) 2023 Matyáš Caras
@ -33,6 +33,8 @@ class MyApp extends StatelessWidget {
return DynamicColorBuilder( return DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) => MaterialApp( builder: (lightDynamic, darkDynamic) => MaterialApp(
title: 'Voyage Handbook', title: 'Voyage Handbook',
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ThemeData( theme: ThemeData(
useMaterial3: true, colorScheme: lightDynamic ?? lightColorScheme), useMaterial3: true, colorScheme: lightDynamic ?? lightColorScheme),

View file

@ -1,6 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:voyagehandbook/views/downloads.dart';
import 'package:voyagehandbook/views/home.dart'; import 'package:voyagehandbook/views/home.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../views/search.dart'; import '../views/search.dart';
/* /*
@ -28,19 +31,19 @@ Drawer genDrawer(int page, BuildContext context) => Drawer(
children: [ children: [
DrawerHeader( DrawerHeader(
child: Column( child: Column(
children: const [ children: [
Text( const Text(
"Voyage Handbook", "Voyage Handbook",
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
Text("Created by Matyáš Caras"), Text(AppLocalizations.of(context)!.creditsCreatedBy),
Text("Not affiliated with WikiVoyage") Text(AppLocalizations.of(context)!.creditsAffiliation)
], ],
), ),
), ),
ListTile( ListTile(
selected: page == 1, selected: page == 1,
title: const Text("Home"), title: Text(AppLocalizations.of(context)!.home),
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
onTap: () => page == 1 onTap: () => page == 1
? Navigator.of(context).pop() ? Navigator.of(context).pop()
@ -52,7 +55,7 @@ Drawer genDrawer(int page, BuildContext context) => Drawer(
), ),
ListTile( ListTile(
selected: page == 2, selected: page == 2,
title: const Text("Search"), title: Text(AppLocalizations.of(context)!.search),
leading: const Icon(Icons.search), leading: const Icon(Icons.search),
onTap: () => page == 2 onTap: () => page == 2
? Navigator.of(context).pop() ? Navigator.of(context).pop()
@ -64,9 +67,21 @@ Drawer genDrawer(int page, BuildContext context) => Drawer(
), ),
ListTile( ListTile(
selected: page == 3, selected: page == 3,
title: const Text("About"), title: Text(AppLocalizations.of(context)!.downloadsTitle),
leading: const Icon(Icons.info_outline), leading: const Icon(Icons.download),
onTap: () => page == 3 onTap: () => page == 3
? Navigator.of(context).pop()
: Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const DownloadsView(),
),
),
),
ListTile(
selected: page == 99,
title: Text(AppLocalizations.of(context)!.about),
leading: const Icon(Icons.info_outline),
onTap: () => page == 99
? Navigator.of(context).pop() ? Navigator.of(context).pop()
: Navigator.of(context).push( : Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
@ -78,6 +93,15 @@ Drawer genDrawer(int page, BuildContext context) => Drawer(
), ),
), ),
), ),
ListTile(
selected: page == 99,
title: Text(AppLocalizations.of(context)!.sourceCode),
leading: const Icon(Icons.code),
onTap: () => page == 99
? Navigator.of(context).pop()
: launchUrlString("https://git.mnau.xyz/hernik/voyagehandbook",
mode: LaunchMode.externalApplication),
),
], ],
), ),
); );

View file

@ -2,16 +2,21 @@ import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:html/dom.dart' as dom; import 'package:html/dom.dart' as dom;
import 'package:latlong2/latlong.dart';
import 'package:logger/logger.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:voyagehandbook/api/classes.dart'; import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/util/styles.dart'; import 'package:voyagehandbook/util/styles.dart';
import 'package:voyagehandbook/util/widgets/warning.dart'; import 'package:voyagehandbook/util/widgets/warning.dart';
import 'package:html_unescape/html_unescape_small.dart'; import 'package:html_unescape/html_unescape_small.dart';
import 'package:voyagehandbook/views/pageview.dart'; import 'package:voyagehandbook/views/pageview.dart';
import 'package:widget_zoom/widget_zoom.dart'; import 'package:widget_zoom/widget_zoom.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
/* /*
Voyage Handbook - The open-source WikiVoyage reader Voyage Handbook - The open-source WikiVoyage reader
@ -30,17 +35,27 @@ import 'package:widget_zoom/widget_zoom.dart';
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
final _ignoredTags = ["style", "script"]; final _ignoredTags = ["style", "script"];
final logger = Logger(printer: PrettyPrinter());
class PageRenderer { class PageRenderer {
final ColorScheme scheme; final ColorScheme scheme;
final double height; final double height;
final double width; final double width;
final BuildContext context; final BuildContext context;
final AppLocalizations loc;
PageRenderer(this.scheme, this.height, this.width, this.context); /// For offline downloads; don't bother rendering the widget tree
final bool offline;
/// HTML for offline download / caching
String outHtml = "";
String? document;
PageRenderer(this.scheme, this.height, this.width, this.context, this.loc,
{this.offline = false});
/// Used to create Widgets from raw HTML from WM API /// Used to create Widgets from raw HTML from WM API
ListView renderFromPageHTML(RawPage page) { Future<ListView> renderFromPageHTML(RawPage page) async {
var out = <Widget>[ var out = <Widget>[
const SizedBox( const SizedBox(
height: 10, height: 10,
@ -56,7 +71,7 @@ class PageRenderer {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SelectableText("From"), SelectableText(loc.attrFrom),
const SizedBox( const SizedBox(
width: 5, width: 5,
), ),
@ -74,26 +89,32 @@ class PageRenderer {
), ),
Row( Row(
children: [ children: [
const SelectableText("under"), SelectableText(loc.attrUnder),
const SizedBox( const SizedBox(
width: 5, width: 2,
), ),
SelectableText( Flexible(
page.license.title, child: SelectableText(
onTap: () => launchUrl(Uri.parse(page.license.url), page.license.title,
mode: LaunchMode.externalApplication), textAlign: TextAlign.center,
style: const TextStyle( onTap: () => launchUrl(Uri.parse(page.license.url),
decoration: TextDecoration.underline, mode: LaunchMode.externalApplication),
style: const TextStyle(
decoration: TextDecoration.underline,
),
), ),
) )
], ],
) )
]; ];
outHtml = "<html><head></head><body>";
var document = parse(page.html); var document = parse(page.html);
var sections = document.body!.getElementsByTagName("section"); var sections = document.body!.getElementsByTagName("section");
this.document = page.html;
for (var sec in sections) { for (var sec in sections) {
if (sec.localName == "section") { if (sec.localName == "section") {
out.addAll(_renderSection(sec)); if (!offline) out.addAll(await _renderSection(sec));
outHtml += sec.outerHtml;
} }
} }
var l = ListView.builder( var l = ListView.builder(
@ -104,7 +125,7 @@ class PageRenderer {
return l; return l;
} }
List<Widget> _renderSection(dom.Element sec) { Future<List<Widget>> _renderSection(dom.Element sec) async {
var out = <Widget>[]; var out = <Widget>[];
// Get Section Title // Get Section Title
var headings = sec.children.where( var headings = sec.children.where(
@ -202,7 +223,7 @@ class PageRenderer {
); // space paragraphs ); // space paragraphs
break; break;
case "figure": case "figure":
out.addAll(_renderImageFigure(element)); out.addAll(await _renderImageFigure(element));
out.add( out.add(
const SizedBox( const SizedBox(
height: 10, height: 10,
@ -210,7 +231,7 @@ class PageRenderer {
); );
break; break;
case "section": case "section":
out.addAll(_renderSection(element)); out.addAll(await _renderSection(element));
break; break;
case "dl": case "dl":
var dd = element.getElementsByTagName("dd").first; var dd = element.getElementsByTagName("dd").first;
@ -274,7 +295,7 @@ class PageRenderer {
for (var e in inner.body!.children) { for (var e in inner.body!.children) {
if (e.localName == "figure") { if (e.localName == "figure") {
// render image // render image
out.addAll(_renderImageFigure(e)); out.addAll(await _renderImageFigure(e));
out.add( out.add(
const SizedBox( const SizedBox(
height: 5, height: 5,
@ -329,50 +350,157 @@ class PageRenderer {
), ),
); );
} else if (element.classes.contains("mw-kartographer-container")) { } else if (element.classes.contains("mw-kartographer-container")) {
logger.i("Found map container");
var imgs = element var imgs = element
.getElementsByTagName("div") .getElementsByTagName("div")
.first .first
.getElementsByTagName("a") .getElementsByTagName("a")
.first .first
.getElementsByTagName("img"); .getElementsByTagName("img");
if (imgs.isEmpty) break; // load maps that have a static image if (imgs.isEmpty ||
var img = imgs[0]; (imgs.first.attributes["src"]
var cap = element.getElementsByClassName("thumbcaption")[0]; ?.startsWith("https://maps.wikimedia.org") ??
out.add(const SizedBox( false)) {
height: 10, logger.i("Rendering with FlutterMap");
)); // render map using FlutterMap
out.add( var dataElement = element
SizedBox( .getElementsByClassName("thumbinner")
width: width * 0.8, .first
height: height * 0.3, .getElementsByClassName("mw-kartographer-map")
child: WidgetZoom( .first;
zoomWidget:
CachedNetworkImage(imageUrl: img.attributes["src"]!), var pointsRaw = RegExp(
heroAnimationTag: 'tag', r"""<span class="noprint listing-coordinates".+?><span class="geo"><abbr class="latitude">(.+?)<\/abbr><abbr class="longitude">(.+?)<\/abbr>.+?}'>(\d+)<""")
), .allMatches(document!); // find markers
), logger.i("Found ${pointsRaw.length} markers");
); if (pointsRaw.isEmpty) break;
out.add( var points = <Marker>[];
const SizedBox( for (var point in pointsRaw) {
height: 3, // convert to FlutterMap markers
), if (point.groups([1, 2]).any((element) => element == null)) {
); logger.w("No lat/lon found in pointer");
out.add( continue;
SelectableText( }
cap.text, // assign marker color
textAlign: TextAlign.center, var colorMatch = RegExp(r'background: #(.+?);')
), .firstMatch(point.group(0)!)
); ?.group(1);
out.add( Color bubbleColor = (colorMatch == null)
const SizedBox( ? scheme.secondary
: Color(int.parse("0xff$colorMatch"));
points.add(
Marker(
builder: (c) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: bubbleColor),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(point.group(3) ?? "X"),
),
),
point: LatLng(
double.parse(point.group(1)!),
double.parse(point.group(2)!),
),
),
);
}
out.add(const SizedBox(
height: 10, height: 10,
), ));
); out.add(
SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
height: (MediaQuery.of(context).size.width * 0.8 / 4) * 3,
child: FlutterMap(
options: MapOptions(
center: LatLng(
double.parse(
dataElement.attributes["data-lat"] ?? "00.0"),
double.parse(
dataElement.attributes["data-lon"] ?? "00.0"),
),
zoom: double.parse(
dataElement.attributes["data-zoom"] ?? "1"),
),
nonRotatedChildren: [
RichAttributionWidget(
attributions: [
TextSourceAttribution(
"OpenStreetMap contributors",
onTap: () => launchUrlString(
"https://openstreetmap.org/copyright"),
)
],
)
],
children: [
TileLayer(
urlTemplate:
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
userAgentPackageName: "cafe.caras.voyagehandbook",
),
MarkerLayer(
markers: points,
)
],
),
),
);
} else {
var img = imgs[0];
var cap = element.getElementsByClassName("thumbcaption")[0];
out.add(const SizedBox(
height: 10,
));
var offlineImage = await StorageAccess.getOfflineImage(img
.attributes["src"]!
.split('/')
.last
.replaceAll(RegExp(r"(?!\..+?)\?.+"), ""));
out.add(
SizedBox(
width: width * 0.8,
height: height * 0.3,
child: WidgetZoom(
zoomWidget: CachedNetworkImage(
imageUrl: img.attributes["src"]!,
errorWidget: (context, url, error) =>
(offlineImage != null)
? Image.file(offlineImage)
: Flexible(
child: Text(loc.imageError),
),
),
heroAnimationTag: 'tag',
),
),
);
out.add(
const SizedBox(
height: 3,
),
);
out.add(
SelectableText(
cap.text,
textAlign: TextAlign.center,
),
);
out.add(
const SizedBox(
height: 10,
),
);
}
} }
break; break;
default: default:
break; break;
} }
element.remove(); element.remove();
} }
@ -384,7 +512,7 @@ class PageRenderer {
return out; return out;
} }
List<Widget> _renderImageFigure(dom.Element element) { Future<List<Widget>> _renderImageFigure(dom.Element element) async {
var out = <Widget>[]; var out = <Widget>[];
/// Image figure /// Image figure
@ -397,6 +525,12 @@ class PageRenderer {
if (figcap.isNotEmpty) { if (figcap.isNotEmpty) {
caption = figcap.first.text; // TODO: handle links caption = figcap.first.text; // TODO: handle links
} }
var offlineImage = await StorageAccess.getOfflineImage(img
.attributes["src"]!
.split('/')
.last
.replaceAll(RegExp(r"(?!\..+?)\?.+"), ""));
out.add(const SizedBox( out.add(const SizedBox(
height: 10, height: 10,
)); ));
@ -411,10 +545,11 @@ class PageRenderer {
imageUrl: img.attributes["src"]!.replaceAll("//", "https://"), imageUrl: img.attributes["src"]!.replaceAll("//", "https://"),
progressIndicatorBuilder: (context, url, downloadProgress) => progressIndicatorBuilder: (context, url, downloadProgress) =>
LinearProgressIndicator(value: downloadProgress.progress), LinearProgressIndicator(value: downloadProgress.progress),
errorWidget: (context, url, error) => Icon( errorWidget: (context, url, error) => (offlineImage != null)
Icons.error, ? Image.file(offlineImage)
color: scheme.error, : Flexible(
), child: Text(loc.imageError),
),
), ),
), ),
), ),
@ -489,7 +624,9 @@ class PageRenderer {
content.add( content.add(
WidgetSpan( WidgetSpan(
child: SelectableText( child: SelectableText(
match.group(2)!, match.group(2)!.replaceAll(
RegExp(r'<(?:(?:\/b)|(?:\/i)|(?:\/a)|(?:b)|(?:i)|(?:a )).*?>'),
""),
onTap: () { onTap: () {
if (match.group(0)!.contains('rel="mw:ExtLink')) { if (match.group(0)!.contains('rel="mw:ExtLink')) {
// handle as an external link // handle as an external link
@ -535,8 +672,9 @@ class PageRenderer {
.toList(); .toList();
for (var s in needToFormat) { for (var s in needToFormat) {
content.add(TextSpan( content.add(TextSpan(
text: text: noFormatting[needToFormat.indexOf(s)].replaceAll(
noFormatting[needToFormat.indexOf(s)])); // add text before styled RegExp(r'<(?:(?:\/b)|(?:\/i)|(?:\/a)|(?:b)|(?:i)|(?:a )).*?>'),
""))); // add text before styled
var raw = s.group(0)!; var raw = s.group(0)!;
content.add( content.add(
TextSpan( TextSpan(
@ -561,16 +699,6 @@ class PageRenderer {
return content; return content;
} }
final _extraColorsLight = {
"blue": const Color.fromARGB(255, 34, 34, 157),
"red": const Color.fromARGB(255, 152, 33, 33)
};
final _extraColorsDark = {
"blue": const Color.fromARGB(255, 84, 95, 247),
"red": const Color.fromARGB(255, 242, 69, 69)
};
SingleChildScrollView _renderList(dom.Element element) { SingleChildScrollView _renderList(dom.Element element) {
var out = <Widget>[]; var out = <Widget>[];
var i = 0; var i = 0;
@ -580,24 +708,19 @@ class PageRenderer {
? item.getElementsByClassName("listing-name")[0].text ? item.getElementsByClassName("listing-name")[0].text
: null; : null;
Color bubbleColor; var colorMatch =
if (item.innerHtml.contains("background: #0000FF")) { RegExp(r'background: #(.+?);').firstMatch(item.innerHtml)?.group(1);
bubbleColor = Color bubbleColor = (colorMatch == null)
(MediaQuery.of(context).platformBrightness == Brightness.dark) ? scheme.secondary
? _extraColorsDark["blue"]! : Color(int.parse("0xff$colorMatch"));
: _extraColorsLight["blue"]!; if (element.getElementsByClassName("geo").isNotEmpty) {
} else if (item.innerHtml.contains("background: #800000")) { element.getElementsByClassName("geo").first.remove();
bubbleColor = }
(MediaQuery.of(context).platformBrightness == Brightness.dark) var rest =
? _extraColorsDark["red"]! ((title != null) ? item.text.replaceAll(title, "") : item.text);
: _extraColorsLight["red"]!; if (rest.startsWith("1")) {
} else { rest = rest.substring(1); // TODO: figure out how to remove it better?
bubbleColor = scheme.secondary;
} }
var rest = (title != null)
? item.text.replaceAll(item.getElementsByTagName("span")[0].text, "")
: item.text;
out.add(const SizedBox( out.add(const SizedBox(
height: 5, height: 5,
)); ));

View file

@ -1,7 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:html/parser.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/api/wikimedia.dart';
/* /*
Voyage Handbook - The open-source WikiVoyage reader Voyage Handbook - The open-source WikiVoyage reader
@ -35,6 +40,19 @@ class StorageAccess {
.toList(); .toList();
} }
/// Get files in `offline` folder, which contains recently opened pages
static Future<List<Map<String, dynamic>>> get offline async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
if (!files.existsSync()) files.createSync();
return files
.listSync()
.whereType<File>()
.toList()
.map<Map<String, dynamic>>((e) => jsonDecode(e.readAsStringSync()))
.toList();
}
static void addToRecents(String pageName, String pageKey) async { static void addToRecents(String pageName, String pageKey) async {
var files = var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/recent"); Directory("${(await getApplicationDocumentsDirectory()).path}/recent");
@ -70,4 +88,91 @@ class StorageAccess {
recent.writeAsStringSync(jsonEncode(recentContent)); recent.writeAsStringSync(jsonEncode(recentContent));
} }
} }
static Future<bool> isDownloaded(String pageKey) async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
if (!files.existsSync()) files.createSync();
var offlinePage = File("${files.path}/$pageKey");
return offlinePage.existsSync();
}
static Future<void> downloadArticle(String pageKey, String pageTitle) async {
try {
var files = Directory(
"${(await getApplicationDocumentsDirectory()).path}/offline");
if (!files.existsSync()) files.createSync();
var offlinePage = File("${files.path}/$pageKey");
var raw = await WikiApi.getRawPage(pageKey);
var page = parse(raw.html);
var out = "<html><head></head><body>";
var sections = page.body!.children
.where((element) => element.localName == "section");
for (var el in sections) {
out += el.outerHtml;
}
out += "</body></html>";
var imgMatch = RegExp(r'<img.+?src="(.+?)".+?>')
.allMatches(page.body!.innerHtml); // TODO: ask to overwrite
if (imgMatch.isNotEmpty) {
// download images offline
for (var match in imgMatch) {
var src = match.group(1)!;
if (!src.startsWith("https://")) {
src = src.replaceAll("//", "https://");
}
var r = await Dio().get(
src,
options: Options(
responseType: ResponseType.bytes,
followRedirects: false,
validateStatus: (status) {
return (status ?? 200) < 500;
},
),
);
var assetDir = Directory("${files.path}/assets");
if (!assetDir.existsSync()) assetDir.createSync();
var img = File(
"${assetDir.path}/${src.split('/').last.replaceAll(RegExp(r"(?!\..+?)\?.+"), "")}");
print(img.path);
var openImg = img.openSync(mode: FileMode.write);
openImg.writeFromSync(r.data);
}
}
raw.html = out;
if (sections.isEmpty) {
return Future.error("No sections to save");
}
offlinePage.writeAsStringSync(jsonEncode(raw.toJson()),
mode: FileMode.writeOnly);
} catch (e) {
if (kDebugMode) {
print(e);
}
return Future.error(e);
}
}
static Future<RawPage?> getOfflinePage(String pageKey) async {
var files =
Directory("${(await getApplicationDocumentsDirectory()).path}/offline");
if (!files.existsSync()) return null;
var offlinePage = File("${files.path}/$pageKey");
if (!offlinePage.existsSync()) return null;
try {
return RawPage.fromJson(jsonDecode(offlinePage.readAsStringSync()));
} catch (e) {
if (kDebugMode) {
print(e);
}
return null;
}
}
static Future<File?> getOfflineImage(String key) async {
var files = Directory(
"${(await getApplicationDocumentsDirectory()).path}/offline/assets");
return File("${files.path}/$key");
}
} }

85
lib/views/downloads.dart Normal file
View file

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:voyagehandbook/util/drawer.dart';
import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/util/styles.dart';
import 'package:voyagehandbook/views/pageview.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class DownloadsView extends StatefulWidget {
const DownloadsView({super.key});
@override
State<DownloadsView> createState() => _DownloadsViewState();
}
class _DownloadsViewState extends State<DownloadsView> {
@override
void initState() {
super.initState();
loadDownloads();
}
var _content = <Widget>[];
var _isLoading = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.downloadsTitle),
),
drawer: genDrawer(3, context),
body: Center(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width * 0.9,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: _isLoading
? [const CircularProgressIndicator()]
: _content.isEmpty
? [
Text(
AppLocalizations.of(context)!.noDownloads,
textAlign: TextAlign.center,
)
]
: _content,
),
),
),
);
}
void loadDownloads() async {
var files = await StorageAccess.offline;
_content = List.generate(
files.length,
(index) => SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height * 0.1,
child: InkWell(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ArticleView(
pageKey: files[index]["key"],
name: files[index]["title"],
),
),
),
child: Align(
alignment: Alignment.center,
child: Text(
files[index]["title"],
textAlign: TextAlign.center,
style: PageStyles.h1,
),
),
),
),
);
_isLoading = false;
setState(() {});
}
}

View file

@ -4,6 +4,7 @@ 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'; import 'package:voyagehandbook/views/search.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
/* /*
Voyage Handbook - The open-source WikiVoyage reader Voyage Handbook - The open-source WikiVoyage reader
@ -48,7 +49,7 @@ class _HomeViewState extends State<HomeView> {
), ),
child: const Icon(Icons.search), child: const Icon(Icons.search),
), ),
appBar: AppBar(title: const Text("Home")), appBar: AppBar(title: Text(AppLocalizations.of(context)!.home)),
drawer: genDrawer(1, context), drawer: genDrawer(1, context),
body: Center( body: Center(
child: SizedBox( child: SizedBox(
@ -58,9 +59,8 @@ class _HomeViewState extends State<HomeView> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: (_recents.isEmpty) children: (_recents.isEmpty)
? [ ? [
const Flexible( Flexible(
child: Text( child: Text(AppLocalizations.of(context)!.noRecents),
"You haven't opened anything recently, swipe right and start searching."),
) )
] ]
: _recents), : _recents),
@ -79,9 +79,10 @@ class _HomeViewState extends State<HomeView> {
DateTime.fromMillisecondsSinceEpoch(b["date"])) DateTime.fromMillisecondsSinceEpoch(b["date"]))
? 0 ? 0
: 1); : 1);
if (!mounted) return;
_recents = [ _recents = [
const Text( Text(
"Recent pages", AppLocalizations.of(context)!.recentPages,
style: PageStyles.h1, style: PageStyles.h1,
), ),
const SizedBox( const SizedBox(

View file

@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:voyagehandbook/api/wikimedia.dart'; import 'package:voyagehandbook/api/wikimedia.dart';
@ -5,6 +6,7 @@ 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'; import 'package:voyagehandbook/util/styles.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
/* /*
Voyage Handbook - The open-source WikiVoyage reader Voyage Handbook - The open-source WikiVoyage reader
@ -77,34 +79,55 @@ class _ArticleViewState extends State<ArticleView> {
} }
void loadPage() async { void loadPage() async {
if (!mounted) return;
var renderer = PageRenderer( var renderer = PageRenderer(
Theme.of(context).colorScheme, Theme.of(context).colorScheme,
MediaQuery.of(context).size.height, MediaQuery.of(context).size.height,
MediaQuery.of(context).size.width, MediaQuery.of(context).size.width,
context); context,
if (kDebugMode) { AppLocalizations.of(context)!);
try {
_content = [ _content = [
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width * 0.9, width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height, height: MediaQuery.of(context).size.height,
child: renderer child: await renderer
.renderFromPageHTML(await WikiApi.getRawPage(widget.pageKey)), .renderFromPageHTML(await WikiApi.getRawPage(widget.pageKey)),
) )
]; ];
} else { } catch (e) {
try { if (e.toString().contains("Failed host lookup")) {
// user is offline
var offline = await StorageAccess.getOfflinePage(widget.pageKey);
if (offline == null) {
// Not downloaded, show error
if (!mounted) return;
_content = [
Text(
AppLocalizations.of(context)!.renderError,
style: PageStyles.h1,
),
const SizedBox(
height: 10,
),
Text(AppLocalizations.of(context)!.offlineError)
];
} else {
// Render offline version
if (!mounted) return;
_content = [
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
height: MediaQuery.of(context).size.height,
child: await renderer.renderFromPageHTML(offline),
)
];
}
} else {
_content = [ _content = [
SizedBox( Text(
width: MediaQuery.of(context).size.width * 0.9, AppLocalizations.of(context)!.renderError,
height: MediaQuery.of(context).size.height,
child: renderer
.renderFromPageHTML(await WikiApi.getRawPage(widget.pageKey)),
)
];
} catch (e) {
_content = [
const Text(
"Error while rendering:",
style: PageStyles.h1, style: PageStyles.h1,
), ),
const SizedBox( const SizedBox(

View file

@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:voyagehandbook/api/classes.dart'; import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/api/wikimedia.dart'; import 'package:voyagehandbook/api/wikimedia.dart';
import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/views/pageview.dart'; import 'package:voyagehandbook/views/pageview.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../util/drawer.dart'; import '../util/drawer.dart';
/* /*
@ -36,7 +38,8 @@ class _SearchViewState extends State<SearchView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text("Search WikiVoyage")), appBar:
AppBar(title: Text(AppLocalizations.of(context)!.searchAppBarTitle)),
drawer: genDrawer(2, context), drawer: genDrawer(2, context),
body: SingleChildScrollView( body: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
@ -59,7 +62,7 @@ class _SearchViewState extends State<SearchView> {
_searchResults = r; _searchResults = r;
setState(() {}); setState(() {});
}, },
child: const Text("Search"), child: Text(AppLocalizations.of(context)!.search),
), ),
const SizedBox( const SizedBox(
height: 15, height: 15,
@ -129,6 +132,126 @@ class _SearchViewState extends State<SearchView> {
child: Card( child: Card(
elevation: 2, elevation: 2,
child: InkWell( child: InkWell(
onLongPress: () {
// Show download dialog
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(
AppLocalizations.of(context)!
.offlineTitle),
content: Text(
AppLocalizations.of(context)!
.offlineDialog(
_searchResults[index]
.title),
),
actions: [
TextButton(
onPressed: () async {
Navigator.of(context).pop();
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => Dialog(
child: SizedBox(
height: 100,
child: Row(
children: [
const Padding(
padding:
EdgeInsets
.all(
10),
child:
CircularProgressIndicator(),
),
Text(AppLocalizations
.of(context)!
.downloading)
],
),
),
),
);
try {
await StorageAccess
.downloadArticle(
_searchResults[
index]
.key,
_searchResults[
index]
.title);
if (!mounted) return;
Navigator.of(context)
.pop();
ScaffoldMessenger.of(
context)
.clearSnackBars();
ScaffoldMessenger.of(
context)
.showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(
context)!
.downloadComplete),
duration:
const Duration(
seconds: 4),
),
);
} catch (e) {
Navigator.of(context)
.pop();
showDialog(
context: context,
builder: (_) =>
AlertDialog(
title: Text(
AppLocalizations.of(
context)!
.error),
content:
SingleChildScrollView(
child: Text(
e.toString()),
),
actions: [
TextButton(
onPressed: () =>
Navigator.of(
context)
.pop(),
child: Text(
AppLocalizations.of(
context)!
.ok),
)
],
),
);
}
},
child: Text(
AppLocalizations.of(
context)!
.yes),
),
TextButton(
onPressed: () =>
Navigator.of(context)
.pop(),
child: Text(
AppLocalizations.of(
context)!
.no),
),
],
),
);
},
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
@ -145,13 +268,27 @@ class _SearchViewState extends State<SearchView> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
children: [ children: [
Text( Row(children: [
_searchResults[index].title, Text(
style: const TextStyle( _searchResults[index].title,
fontWeight: FontWeight.bold, style: const TextStyle(
fontSize: fontWeight:
16), // TODO: responsive sizing FontWeight.bold,
), fontSize:
16), // TODO: responsive sizing
),
if (_searchResults[index]
.downloaded)
const SizedBox(
width: 15,
),
if (_searchResults[index]
.downloaded)
const Icon(
Icons.download,
size: 15,
),
]),
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),

View file

@ -5,42 +5,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "503361166f4a100e0d7eb7fb5a62c6f0322512f2bcb48d6922caf98f24b0ab90" sha256: "0816708f5fbcacca324d811297153fe3c8e047beb5c6752e12292d2974c17045"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "56.0.0" version: "62.0.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "93fcd81a6716e69864516750590cf1e699420615046bda19100238aa7b429785" sha256: "21862995c9932cd082f89d72ae5f5e2c110d1a0204ad06e4ebaee8307b76b834"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.8.0" version: "6.0.0"
archive: archive:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.6" version: "3.3.7"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.2"
async: async:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.10.0" version: "2.11.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -53,10 +53,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.4.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -69,34 +69,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "4.0.0"
build_resolvers: build_resolvers:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.1"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.3" version: "2.4.6"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.7" version: "7.2.10"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -109,10 +109,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0" sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.4.4" version: "8.6.1"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@ -141,18 +141,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.3.0"
checked_yaml: checked_yaml:
dependency: transitive dependency: transitive
description: description:
name: checked_yaml name: checked_yaml
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.2" version: "2.0.3"
cli_util: cli_util:
dependency: transitive dependency: transitive
description: description:
@ -173,18 +173,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: code_builder name: code_builder
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.0" version: "4.5.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.1"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -197,18 +197,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
csslib: csslib:
dependency: transitive dependency: transitive
description: description:
name: csslib name: csslib
sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.2" version: "1.0.0"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -217,30 +217,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.5" version: "1.0.5"
dart_internal:
dependency: transitive
description:
name: dart_internal
sha256: dae3976f383beddcfcd07ad5291a422df2c8c0a8a03c52cda63ac7b4f26e0f4e
url: "https://pub.dev"
source: hosted
version: "0.2.8"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "6d691edde054969f0e0f26abb1b30834b5138b963793e56f69d3a9a4435e6352" sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.2"
dio: dio:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "2644a9e0965a7aa3deb09cb8ce4081db4450c178f472818c8cd34216a3070d7b" sha256: a9d76e72985d7087eb7c5e7903224ae52b337131518d127c554b9405936752b8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.2" version: "5.2.1+1"
dynamic_color: dynamic_color:
dependency: "direct main" dependency: "direct main"
description: description:
name: dynamic_color name: dynamic_color
sha256: c4a508284b14ec4dda5adba2c28b2cdd34fbae1afead7e8c52cad87d51c5405b sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.2" version: "1.6.5"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -253,10 +261,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -290,10 +298,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_cache_manager name: flutter_cache_manager
sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0" version: "3.3.1"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -306,10 +314,23 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
sha256: "5286f72f87deb132daa1489442d6cc46e986fc105cb727d9ae1b602b35b1d1f3"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -332,26 +353,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: glob name: glob
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
name: graphs name: graphs
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.3.1"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
name: html name: html
sha256: "79d498e6d6761925a34ee5ea8fa6dfef38607781d2fa91e37523474282af55cb" sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.2" version: "0.15.4"
html_unescape: html_unescape:
dependency: "direct main" dependency: "direct main"
description: description:
@ -364,10 +385,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http name: http
sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.5" version: "1.1.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -392,6 +413,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.0" version: "3.3.0"
intl:
dependency: "direct main"
description:
name: intl
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
url: "https://pub.dev"
source: hosted
version: "0.18.0"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -404,50 +433,74 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.5" version: "0.6.7"
json_annotation: json_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
name: json_annotation name: json_annotation
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.1"
json_serializable: json_serializable:
dependency: "direct main" dependency: "direct main"
description: description:
name: json_serializable name: json_serializable
sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.6.1" version: "6.7.1"
latlong2:
dependency: "direct main"
description:
name: latlong2
sha256: "18712164760cee655bc790122b0fd8f3d5b3c36da2cb7bf94b68a197fbb0811b"
url: "https://pub.dev"
source: hosted
version: "0.9.0"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.1.1"
lists:
dependency: transitive
description:
name: lists
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
logger:
dependency: "direct main"
description:
name: logger
sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
name: logging name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.13" version: "0.12.15"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -460,10 +513,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.0" version: "1.9.1"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -492,42 +553,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.2" version: "1.8.3"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9" sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.13" version: "2.0.15"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.24" version: "2.0.27"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "026b97a6c29da75181a37aae2eba9227f5fe13cb2838c6b975ce209328b8ab4e" sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.2.3"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.10" version: "2.1.11"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -540,26 +601,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.1.7"
pedantic:
dependency: transitive
description:
name: pedantic
sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602"
url: "https://pub.dev"
source: hosted
version: "1.11.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.0" version: "5.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -580,10 +633,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.2" version: "3.7.3"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -600,22 +661,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "4.2.4"
proj4dart:
dependency: transitive
description:
name: proj4dart
sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e
url: "https://pub.dev"
source: hosted
version: "2.1.0"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.3" version: "2.1.4"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
name: pubspec_parse name: pubspec_parse
sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.2" version: "1.2.3"
rxdart: rxdart:
dependency: transitive dependency: transitive
description: description:
@ -628,74 +697,74 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41 sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.18" version: "2.2.0"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521 sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.17" version: "2.2.0"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310" sha256: "0dc5c49ad8a05ed358b991b60c7b0ba1a14e16dae58af9b420d6b9e82dc024ab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.3.0"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_linux name: shared_preferences_linux
sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707" sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.3.0"
shared_preferences_platform_interface: shared_preferences_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_platform_interface name: shared_preferences_platform_interface
sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc" sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.3.0"
shared_preferences_web: shared_preferences_web:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8" sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.6" version: "2.2.0"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_windows name: shared_preferences_windows
sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436" sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.3.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.4"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -705,18 +774,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298 sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.7" version: "1.4.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.4"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -729,18 +798,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite name: sqflite
sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.6" version: "2.2.8+4"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.3" version: "2.4.5+1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -777,10 +846,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: synchronized name: synchronized
sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b" sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.1.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -793,10 +862,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.16" version: "0.5.1"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -809,74 +878,82 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
unicode:
dependency: transitive
description:
name: unicode
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.10" version: "6.1.11"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: dd729390aa936bf1bdf5cd1bc7468ff340263f80a2c4f569416507667de8e3c8 sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.26" version: "6.0.36"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92" sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.3" version: "6.1.4"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc" sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.4" version: "3.0.5"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: "0ef2b4f97942a16523e51256b799e9aa1843da6c60c55eefbfa9dbc2dcb8331a" sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.4" version: "3.0.5"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.16" version: "2.0.17"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.5" version: "3.0.6"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@ -897,18 +974,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: watcher name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.1.0"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.4.0"
widget_zoom: widget_zoom:
dependency: "direct main" dependency: "direct main"
description: description:
@ -921,10 +998,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 sha256: dfdf0136e0aa7a1b474ea133e67cb0154a0acd2599c4f3ada3b49d38d38793ee
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.3" version: "5.0.5"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -937,18 +1022,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.2" version: "6.3.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:
name: yaml name: yaml
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.2"
sdks: sdks:
dart: ">=2.19.4 <3.0.0" dart: ">=3.0.0 <3.2.0"
flutter: ">=3.4.0-17.0.pre" flutter: ">=3.10.0"

View file

@ -16,10 +16,10 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.0.0-alpha.1+1 version: 1.0.0-alpha.2+3
environment: environment:
sdk: '>=2.19.4 <3.0.0' sdk: '>=3.0.0'
# Dependencies specify other packages that your package needs in order to work. # Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions # To automatically upgrade your package dependencies to the latest versions
@ -46,6 +46,12 @@ dependencies:
cached_network_image: ^3.2.3 cached_network_image: ^3.2.3
html_unescape: ^2.0.0 html_unescape: ^2.0.0
widget_zoom: ^0.0.1 widget_zoom: ^0.0.1
flutter_map: ^5.0.0
latlong2: ^0.9.0
logger: ^1.4.0
flutter_localizations:
sdk: flutter
intl: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -65,6 +71,7 @@ dev_dependencies:
# The following section is specific to Flutter packages. # The following section is specific to Flutter packages.
flutter: flutter:
generate: true
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in # included with your application, so that you can use the icons in