info: alternate map styles
This commit is contained in:
parent
f5da9e3ab9
commit
fe40408c07
12 changed files with 867 additions and 116 deletions
|
@ -1,4 +1,6 @@
|
||||||
// run `scripts/update_flutter_version.sh` to update with the content of `flutter --version --machine`
|
// run `scripts/update_flutter_version.sh` to update with the content of `flutter --version --machine`
|
||||||
|
// note on static analysis: the output of the script above yields double quotes, just like the example below
|
||||||
|
// ignore_for_file: prefer_single_quotes
|
||||||
const Map<String, String> version = {
|
const Map<String, String> version = {
|
||||||
"channel": "unknown",
|
"channel": "unknown",
|
||||||
"dartSdkVersion": "unknown",
|
"dartSdkVersion": "unknown",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
|
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
@ -18,6 +19,7 @@ class Settings {
|
||||||
static const collectionGroupFactorKey = 'collection_group_factor';
|
static const collectionGroupFactorKey = 'collection_group_factor';
|
||||||
static const collectionSortFactorKey = 'collection_sort_factor';
|
static const collectionSortFactorKey = 'collection_sort_factor';
|
||||||
static const collectionTileExtentKey = 'collection_tile_extent';
|
static const collectionTileExtentKey = 'collection_tile_extent';
|
||||||
|
static const infoMapStyleKey = 'info_map_style';
|
||||||
static const infoMapZoomKey = 'info_map_zoom';
|
static const infoMapZoomKey = 'info_map_zoom';
|
||||||
static const catalogTimeZoneKey = 'catalog_time_zone';
|
static const catalogTimeZoneKey = 'catalog_time_zone';
|
||||||
static const hasAcceptedTermsKey = 'has_accepted_terms';
|
static const hasAcceptedTermsKey = 'has_accepted_terms';
|
||||||
|
@ -50,6 +52,10 @@ class Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, EntryMapStyle.stamenWatercolor, EntryMapStyle.values);
|
||||||
|
|
||||||
|
set infoMapStyle(EntryMapStyle newValue) => setAndNotify(infoMapStyleKey, newValue.toString());
|
||||||
|
|
||||||
double get infoMapZoom => _prefs.getDouble(infoMapZoomKey) ?? 12;
|
double get infoMapZoom => _prefs.getDouble(infoMapZoomKey) ?? 12;
|
||||||
|
|
||||||
set infoMapZoom(double newValue) => setAndNotify(infoMapZoomKey, newValue);
|
set infoMapZoom(double newValue) => setAndNotify(infoMapZoomKey, newValue);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import 'package:aves/widgets/common/aves_logo.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/debug_page.dart';
|
import 'package:aves/widgets/debug_page.dart';
|
||||||
import 'package:aves/widgets/filter_grid_page.dart';
|
import 'package:aves/widgets/filter_grid_page.dart';
|
||||||
|
import 'package:aves/widgets/settings_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -93,6 +94,15 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||||
title: 'Favourites',
|
title: 'Favourites',
|
||||||
filter: FavouriteFilter(),
|
filter: FavouriteFilter(),
|
||||||
);
|
);
|
||||||
|
final settingsEntry = SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: false,
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(AIcons.settings),
|
||||||
|
title: Text('Preferences'),
|
||||||
|
onTap: () => _goToSettings(context),
|
||||||
|
),
|
||||||
|
);
|
||||||
final aboutEntry = SafeArea(
|
final aboutEntry = SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
bottom: false,
|
bottom: false,
|
||||||
|
@ -114,6 +124,7 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||||
_buildCountrySection(),
|
_buildCountrySection(),
|
||||||
_buildTagSection(),
|
_buildTagSection(),
|
||||||
Divider(),
|
Divider(),
|
||||||
|
settingsEntry,
|
||||||
aboutEntry,
|
aboutEntry,
|
||||||
if (kDebugMode) ...[
|
if (kDebugMode) ...[
|
||||||
Divider(),
|
Divider(),
|
||||||
|
@ -310,6 +321,16 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _goToSettings(BuildContext context) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SettingsPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _goToAbout(BuildContext context) {
|
void _goToAbout(BuildContext context) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
|
|
|
@ -19,6 +19,7 @@ class AIcons {
|
||||||
static const IconData location = OMIcons.place;
|
static const IconData location = OMIcons.place;
|
||||||
static const IconData shooting = OMIcons.camera;
|
static const IconData shooting = OMIcons.camera;
|
||||||
static const IconData removableStorage = OMIcons.sdStorage;
|
static const IconData removableStorage = OMIcons.sdStorage;
|
||||||
|
static const IconData settings = OMIcons.settings;
|
||||||
static const IconData text = OMIcons.formatQuote;
|
static const IconData text = OMIcons.formatQuote;
|
||||||
static const IconData tag = OMIcons.localOffer;
|
static const IconData tag = OMIcons.localOffer;
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ import 'package:aves/model/filters/location.dart';
|
||||||
import 'package:aves/model/image_entry.dart';
|
import 'package:aves/model/image_entry.dart';
|
||||||
import 'package:aves/model/settings.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.dart';
|
import 'package:aves/model/source/collection_lens.dart';
|
||||||
import 'package:aves/services/android_app_service.dart';
|
|
||||||
import 'package:aves/utils/geo_utils.dart';
|
import 'package:aves/utils/geo_utils.dart';
|
||||||
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
import 'package:aves/widgets/common/aves_filter_chip.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||||
|
import 'package:aves/widgets/fullscreen/info/maps/google_map.dart';
|
||||||
|
import 'package:aves/widgets/fullscreen/info/maps/leaflet_map.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
|
||||||
|
|
||||||
class LocationSection extends StatefulWidget {
|
class LocationSection extends StatefulWidget {
|
||||||
final CollectionLens collection;
|
final CollectionLens collection;
|
||||||
|
@ -94,15 +94,20 @@ class _LocationSectionState extends State<LocationSection> {
|
||||||
padding: EdgeInsets.only(bottom: 8),
|
padding: EdgeInsets.only(bottom: 8),
|
||||||
child: SectionRow(AIcons.location),
|
child: SectionRow(AIcons.location),
|
||||||
),
|
),
|
||||||
ImageMap(
|
if (settings.infoMapStyle == EntryMapStyle.google)
|
||||||
markerId: entry.uri ?? entry.path,
|
EntryGoogleMap(
|
||||||
latLng: LatLng(
|
markerId: entry.uri ?? entry.path,
|
||||||
entry.latLng.item1,
|
latLng: entry.latLng,
|
||||||
entry.latLng.item2,
|
geoUri: entry.geoUri,
|
||||||
|
initialZoom: settings.infoMapZoom,
|
||||||
|
)
|
||||||
|
else
|
||||||
|
EntryLeafletMap(
|
||||||
|
latLng: entry.latLng,
|
||||||
|
geoUri: entry.geoUri,
|
||||||
|
initialZoom: settings.infoMapZoom,
|
||||||
|
style: settings.infoMapStyle,
|
||||||
),
|
),
|
||||||
geoUri: entry.geoUri,
|
|
||||||
initialZoom: settings.infoMapZoom,
|
|
||||||
),
|
|
||||||
if (location.isNotEmpty)
|
if (location.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 8),
|
padding: EdgeInsets.only(top: 8),
|
||||||
|
@ -133,113 +138,21 @@ class _LocationSectionState extends State<LocationSection> {
|
||||||
void _handleChange() => setState(() {});
|
void _handleChange() => setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageMap extends StatefulWidget {
|
enum EntryMapStyle { google, osmHot, stamenToner, stamenWatercolor }
|
||||||
final String markerId;
|
|
||||||
final LatLng latLng;
|
|
||||||
final String geoUri;
|
|
||||||
final double initialZoom;
|
|
||||||
|
|
||||||
const ImageMap({
|
extension ExtraEntryMapStyle on EntryMapStyle {
|
||||||
Key key,
|
String get name {
|
||||||
this.markerId,
|
switch (this) {
|
||||||
this.latLng,
|
case EntryMapStyle.google:
|
||||||
this.geoUri,
|
return 'Google Maps';
|
||||||
this.initialZoom,
|
case EntryMapStyle.osmHot:
|
||||||
}) : super(key: key);
|
return 'Humanitarian OpenStreetMap';
|
||||||
|
case EntryMapStyle.stamenToner:
|
||||||
@override
|
return 'Stamen Toner';
|
||||||
State<StatefulWidget> createState() => ImageMapState();
|
case EntryMapStyle.stamenWatercolor:
|
||||||
}
|
return 'Stamen Watercolor';
|
||||||
|
default:
|
||||||
class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
|
return toString();
|
||||||
GoogleMapController _controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(ImageMap oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (widget.latLng != oldWidget.latLng && _controller != null) {
|
|
||||||
_controller.moveCamera(CameraUpdate.newLatLng(widget.latLng));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
super.build(context);
|
|
||||||
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onScaleStart: (details) {
|
|
||||||
// absorb scale gesture here to prevent scrolling
|
|
||||||
// and triggering by mistake a move to the image page above
|
|
||||||
},
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.all(
|
|
||||||
Radius.circular(16),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
color: Colors.white70,
|
|
||||||
height: 200,
|
|
||||||
child: GoogleMap(
|
|
||||||
// GoogleMap init perf issue: https://github.com/flutter/flutter/issues/28493
|
|
||||||
initialCameraPosition: CameraPosition(
|
|
||||||
target: widget.latLng,
|
|
||||||
zoom: widget.initialZoom,
|
|
||||||
),
|
|
||||||
onMapCreated: (controller) => setState(() => _controller = controller),
|
|
||||||
rotateGesturesEnabled: false,
|
|
||||||
scrollGesturesEnabled: false,
|
|
||||||
zoomControlsEnabled: false,
|
|
||||||
zoomGesturesEnabled: false,
|
|
||||||
liteModeEnabled: false,
|
|
||||||
tiltGesturesEnabled: false,
|
|
||||||
myLocationEnabled: false,
|
|
||||||
myLocationButtonEnabled: false,
|
|
||||||
markers: {
|
|
||||||
Marker(
|
|
||||||
markerId: MarkerId(widget.markerId),
|
|
||||||
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
|
|
||||||
position: widget.latLng,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: 8),
|
|
||||||
TooltipTheme(
|
|
||||||
data: TooltipTheme.of(context).copyWith(
|
|
||||||
preferBelow: false,
|
|
||||||
),
|
|
||||||
child: Column(children: [
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(AIcons.zoomIn),
|
|
||||||
onPressed: _controller == null ? null : () => _zoomBy(1),
|
|
||||||
tooltip: 'Zoom in',
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(AIcons.zoomOut),
|
|
||||||
onPressed: _controller == null ? null : () => _zoomBy(-1),
|
|
||||||
tooltip: 'Zoom out',
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(AIcons.openInNew),
|
|
||||||
onPressed: () => AndroidAppService.openMap(widget.geoUri),
|
|
||||||
tooltip: 'Show on map...',
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _zoomBy(double amount) {
|
|
||||||
settings.infoMapZoom += amount;
|
|
||||||
_controller.animateCamera(CameraUpdate.zoomBy(amount));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => true;
|
|
||||||
}
|
}
|
||||||
|
|
118
lib/widgets/fullscreen/info/maps/google_map.dart
Normal file
118
lib/widgets/fullscreen/info/maps/google_map.dart
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import 'package:aves/model/settings.dart';
|
||||||
|
import 'package:aves/services/android_app_service.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
class EntryGoogleMap extends StatefulWidget {
|
||||||
|
final String markerId;
|
||||||
|
final LatLng latLng;
|
||||||
|
final String geoUri;
|
||||||
|
final double initialZoom;
|
||||||
|
|
||||||
|
EntryGoogleMap({
|
||||||
|
Key key,
|
||||||
|
this.markerId,
|
||||||
|
Tuple2<double, double> latLng,
|
||||||
|
this.geoUri,
|
||||||
|
this.initialZoom,
|
||||||
|
}) : latLng = LatLng(latLng.item1, latLng.item2),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => EntryGoogleMapState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveClientMixin {
|
||||||
|
GoogleMapController _controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(EntryGoogleMap oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.latLng != oldWidget.latLng && _controller != null) {
|
||||||
|
_controller.moveCamera(CameraUpdate.newLatLng(widget.latLng));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onScaleStart: (details) {
|
||||||
|
// absorb scale gesture here to prevent scrolling
|
||||||
|
// and triggering by mistake a move to the image page above
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(16),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white70,
|
||||||
|
height: 200,
|
||||||
|
child: GoogleMap(
|
||||||
|
// GoogleMap init perf issue: https://github.com/flutter/flutter/issues/28493
|
||||||
|
initialCameraPosition: CameraPosition(
|
||||||
|
target: widget.latLng,
|
||||||
|
zoom: widget.initialZoom,
|
||||||
|
),
|
||||||
|
onMapCreated: (controller) => setState(() => _controller = controller),
|
||||||
|
rotateGesturesEnabled: false,
|
||||||
|
scrollGesturesEnabled: false,
|
||||||
|
zoomControlsEnabled: false,
|
||||||
|
zoomGesturesEnabled: false,
|
||||||
|
liteModeEnabled: false,
|
||||||
|
tiltGesturesEnabled: false,
|
||||||
|
myLocationEnabled: false,
|
||||||
|
myLocationButtonEnabled: false,
|
||||||
|
markers: {
|
||||||
|
Marker(
|
||||||
|
markerId: MarkerId(widget.markerId),
|
||||||
|
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
|
||||||
|
position: widget.latLng,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
TooltipTheme(
|
||||||
|
data: TooltipTheme.of(context).copyWith(
|
||||||
|
preferBelow: false,
|
||||||
|
),
|
||||||
|
child: Column(children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(AIcons.zoomIn),
|
||||||
|
onPressed: _controller == null ? null : () => _zoomBy(1),
|
||||||
|
tooltip: 'Zoom in',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(AIcons.zoomOut),
|
||||||
|
onPressed: _controller == null ? null : () => _zoomBy(-1),
|
||||||
|
tooltip: 'Zoom out',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(AIcons.openInNew),
|
||||||
|
onPressed: () => AndroidAppService.openMap(widget.geoUri),
|
||||||
|
tooltip: 'Show on map...',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _zoomBy(double amount) {
|
||||||
|
settings.infoMapZoom += amount;
|
||||||
|
_controller.animateCamera(CameraUpdate.zoomBy(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
}
|
216
lib/widgets/fullscreen/info/maps/leaflet_map.dart
Normal file
216
lib/widgets/fullscreen/info/maps/leaflet_map.dart
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
import 'package:aves/model/settings.dart';
|
||||||
|
import 'package:aves/services/android_app_service.dart';
|
||||||
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
import 'package:aves/widgets/fullscreen/info/maps/scale_layer.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:latlong/latlong.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
import '../location_section.dart';
|
||||||
|
|
||||||
|
class EntryLeafletMap extends StatefulWidget {
|
||||||
|
final LatLng latLng;
|
||||||
|
final String geoUri;
|
||||||
|
final double initialZoom;
|
||||||
|
final EntryMapStyle style;
|
||||||
|
|
||||||
|
EntryLeafletMap({
|
||||||
|
Key key,
|
||||||
|
Tuple2<double, double> latLng,
|
||||||
|
this.geoUri,
|
||||||
|
this.initialZoom,
|
||||||
|
this.style,
|
||||||
|
}) : latLng = LatLng(latLng.item1, latLng.item2),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => EntryLeafletMapState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||||
|
final MapController _mapController = MapController();
|
||||||
|
|
||||||
|
static const markerSize = 40.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(EntryLeafletMap oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (widget.latLng != oldWidget.latLng && _mapController != null) {
|
||||||
|
_mapController.move(widget.latLng, settings.infoMapZoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
final accentColor = Theme.of(context).accentColor;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onScaleStart: (details) {
|
||||||
|
// absorb scale gesture here to prevent scrolling
|
||||||
|
// and triggering by mistake a move to the image page above
|
||||||
|
},
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(16),
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white70,
|
||||||
|
height: 200,
|
||||||
|
child: FlutterMap(
|
||||||
|
options: MapOptions(
|
||||||
|
center: widget.latLng,
|
||||||
|
zoom: widget.initialZoom,
|
||||||
|
interactive: false,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
_buildMapLayer(),
|
||||||
|
ScaleLayerWidget(
|
||||||
|
options: ScaleLayerOptions(
|
||||||
|
lineColor: accentColor,
|
||||||
|
lineWidth: 2,
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: accentColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MarkerLayerWidget(
|
||||||
|
options: MarkerLayerOptions(
|
||||||
|
markers: [
|
||||||
|
Marker(
|
||||||
|
width: markerSize,
|
||||||
|
height: markerSize,
|
||||||
|
point: widget.latLng,
|
||||||
|
builder: (ctx) {
|
||||||
|
return Icon(
|
||||||
|
Icons.place,
|
||||||
|
size: markerSize,
|
||||||
|
color: accentColor,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
mapController: _mapController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
TooltipTheme(
|
||||||
|
data: TooltipTheme.of(context).copyWith(
|
||||||
|
preferBelow: false,
|
||||||
|
),
|
||||||
|
child: Column(children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(AIcons.zoomIn),
|
||||||
|
onPressed: _mapController == null ? null : () => _zoomBy(1),
|
||||||
|
tooltip: 'Zoom in',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(AIcons.zoomOut),
|
||||||
|
onPressed: _mapController == null ? null : () => _zoomBy(-1),
|
||||||
|
tooltip: 'Zoom out',
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(AIcons.openInNew),
|
||||||
|
onPressed: () => AndroidAppService.openMap(widget.geoUri),
|
||||||
|
tooltip: 'Show on map...',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMapLayer() {
|
||||||
|
switch (widget.style) {
|
||||||
|
case EntryMapStyle.osmHot:
|
||||||
|
return OSMHotLayer();
|
||||||
|
case EntryMapStyle.stamenToner:
|
||||||
|
return StamenTonerLayer();
|
||||||
|
case EntryMapStyle.stamenWatercolor:
|
||||||
|
return StamenWatercolorLayer();
|
||||||
|
default:
|
||||||
|
return SizedBox.shrink();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _zoomBy(double amount) {
|
||||||
|
final endZoom = (settings.infoMapZoom + amount).clamp(1.0, 16.0);
|
||||||
|
settings.infoMapZoom = endZoom;
|
||||||
|
|
||||||
|
final zoomTween = Tween<double>(begin: _mapController.zoom, end: endZoom);
|
||||||
|
final controller = AnimationController(duration: const Duration(milliseconds: 200), vsync: this);
|
||||||
|
final animation = CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
|
||||||
|
controller.addListener(() => _mapController.move(widget.latLng, zoomTween.evaluate(animation)));
|
||||||
|
animation.addStatusListener((status) {
|
||||||
|
if (status == AnimationStatus.completed) {
|
||||||
|
controller.dispose();
|
||||||
|
} else if (status == AnimationStatus.dismissed) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controller.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class OSMHotLayer extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TileLayerWidget(
|
||||||
|
options: TileLayerOptions(
|
||||||
|
// attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Tiles style by <a href="https://www.hotosm.org/" target="_blank">Humanitarian OpenStreetMap Team</a> hosted by <a href="https://openstreetmap.fr/" target="_blank">OpenStreetMap France</a>'
|
||||||
|
minZoom: 1,
|
||||||
|
maxZoom: 19,
|
||||||
|
urlTemplate: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
|
||||||
|
subdomains: ['a', 'b', 'c'],
|
||||||
|
retinaMode: MediaQuery.of(context).devicePixelRatio > 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StamenTonerLayer extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TileLayerWidget(
|
||||||
|
options: TileLayerOptions(
|
||||||
|
// attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> — Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
minZoom: 1,
|
||||||
|
maxZoom: 20,
|
||||||
|
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.png',
|
||||||
|
subdomains: ['a', 'b', 'c', 'd'],
|
||||||
|
retinaMode: MediaQuery.of(context).devicePixelRatio > 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StamenWatercolorLayer extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TileLayerWidget(
|
||||||
|
options: TileLayerOptions(
|
||||||
|
// attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> — Map data © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
minZoom: 1,
|
||||||
|
maxZoom: 16,
|
||||||
|
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg',
|
||||||
|
subdomains: ['a', 'b', 'c', 'd'],
|
||||||
|
retinaMode: MediaQuery.of(context).devicePixelRatio > 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
141
lib/widgets/fullscreen/info/maps/scale_layer.dart
Normal file
141
lib/widgets/fullscreen/info/maps/scale_layer.dart
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
|
import 'package:flutter_map/plugin_api.dart';
|
||||||
|
|
||||||
|
import 'scalebar_utils.dart' as util;
|
||||||
|
|
||||||
|
class ScaleLayerOptions extends LayerOptions {
|
||||||
|
TextStyle textStyle;
|
||||||
|
Color lineColor;
|
||||||
|
double lineWidth;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
|
ScaleLayerOptions({
|
||||||
|
Key key,
|
||||||
|
this.textStyle,
|
||||||
|
this.lineColor = Colors.white,
|
||||||
|
this.lineWidth = 2,
|
||||||
|
this.padding,
|
||||||
|
rebuild,
|
||||||
|
}) : super(key: key, rebuild: rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScaleLayerWidget extends StatelessWidget {
|
||||||
|
final ScaleLayerOptions options;
|
||||||
|
|
||||||
|
ScaleLayerWidget({@required this.options}) : super(key: options.key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final mapState = MapState.of(context);
|
||||||
|
return ScaleLayer(options, mapState, mapState.onMoved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScaleLayer extends StatelessWidget {
|
||||||
|
final ScaleLayerOptions scaleLayerOpts;
|
||||||
|
final MapState map;
|
||||||
|
final Stream<Null> stream;
|
||||||
|
final scale = [
|
||||||
|
25000000,
|
||||||
|
15000000,
|
||||||
|
8000000,
|
||||||
|
4000000,
|
||||||
|
2000000,
|
||||||
|
1000000,
|
||||||
|
500000,
|
||||||
|
250000,
|
||||||
|
100000,
|
||||||
|
50000,
|
||||||
|
25000,
|
||||||
|
15000,
|
||||||
|
8000,
|
||||||
|
4000,
|
||||||
|
2000,
|
||||||
|
1000,
|
||||||
|
500,
|
||||||
|
250,
|
||||||
|
100,
|
||||||
|
50,
|
||||||
|
25,
|
||||||
|
10,
|
||||||
|
5,
|
||||||
|
];
|
||||||
|
|
||||||
|
ScaleLayer(this.scaleLayerOpts, this.map, this.stream) : super(key: scaleLayerOpts.key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StreamBuilder<Null>(
|
||||||
|
stream: stream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
var zoom = map.zoom;
|
||||||
|
var distance = scale[max(0, min(20, zoom.round() + 2))].toDouble();
|
||||||
|
var center = map.center;
|
||||||
|
var start = map.project(center);
|
||||||
|
var targetPoint = util.calculateEndingGlobalCoordinates(center, 90, distance);
|
||||||
|
var end = map.project(targetPoint);
|
||||||
|
var displayDistance = distance > 999 ? '${(distance / 1000).toStringAsFixed(0)} km' : '${distance.toStringAsFixed(0)} m';
|
||||||
|
double width = (end.x - start.x);
|
||||||
|
|
||||||
|
return CustomPaint(
|
||||||
|
painter: ScalePainter(
|
||||||
|
width,
|
||||||
|
displayDistance,
|
||||||
|
lineColor: scaleLayerOpts.lineColor,
|
||||||
|
lineWidth: scaleLayerOpts.lineWidth,
|
||||||
|
padding: scaleLayerOpts.padding,
|
||||||
|
textStyle: scaleLayerOpts.textStyle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScalePainter extends CustomPainter {
|
||||||
|
ScalePainter(this.width, this.text, {this.padding, this.textStyle, this.lineWidth, this.lineColor});
|
||||||
|
|
||||||
|
final double width;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
final String text;
|
||||||
|
TextStyle textStyle;
|
||||||
|
double lineWidth;
|
||||||
|
Color lineColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(ui.Canvas canvas, ui.Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = lineColor
|
||||||
|
..strokeCap = StrokeCap.square
|
||||||
|
..strokeWidth = lineWidth;
|
||||||
|
|
||||||
|
var sizeForStartEnd = 4;
|
||||||
|
var paddingLeft = padding == null ? 0 : padding.left + sizeForStartEnd / 2;
|
||||||
|
var paddingTop = padding == null ? 0 : padding.top;
|
||||||
|
|
||||||
|
var textSpan = TextSpan(style: textStyle, text: text);
|
||||||
|
var textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr)..layout();
|
||||||
|
textPainter.paint(canvas, Offset(width / 2 - textPainter.width / 2 + paddingLeft, paddingTop));
|
||||||
|
paddingTop += textPainter.height;
|
||||||
|
var p1 = Offset(paddingLeft, sizeForStartEnd + paddingTop);
|
||||||
|
var p2 = Offset(paddingLeft + width, sizeForStartEnd + paddingTop);
|
||||||
|
// draw start line
|
||||||
|
canvas.drawLine(Offset(paddingLeft, paddingTop), Offset(paddingLeft, sizeForStartEnd + paddingTop), paint);
|
||||||
|
// draw middle line
|
||||||
|
var middleX = width / 2 + paddingLeft - lineWidth / 2;
|
||||||
|
canvas.drawLine(Offset(middleX, paddingTop + sizeForStartEnd / 2), Offset(middleX, sizeForStartEnd + paddingTop), paint);
|
||||||
|
// draw end line
|
||||||
|
canvas.drawLine(Offset(width + paddingLeft, paddingTop), Offset(width + paddingLeft, sizeForStartEnd + paddingTop), paint);
|
||||||
|
// draw bottom line
|
||||||
|
canvas.drawLine(p1, p2, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CustomPainter oldDelegate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
126
lib/widgets/fullscreen/info/maps/scalebar_utils.dart
Normal file
126
lib/widgets/fullscreen/info/maps/scalebar_utils.dart
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:latlong/latlong.dart';
|
||||||
|
|
||||||
|
const double piOver180 = PI / 180.0;
|
||||||
|
|
||||||
|
double toDegrees(double radians) {
|
||||||
|
return radians / piOver180;
|
||||||
|
}
|
||||||
|
|
||||||
|
double toRadians(double degrees) {
|
||||||
|
return degrees * piOver180;
|
||||||
|
}
|
||||||
|
|
||||||
|
LatLng calculateEndingGlobalCoordinates(LatLng start, double startBearing, double distance) {
|
||||||
|
var mSemiMajorAxis = 6378137.0; //WGS84 major axis
|
||||||
|
var mSemiMinorAxis = (1.0 - 1.0 / 298.257223563) * 6378137.0;
|
||||||
|
var mFlattening = 1.0 / 298.257223563;
|
||||||
|
// double mInverseFlattening = 298.257223563;
|
||||||
|
|
||||||
|
var a = mSemiMajorAxis;
|
||||||
|
var b = mSemiMinorAxis;
|
||||||
|
var aSquared = a * a;
|
||||||
|
var bSquared = b * b;
|
||||||
|
var f = mFlattening;
|
||||||
|
var phi1 = toRadians(start.latitude);
|
||||||
|
var alpha1 = toRadians(startBearing);
|
||||||
|
var cosAlpha1 = cos(alpha1);
|
||||||
|
var sinAlpha1 = sin(alpha1);
|
||||||
|
var s = distance;
|
||||||
|
var tanU1 = (1.0 - f) * tan(phi1);
|
||||||
|
var cosU1 = 1.0 / sqrt(1.0 + tanU1 * tanU1);
|
||||||
|
var sinU1 = tanU1 * cosU1;
|
||||||
|
|
||||||
|
// eq. 1
|
||||||
|
var sigma1 = atan2(tanU1, cosAlpha1);
|
||||||
|
|
||||||
|
// eq. 2
|
||||||
|
var sinAlpha = cosU1 * sinAlpha1;
|
||||||
|
|
||||||
|
var sin2Alpha = sinAlpha * sinAlpha;
|
||||||
|
var cos2Alpha = 1 - sin2Alpha;
|
||||||
|
var uSquared = cos2Alpha * (aSquared - bSquared) / bSquared;
|
||||||
|
|
||||||
|
// eq. 3
|
||||||
|
var A = 1 + (uSquared / 16384) * (4096 + uSquared * (-768 + uSquared * (320 - 175 * uSquared)));
|
||||||
|
|
||||||
|
// eq. 4
|
||||||
|
var B = (uSquared / 1024) * (256 + uSquared * (-128 + uSquared * (74 - 47 * uSquared)));
|
||||||
|
|
||||||
|
// iterate until there is a negligible change in sigma
|
||||||
|
double deltaSigma;
|
||||||
|
var sOverbA = s / (b * A);
|
||||||
|
var sigma = sOverbA;
|
||||||
|
double sinSigma;
|
||||||
|
var prevSigma = sOverbA;
|
||||||
|
double sigmaM2;
|
||||||
|
double cosSigmaM2;
|
||||||
|
double cos2SigmaM2;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// eq. 5
|
||||||
|
sigmaM2 = 2.0 * sigma1 + sigma;
|
||||||
|
cosSigmaM2 = cos(sigmaM2);
|
||||||
|
cos2SigmaM2 = cosSigmaM2 * cosSigmaM2;
|
||||||
|
sinSigma = sin(sigma);
|
||||||
|
var cosSignma = cos(sigma);
|
||||||
|
|
||||||
|
// eq. 6
|
||||||
|
deltaSigma = B * sinSigma * (cosSigmaM2 + (B / 4.0) * (cosSignma * (-1 + 2 * cos2SigmaM2) - (B / 6.0) * cosSigmaM2 * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM2)));
|
||||||
|
|
||||||
|
// eq. 7
|
||||||
|
sigma = sOverbA + deltaSigma;
|
||||||
|
|
||||||
|
// break after converging to tolerance
|
||||||
|
if ((sigma - prevSigma).abs() < 0.0000000000001) break;
|
||||||
|
|
||||||
|
prevSigma = sigma;
|
||||||
|
}
|
||||||
|
|
||||||
|
sigmaM2 = 2.0 * sigma1 + sigma;
|
||||||
|
cosSigmaM2 = cos(sigmaM2);
|
||||||
|
cos2SigmaM2 = cosSigmaM2 * cosSigmaM2;
|
||||||
|
|
||||||
|
var cosSigma = cos(sigma);
|
||||||
|
sinSigma = sin(sigma);
|
||||||
|
|
||||||
|
// eq. 8
|
||||||
|
var phi2 = atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, (1.0 - f) * sqrt(sin2Alpha + pow(sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1, 2.0)));
|
||||||
|
|
||||||
|
// eq. 9
|
||||||
|
// This fixes the pole crossing defect spotted by Matt Feemster. When a
|
||||||
|
// path passes a pole and essentially crosses a line of latitude twice -
|
||||||
|
// once in each direction - the longitude calculation got messed up.
|
||||||
|
// Using
|
||||||
|
// atan2 instead of atan fixes the defect. The change is in the next 3
|
||||||
|
// lines.
|
||||||
|
// double tanLambda = sinSigma * sinAlpha1 / (cosU1 * cosSigma - sinU1 *
|
||||||
|
// sinSigma * cosAlpha1);
|
||||||
|
// double lambda = Math.atan(tanLambda);
|
||||||
|
var lambda = atan2(sinSigma * sinAlpha1, (cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1));
|
||||||
|
|
||||||
|
// eq. 10
|
||||||
|
var C = (f / 16) * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha));
|
||||||
|
|
||||||
|
// eq. 11
|
||||||
|
var L = lambda - (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2)));
|
||||||
|
|
||||||
|
// eq. 12
|
||||||
|
// double alpha2 = Math.atan2(sinAlpha, -sinU1 * sinSigma + cosU1 *
|
||||||
|
// cosSigma * cosAlpha1);
|
||||||
|
|
||||||
|
// build result
|
||||||
|
var latitude = toDegrees(phi2);
|
||||||
|
var longitude = start.longitude + toDegrees(L);
|
||||||
|
|
||||||
|
// if ((endBearing != null) && (endBearing.length > 0)) {
|
||||||
|
// endBearing[0] = toDegrees(alpha2);
|
||||||
|
// }
|
||||||
|
|
||||||
|
latitude = latitude < -90 ? -90 : latitude;
|
||||||
|
latitude = latitude > 90 ? 90 : latitude;
|
||||||
|
longitude = longitude < -180 ? -180 : longitude;
|
||||||
|
longitude = longitude > 180 ? 180 : longitude;
|
||||||
|
return LatLng(latitude, longitude);
|
||||||
|
}
|
65
lib/widgets/settings_page.dart
Normal file
65
lib/widgets/settings_page.dart
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import 'package:aves/model/settings.dart';
|
||||||
|
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
|
||||||
|
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SettingsPage extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MediaQueryDataProvider(
|
||||||
|
child: DefaultTabController(
|
||||||
|
length: 4,
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Preferences'),
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: ListView(
|
||||||
|
padding: EdgeInsets.all(16),
|
||||||
|
children: [
|
||||||
|
Text('Maps'),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text('Storage:'),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Flexible(child: InfoMapStyleSelector()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InfoMapStyleSelector extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_InfoMapStyleSelectorState createState() => _InfoMapStyleSelectorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InfoMapStyleSelectorState extends State<InfoMapStyleSelector> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DropdownButton<EntryMapStyle>(
|
||||||
|
items: EntryMapStyle.values
|
||||||
|
.map((style) => DropdownMenuItem(
|
||||||
|
value: style,
|
||||||
|
child: Text(
|
||||||
|
style.name,
|
||||||
|
softWrap: false,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
value: settings.infoMapStyle,
|
||||||
|
onChanged: (style) {
|
||||||
|
settings.infoMapStyle = style;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
140
pubspec.lock
140
pubspec.lock
|
@ -1,6 +1,13 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -36,6 +43,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
cached_network_image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cached_network_image
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0+1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -78,6 +92,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.14.13"
|
version: "1.14.13"
|
||||||
|
console_log_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: console_log_handler
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.6"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -150,6 +171,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_cache_manager:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_cache_manager
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
flutter_ijkplayer:
|
flutter_ijkplayer:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -159,6 +187,20 @@ packages:
|
||||||
url: "git://github.com/deckerst/flutter_ijkplayer.git"
|
url: "git://github.com/deckerst/flutter_ijkplayer.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.3.7"
|
version: "0.3.7"
|
||||||
|
flutter_image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_image
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
flutter_map:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_map
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.1+1"
|
||||||
flutter_markdown:
|
flutter_markdown:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -225,6 +267,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.3"
|
version: "1.0.3"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.2"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
image:
|
image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -246,6 +302,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.2"
|
version: "0.6.2"
|
||||||
|
latlong:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: latlong
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.1"
|
||||||
|
lists:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lists
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -274,6 +344,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.8"
|
version: "1.1.8"
|
||||||
|
mgrs_dart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mgrs_dart
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -323,6 +400,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4"
|
version: "0.1.4"
|
||||||
|
path_provider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.11"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -330,6 +414,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.1+2"
|
version: "0.0.1+2"
|
||||||
|
path_provider_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.4+3"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -409,6 +500,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
positioned_tap_detector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: positioned_tap_detector
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
printing:
|
printing:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -423,6 +521,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.13"
|
version: "3.0.13"
|
||||||
|
proj4dart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: proj4dart
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -451,6 +556,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.24.1"
|
||||||
screen:
|
screen:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -575,6 +687,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.17"
|
version: "0.2.17"
|
||||||
|
transparent_image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: transparent_image
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
tuple:
|
tuple:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -589,6 +708,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
unicode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: unicode
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.4"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -638,6 +764,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
validate:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: validate
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.7.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -645,6 +778,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
version: "2.0.8"
|
||||||
|
wkt_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: wkt_parser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.7"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -49,6 +49,7 @@ dependencies:
|
||||||
# path: ../flutter_ijkplayer
|
# path: ../flutter_ijkplayer
|
||||||
git:
|
git:
|
||||||
url: git://github.com/deckerst/flutter_ijkplayer.git
|
url: git://github.com/deckerst/flutter_ijkplayer.git
|
||||||
|
flutter_map:
|
||||||
flutter_markdown:
|
flutter_markdown:
|
||||||
flutter_native_timezone:
|
flutter_native_timezone:
|
||||||
flutter_staggered_animations:
|
flutter_staggered_animations:
|
||||||
|
@ -56,6 +57,7 @@ dependencies:
|
||||||
geocoder:
|
geocoder:
|
||||||
google_maps_flutter:
|
google_maps_flutter:
|
||||||
intl:
|
intl:
|
||||||
|
latlong: # for flutter_map
|
||||||
outline_material_icons:
|
outline_material_icons:
|
||||||
package_info:
|
package_info:
|
||||||
palette_generator:
|
palette_generator:
|
||||||
|
|
Loading…
Reference in a new issue