diff --git a/lib/attributionMapPlugin.dart b/lib/attributionMapPlugin.dart new file mode 100644 index 0000000..a33b457 --- /dev/null +++ b/lib/attributionMapPlugin.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_map/plugin_api.dart'; + +class AttributionPluginOptions extends LayerOptions { + final String text; + + AttributionPluginOptions({this.text = ""}); +} + +class AttributionPlugin implements MapPlugin { + @override + Widget createLayer(LayerOptions options, MapState mapState) { + if (options is AttributionPluginOptions) { + var style = new TextStyle( + // fontWeight: FontWeight.bold, + fontSize: 12.0, + color: Colors.white, + ); + return Padding( + padding: const EdgeInsets.all(5.0), + child: new Text( + options.text, + style: style, + ), + ); + } + throw ("Unknown options type for Attribution" + "plugin: $options"); + } + + @override + bool supportsLayer(LayerOptions options) { + return options is AttributionPluginOptions; + } +} diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart index 547b199..4946ec5 100644 --- a/lib/generated/i18n.dart +++ b/lib/generated/i18n.dart @@ -64,7 +64,7 @@ class S implements WidgetsLocalizations { String get notPermsUbication => "We don't have permission to get your location"; String get notifyAFire => "Notify a fire"; String get privacyPolicy => "Privacy Policy"; - String get supportPageDescription => "You can support this initiative, spreading the word, helping us to develop our software, translating this app to your language, etc."; + String get supportPageDescription => "You can support this initiative, spreading the word, helping us to develop our software, translating this app to your language, suggesting other public data sources of fires to use, etc."; String get supportThisInitiative => "Support this initiative"; String get toDeleteThisNotification => "Slide horizontally to delete this notification"; String get toDeleteThisPlace => "Slide horizontally to delete this place"; @@ -158,7 +158,7 @@ class es extends S { @override String get tweetAboutAFireTitle => "Twittea"; @override - String get supportPageDescription => "Puedes apoyar esta iniciativa, dando difusión, ayudándonos a desarrollar nuestro software, traduciendo esta aplicación a tu idioma, etc."; + String get supportPageDescription => "Puedes apoyar esta iniciativa, dando difusión, ayudándonos a desarrollar nuestro software, traduciendo esta aplicación a tu idioma, sugeriendo otras fuentes de datos públicas de fuegos para usar, etc."; @override String get aMinute => "un minuto"; @override diff --git a/lib/genericMap.dart b/lib/genericMap.dart index 2393234..58ac92a 100644 --- a/lib/genericMap.dart +++ b/lib/genericMap.dart @@ -10,6 +10,7 @@ import 'package:flutter_redux/flutter_redux.dart'; import 'package:latlong/latlong.dart'; import 'package:share/share.dart'; +import 'attributionMapPlugin.dart'; import 'colors.dart'; import 'dummyMapPlugin.dart'; import 'fireMarkType.dart'; @@ -22,7 +23,7 @@ import 'models/fireMapState.dart'; import 'redux/actions.dart'; import 'slider.dart'; import 'zoomMapPlugin.dart'; - +import 'layerSelectorMapPlugin.dart'; @immutable class _ViewModel { final String serverUrl; @@ -118,18 +119,21 @@ class _genericMapState extends State { assert(_location != null); FireMapState mapState = view.mapState; FireMapStatus status = mapState.status; - print('Build map with status: $status'); + FireMapLayer layer = mapState.layer; + print('Build map with status: $status and layer $layer'); + + double maxZoom = 18.0; // works? MapOptions mapOptions = new MapOptions( center: new LatLng(_location.lat, _location.lon), plugins: globals.isDevelopment - ? [new ZoomMapPlugin()] - : [new DummyMapPlugin()], + ? [new ZoomMapPlugin(), new AttributionPlugin(), new LayerSelectorMapPlugin()] + : [new DummyMapPlugin(), new AttributionPlugin(), new LayerSelectorMapPlugin()], // this works ? interactive: false, zoom: 13.0, // THIS does not works as expected - // maxZoom: 6.0, + maxZoom: maxZoom, onTap: (callback) { if (status == FireMapStatus.edit) { _location = _location.copyWith( @@ -155,19 +159,49 @@ class _genericMapState extends State { : status == FireMapStatus.subscriptionConfirm ? Icons.check : Icons.notifications_off; + + String baseLayer; + List subdomains = []; + String attribution; + switch (layer) { + case FireMapLayer.osmc: + case FireMapLayer.osmcGrey: + attribution = '© OpenStreetMap contributors'; + break; + case FireMapLayer.esri: + case FireMapLayer.esriSatellite: + case FireMapLayer.esriTerrain: + attribution = '© ESRI'; + } + /* tiles from: https://github.com/dceejay/RedMap/blob/1914ed3b9ce4e8a496049849a93282730b4fff02/worldmap/index.html */ + switch (layer) { + case FireMapLayer.osmc: + subdomains = ['a', 'b', 'c']; + baseLayer = 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png'; + break; + case FireMapLayer.osmcGrey: + subdomains = ['a', 'b', 'c']; + baseLayer = 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png'; + break; + case FireMapLayer.esri: + baseLayer = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}'; + break; + case FireMapLayer.esriSatellite: + baseLayer = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'; + break; + case FireMapLayer.esriTerrain: + baseLayer = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}'; + } FlutterMap map = new FlutterMap( options: mapOptions, mapController: mapController, layers: [ new TileLayerOptions( - maxZoom: 6.0, - subdomains: ['a', 'b', 'c'], - urlTemplate: - 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', + maxZoom: maxZoom, + urlTemplate: baseLayer, + subdomains: subdomains, additionalOptions: { // 'opacity': '0.1', - 'attribution': - '© OpenStreetMap contributors' }, ), globals.isDevelopment @@ -181,6 +215,9 @@ class _genericMapState extends State { mapState.falsePos, mapState.fireNotification != null), ), + // new AttributionPluginOptions(text: "© OpenStreetMap contributors"), + new LayerSelectorMapPluginOptions(), + new AttributionPluginOptions(text: attribution), ], ); // mapController. diff --git a/lib/layerSelectorMapPlugin.dart b/lib/layerSelectorMapPlugin.dart new file mode 100644 index 0000000..d5dc01c --- /dev/null +++ b/lib/layerSelectorMapPlugin.dart @@ -0,0 +1,57 @@ +import 'package:comunes_flutter/comunes_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter_map/plugin_api.dart'; +import 'package:flutter_simple_dependency_injection/injector.dart'; +import 'package:redux/redux.dart'; + +import 'models/appState.dart'; +import 'redux/actions.dart'; + +class LayerSelectorMapPluginOptions extends LayerOptions { + final String text; + + LayerSelectorMapPluginOptions({this.text = ""}); +} + +// https://github.com/apptreesoftware/flutter_map/blob/master/flutter_map_example/lib/pages/plugin_api.dart +class LayerSelectorMapPlugin extends MapPlugin { + Widget LayerSelectorButton(Store store) { + return new FloatingActionButton( + backgroundColor: Colors.black26, + mini: true, + child: Icon(Icons.layers, /* size: 40.0, color: Colors.black45, */ ), + onPressed: () { + store.dispatch(new ToggleMapLayerAction()); + }); + } + + @override + Widget createLayer(LayerOptions options, MapState mapState) { + Store store = Injector.getInjector().get>(); + if (options is LayerSelectorMapPluginOptions) { + return LayoutBuilder( + builder: (context, constraints) => + Stack(fit: StackFit.expand, children: [ + Positioned( + top: constraints.maxHeight - 60, + left: 10.0, + child: new CenteredRow( + children: [ + new Column( + children: [LayerSelectorButton(store)], + ) + ], + ), + ) + ])); + } + throw ("Unknown options type for LayerSelectorMapPlugin" + "plugin: $options"); + } + + @override + bool supportsLayer(LayerOptions options) { + return options is LayerSelectorMapPluginOptions; + } +} diff --git a/lib/models/fireMapState.dart b/lib/models/fireMapState.dart index 08a8961..586e39b 100644 --- a/lib/models/fireMapState.dart +++ b/lib/models/fireMapState.dart @@ -3,10 +3,12 @@ import 'package:fires_flutter/models/fireNotification.dart'; import 'package:meta/meta.dart'; enum FireMapStatus { view, subscriptionConfirm, unsubscribe, edit, viewFireNotification } +enum FireMapLayer { osmcGrey, esriSatellite, osmc, esri, esriTerrain } @immutable class FireMapState { final FireMapStatus status; + final FireMapLayer layer; final int numFires; final FireNotification fireNotification; final List fires; @@ -16,6 +18,7 @@ class FireMapState { const FireMapState.initial() : this.status = FireMapStatus.view, + this.layer = FireMapLayer.osmcGrey, this.yourLocation = null, this.fireNotification = null, this.numFires = 0, @@ -25,6 +28,7 @@ class FireMapState { FireMapState( {this.status: FireMapStatus.view, + this.layer: FireMapLayer.osmcGrey, this.yourLocation, this.numFires, this.fires, @@ -34,6 +38,7 @@ class FireMapState { FireMapState copyWith({ FireMapStatus status, + FireMapLayer layer, YourLocation yourLocation, FireNotification fireNotication, int numFires, @@ -48,6 +53,7 @@ class FireMapState { fires: fires ?? this.fires, falsePos: falsePos ?? this.falsePos, industries: industries ?? this.industries, + layer: layer ?? this.layer, status: status ?? this.status); } @@ -57,6 +63,7 @@ class FireMapState { other is FireMapState && runtimeType == other.runtimeType && status == other.status && + layer == other.layer && numFires == other.numFires && fireNotification == other.fireNotification && fires == other.fires && @@ -67,6 +74,7 @@ class FireMapState { @override int get hashCode => status.hashCode ^ + layer.hashCode ^ numFires.hashCode ^ fireNotification.hashCode ^ fires.hashCode ^ @@ -76,7 +84,7 @@ class FireMapState { @override String toString() { - return 'FireMapState{status: $status, numFires: $numFires, fires: ${fires + return 'FireMapState{status: $status, layer: $layer, numFires: $numFires, fires: ${fires .length}, falsePos: ${falsePos.length}, industries: ${industries .length}, yourLocation: $yourLocation, fireNotification: $fireNotification}'; } diff --git a/lib/redux/fireMapActions.dart b/lib/redux/fireMapActions.dart index 0535552..6b7333d 100644 --- a/lib/redux/fireMapActions.dart +++ b/lib/redux/fireMapActions.dart @@ -53,3 +53,5 @@ class EditCancelYourLocationAction extends FiresMapActions { YourLocation loc; EditCancelYourLocationAction(this.loc); } + +class ToggleMapLayerAction extends FiresMapActions {} \ No newline at end of file diff --git a/lib/redux/fireMapReducer.dart b/lib/redux/fireMapReducer.dart index 5acb58d..d6790e6 100644 --- a/lib/redux/fireMapReducer.dart +++ b/lib/redux/fireMapReducer.dart @@ -20,6 +20,8 @@ final fireMapReducer = combineReducers([ _editConfirmYourLocationMap), new TypedReducer( _editCancelYourLocationMap), + new TypedReducer( + _toggleMapLayer), new TypedReducer( _updateYourLocationMap) ]); @@ -89,3 +91,13 @@ FireMapState _editCancelYourLocationMap( FireMapStatus restoreStatusAfterSave(loc) => loc.subscribed ? FireMapStatus.unsubscribe : FireMapStatus.view; + +FireMapState _toggleMapLayer( + FireMapState state, ToggleMapLayerAction action) { + FireMapLayer currentLayer = state.layer; + List list = FireMapLayer.values; + int currentPos = list.indexOf(currentLayer); + currentPos++; + FireMapLayer next = list.elementAt(currentPos >= list.length ? 0: currentPos); + return state.copyWith(layer: next); +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index b48c7d5..0e27495 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -54,7 +54,7 @@ "callEmergencyServicesDescription": "You should call 112 if you have not already done so to notify the emergency services.", "tweetAboutAFireTitle": "Tweet about", "tweetAboutAFireDescription": "Additionally if you use twitter you can share additional information with the emergency services, for example, attaching photos to the tweet if you have good visibility of the fire, when you took the photos, exact location, etc. Use #hashtags type #IFMinicipalTerminal for example #IFJumilla (Forest Fire in Jumilla).", - "supportPageDescription": "You can support this initiative, spreading the word, helping us to develop our software, translating this app to your language, etc.", + "supportPageDescription": "You can support this initiative, spreading the word, helping us to develop our software, translating this app to your language, suggesting other public data sources of fires to use, etc.", "fireNotificationsTitleShort": "Notifications", "fireNotificationsTitle": "Fire Notifications", "fireNotificationTitle": "Fire Notification", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 08f8b57..619de3b 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -54,7 +54,7 @@ "callEmergencyServicesDescription": "Deberías llamar al 112 si no lo has hecho ya para avisar a los servicios de emergencia.", "tweetAboutAFireTitle": "Twittea", "tweetAboutAFireDescription": "Adicionalmente si usas twitter puedes compartir información adicional con los servicios de emergencia, por ejemplo, adjuntando fotos al tweet si tienes buena visibilidad del fuego, cuando tomaste las fotos, ubicación exacta, etc. Usa #hashtags tipo #IFTerminoMunicipal por ejemplo #IFJumilla (Incendio Forestal en Jumilla).", - "supportPageDescription": "Puedes apoyar esta iniciativa, dando difusión, ayudándonos a desarrollar nuestro software, traduciendo esta aplicación a tu idioma, etc.", + "supportPageDescription": "Puedes apoyar esta iniciativa, dando difusión, ayudándonos a desarrollar nuestro software, traduciendo esta aplicación a tu idioma, sugeriendo otras fuentes de datos públicas de fuegos para usar, etc.", "fireNotificationsTitle": "Notificaciones de fuegos", "fireNotificationTitle": "Notificación de fuego", "fireNotificationsTitleShort": "Notificaciones",