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/profile
/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
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
*(In no particular order)*
- [ ] Render articles *completely* using native widgets
- [ ] Navigation in articles by heading
- [ ] Settings for reader
- [ ] Translate UI
- [X] Translate UI
- [ ] Support different WikiVoyage languages
- [ ] Bookmark articles
- [ ] Download articles
@ -35,7 +39,13 @@ cd voyagehandbook
./flutterw doctor
```
3. Run on your connected device
3. Generate locales
```sh
./flutterw gen-l10n
```
4. Run on your connected device
```sh
./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')
}
task clean(type: Delete) {
tasks.register("clean", Delete) {
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 excerpt;
final String? description;
const SearchResponse(
this.id, this.key, this.title, this.excerpt, this.description);
@JsonKey(includeFromJson: false, includeToJson: false)
bool downloaded;
SearchResponse(this.id, this.key, this.title, this.excerpt, this.description,
{this.downloaded = false});
/// Connect the generated function to the `fromJson`
/// factory.
@ -56,11 +58,10 @@ class RawPage {
final String title;
@JsonKey(fromJson: _editedFromJson, toJson: _editedToJson, name: "latest")
final String edited;
final String html;
String html;
final LicenseAttribution license;
const RawPage(
this.id, this.key, this.title, this.edited, this.html, this.license);
RawPage(this.id, this.key, this.title, this.edited, this.html, this.license);
factory RawPage.fromJson(Map<String, dynamic> json) =>
_$RawPageFromJson(json);

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/util/storage.dart';
/*
Voyage Handbook - The open-source WikiVoyage reader
@ -39,8 +40,14 @@ class WikiApi {
var r = await _getRequest("search/page?q=$q&limit=$limit");
if (r.statusCode! > 399) return Future.error("API error ${r.statusCode}");
var json = jsonDecode(r.data)["pages"];
return List<SearchResponse>.generate(
var list = List<SearchResponse>.generate(
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 {

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

View file

@ -1,6 +1,9 @@
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:flutter_gen/gen_l10n/app_localizations.dart';
import '../views/search.dart';
/*
@ -28,19 +31,19 @@ Drawer genDrawer(int page, BuildContext context) => Drawer(
children: [
DrawerHeader(
child: Column(
children: const [
Text(
children: [
const Text(
"Voyage Handbook",
style: TextStyle(fontWeight: FontWeight.bold),
),
Text("Created by Matyáš Caras"),
Text("Not affiliated with WikiVoyage")
Text(AppLocalizations.of(context)!.creditsCreatedBy),
Text(AppLocalizations.of(context)!.creditsAffiliation)
],
),
),
ListTile(
selected: page == 1,
title: const Text("Home"),
title: Text(AppLocalizations.of(context)!.home),
leading: const Icon(Icons.home),
onTap: () => page == 1
? Navigator.of(context).pop()
@ -52,7 +55,7 @@ Drawer genDrawer(int page, BuildContext context) => Drawer(
),
ListTile(
selected: page == 2,
title: const Text("Search"),
title: Text(AppLocalizations.of(context)!.search),
leading: const Icon(Icons.search),
onTap: () => page == 2
? Navigator.of(context).pop()
@ -64,9 +67,21 @@ Drawer genDrawer(int page, BuildContext context) => Drawer(
),
ListTile(
selected: page == 3,
title: const Text("About"),
leading: const Icon(Icons.info_outline),
title: Text(AppLocalizations.of(context)!.downloadsTitle),
leading: const Icon(Icons.download),
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).push(
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:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:html/parser.dart';
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_string.dart';
import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/util/styles.dart';
import 'package:voyagehandbook/util/widgets/warning.dart';
import 'package:html_unescape/html_unescape_small.dart';
import 'package:voyagehandbook/views/pageview.dart';
import 'package:widget_zoom/widget_zoom.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
/*
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/>.
*/
final _ignoredTags = ["style", "script"];
final logger = Logger(printer: PrettyPrinter());
class PageRenderer {
final ColorScheme scheme;
final double height;
final double width;
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
ListView renderFromPageHTML(RawPage page) {
Future<ListView> renderFromPageHTML(RawPage page) async {
var out = <Widget>[
const SizedBox(
height: 10,
@ -56,7 +71,7 @@ class PageRenderer {
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SelectableText("From"),
SelectableText(loc.attrFrom),
const SizedBox(
width: 5,
),
@ -74,26 +89,32 @@ class PageRenderer {
),
Row(
children: [
const SelectableText("under"),
SelectableText(loc.attrUnder),
const SizedBox(
width: 5,
width: 2,
),
SelectableText(
page.license.title,
onTap: () => launchUrl(Uri.parse(page.license.url),
mode: LaunchMode.externalApplication),
style: const TextStyle(
decoration: TextDecoration.underline,
Flexible(
child: SelectableText(
page.license.title,
textAlign: TextAlign.center,
onTap: () => launchUrl(Uri.parse(page.license.url),
mode: LaunchMode.externalApplication),
style: const TextStyle(
decoration: TextDecoration.underline,
),
),
)
],
)
];
outHtml = "<html><head></head><body>";
var document = parse(page.html);
var sections = document.body!.getElementsByTagName("section");
this.document = page.html;
for (var sec in sections) {
if (sec.localName == "section") {
out.addAll(_renderSection(sec));
if (!offline) out.addAll(await _renderSection(sec));
outHtml += sec.outerHtml;
}
}
var l = ListView.builder(
@ -104,7 +125,7 @@ class PageRenderer {
return l;
}
List<Widget> _renderSection(dom.Element sec) {
Future<List<Widget>> _renderSection(dom.Element sec) async {
var out = <Widget>[];
// Get Section Title
var headings = sec.children.where(
@ -202,7 +223,7 @@ class PageRenderer {
); // space paragraphs
break;
case "figure":
out.addAll(_renderImageFigure(element));
out.addAll(await _renderImageFigure(element));
out.add(
const SizedBox(
height: 10,
@ -210,7 +231,7 @@ class PageRenderer {
);
break;
case "section":
out.addAll(_renderSection(element));
out.addAll(await _renderSection(element));
break;
case "dl":
var dd = element.getElementsByTagName("dd").first;
@ -274,7 +295,7 @@ class PageRenderer {
for (var e in inner.body!.children) {
if (e.localName == "figure") {
// render image
out.addAll(_renderImageFigure(e));
out.addAll(await _renderImageFigure(e));
out.add(
const SizedBox(
height: 5,
@ -329,50 +350,157 @@ class PageRenderer {
),
);
} else if (element.classes.contains("mw-kartographer-container")) {
logger.i("Found map container");
var imgs = element
.getElementsByTagName("div")
.first
.getElementsByTagName("a")
.first
.getElementsByTagName("img");
if (imgs.isEmpty) break; // load maps that have a static image
var img = imgs[0];
var cap = element.getElementsByClassName("thumbcaption")[0];
out.add(const SizedBox(
height: 10,
));
out.add(
SizedBox(
width: width * 0.8,
height: height * 0.3,
child: WidgetZoom(
zoomWidget:
CachedNetworkImage(imageUrl: img.attributes["src"]!),
heroAnimationTag: 'tag',
),
),
);
out.add(
const SizedBox(
height: 3,
),
);
out.add(
SelectableText(
cap.text,
textAlign: TextAlign.center,
),
);
out.add(
const SizedBox(
if (imgs.isEmpty ||
(imgs.first.attributes["src"]
?.startsWith("https://maps.wikimedia.org") ??
false)) {
logger.i("Rendering with FlutterMap");
// render map using FlutterMap
var dataElement = element
.getElementsByClassName("thumbinner")
.first
.getElementsByClassName("mw-kartographer-map")
.first;
var pointsRaw = RegExp(
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;
var points = <Marker>[];
for (var point in pointsRaw) {
// convert to FlutterMap markers
if (point.groups([1, 2]).any((element) => element == null)) {
logger.w("No lat/lon found in pointer");
continue;
}
// assign marker color
var colorMatch = RegExp(r'background: #(.+?);')
.firstMatch(point.group(0)!)
?.group(1);
Color bubbleColor = (colorMatch == null)
? 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,
),
);
));
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;
default:
break;
}
element.remove();
}
@ -384,7 +512,7 @@ class PageRenderer {
return out;
}
List<Widget> _renderImageFigure(dom.Element element) {
Future<List<Widget>> _renderImageFigure(dom.Element element) async {
var out = <Widget>[];
/// Image figure
@ -397,6 +525,12 @@ class PageRenderer {
if (figcap.isNotEmpty) {
caption = figcap.first.text; // TODO: handle links
}
var offlineImage = await StorageAccess.getOfflineImage(img
.attributes["src"]!
.split('/')
.last
.replaceAll(RegExp(r"(?!\..+?)\?.+"), ""));
out.add(const SizedBox(
height: 10,
));
@ -411,10 +545,11 @@ class PageRenderer {
imageUrl: img.attributes["src"]!.replaceAll("//", "https://"),
progressIndicatorBuilder: (context, url, downloadProgress) =>
LinearProgressIndicator(value: downloadProgress.progress),
errorWidget: (context, url, error) => Icon(
Icons.error,
color: scheme.error,
),
errorWidget: (context, url, error) => (offlineImage != null)
? Image.file(offlineImage)
: Flexible(
child: Text(loc.imageError),
),
),
),
),
@ -489,7 +624,9 @@ class PageRenderer {
content.add(
WidgetSpan(
child: SelectableText(
match.group(2)!,
match.group(2)!.replaceAll(
RegExp(r'<(?:(?:\/b)|(?:\/i)|(?:\/a)|(?:b)|(?:i)|(?:a )).*?>'),
""),
onTap: () {
if (match.group(0)!.contains('rel="mw:ExtLink')) {
// handle as an external link
@ -535,8 +672,9 @@ class PageRenderer {
.toList();
for (var s in needToFormat) {
content.add(TextSpan(
text:
noFormatting[needToFormat.indexOf(s)])); // add text before styled
text: noFormatting[needToFormat.indexOf(s)].replaceAll(
RegExp(r'<(?:(?:\/b)|(?:\/i)|(?:\/a)|(?:b)|(?:i)|(?:a )).*?>'),
""))); // add text before styled
var raw = s.group(0)!;
content.add(
TextSpan(
@ -561,16 +699,6 @@ class PageRenderer {
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) {
var out = <Widget>[];
var i = 0;
@ -580,24 +708,19 @@ class PageRenderer {
? item.getElementsByClassName("listing-name")[0].text
: null;
Color bubbleColor;
if (item.innerHtml.contains("background: #0000FF")) {
bubbleColor =
(MediaQuery.of(context).platformBrightness == Brightness.dark)
? _extraColorsDark["blue"]!
: _extraColorsLight["blue"]!;
} else if (item.innerHtml.contains("background: #800000")) {
bubbleColor =
(MediaQuery.of(context).platformBrightness == Brightness.dark)
? _extraColorsDark["red"]!
: _extraColorsLight["red"]!;
} else {
bubbleColor = scheme.secondary;
var colorMatch =
RegExp(r'background: #(.+?);').firstMatch(item.innerHtml)?.group(1);
Color bubbleColor = (colorMatch == null)
? scheme.secondary
: Color(int.parse("0xff$colorMatch"));
if (element.getElementsByClassName("geo").isNotEmpty) {
element.getElementsByClassName("geo").first.remove();
}
var rest =
((title != null) ? item.text.replaceAll(title, "") : item.text);
if (rest.startsWith("1")) {
rest = rest.substring(1); // TODO: figure out how to remove it better?
}
var rest = (title != null)
? item.text.replaceAll(item.getElementsByTagName("span")[0].text, "")
: item.text;
out.add(const SizedBox(
height: 5,
));

View file

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

View file

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

View file

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:voyagehandbook/api/classes.dart';
import 'package:voyagehandbook/api/wikimedia.dart';
import 'package:voyagehandbook/util/storage.dart';
import 'package:voyagehandbook/views/pageview.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../util/drawer.dart';
/*
@ -36,7 +38,8 @@ class _SearchViewState extends State<SearchView> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Search WikiVoyage")),
appBar:
AppBar(title: Text(AppLocalizations.of(context)!.searchAppBarTitle)),
drawer: genDrawer(2, context),
body: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
@ -59,7 +62,7 @@ class _SearchViewState extends State<SearchView> {
_searchResults = r;
setState(() {});
},
child: const Text("Search"),
child: Text(AppLocalizations.of(context)!.search),
),
const SizedBox(
height: 15,
@ -129,6 +132,126 @@ class _SearchViewState extends State<SearchView> {
child: Card(
elevation: 2,
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: () {
Navigator.of(context).push(
MaterialPageRoute(
@ -145,13 +268,27 @@ class _SearchViewState extends State<SearchView> {
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
_searchResults[index].title,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize:
16), // TODO: responsive sizing
),
Row(children: [
Text(
_searchResults[index].title,
style: const TextStyle(
fontWeight:
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(
height: 10,
),

View file

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