diff --git a/lib/fireMarker.dart b/lib/fireMarker.dart index 284a859..a11092b 100644 --- a/lib/fireMarker.dart +++ b/lib/fireMarker.dart @@ -7,12 +7,12 @@ import 'fireMarkType.dart'; class FireMarker extends Marker { - FireMarker(location, type, + FireMarker(LatLng pos, type, [onTap = null, AnchorPos anchor = AnchorPos.center, Anchor anchorOverride]) : super( width: 80.0, height: 80.0, - point: new LatLng(location.lat, location.lon), + point: pos, builder: (ctx) => new Container( child: new GestureDetector( child: new FireMarkerIcon(type), onTap: onTap) diff --git a/lib/genericMap.dart b/lib/genericMap.dart index 4943120..00c8e94 100644 --- a/lib/genericMap.dart +++ b/lib/genericMap.dart @@ -1,7 +1,6 @@ import 'dart:core'; import 'package:comunes_flutter/comunes_flutter.dart'; -import 'package:fires_flutter/models/basicLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -18,12 +17,13 @@ import 'fireMarker.dart'; import 'generated/i18n.dart'; import 'genericMapBottom.dart'; import 'globals.dart' as globals; +import 'layerSelectorMapPlugin.dart'; import 'models/appState.dart'; import 'models/fireMapState.dart'; import 'redux/actions.dart'; import 'slider.dart'; import 'zoomMapPlugin.dart'; -import 'layerSelectorMapPlugin.dart'; + @immutable class _ViewModel { final String serverUrl; @@ -36,6 +36,7 @@ class _ViewModel { final OnLocationEditConfirm onEditConfirm; final OnLocationEditCancel onEditCancel; final OnLocationEditing onEditing; + final OnFalsePositive onFalsePositive; _ViewModel( {@required this.mapState, @@ -47,6 +48,7 @@ class _ViewModel { @required this.onEdit, @required this.onEditing, @required this.onEditConfirm, + @required this.onFalsePositive, @required this.onEditCancel}); @override @@ -108,6 +110,8 @@ class _genericMapState extends State { store.dispatch(new UpdateYourLocationMapAction(loc)); store.dispatch(new EditConfirmYourLocationAction(loc)); }, + onFalsePositive: (notif, type) => store + .dispatch(new MarkFireAsFalsePositiveAction(notif, type)), serverUrl: store.state.serverUrl, mapState: store.state.fireMapState); }, @@ -127,8 +131,16 @@ class _genericMapState extends State { MapOptions mapOptions = new MapOptions( center: new LatLng(_location.lat, _location.lon), plugins: globals.isDevelopment - ? [new ZoomMapPlugin(), new AttributionPlugin(), new LayerSelectorMapPlugin()] - : [new DummyMapPlugin(), new AttributionPlugin(), new LayerSelectorMapPlugin()], + ? [ + new ZoomMapPlugin(), + new AttributionPlugin(), + new LayerSelectorMapPlugin() + ] + : [ + new DummyMapPlugin(), + new AttributionPlugin(), + new LayerSelectorMapPlugin() + ], // this works ? interactive: false, zoom: 13.0, @@ -166,8 +178,8 @@ class _genericMapState extends State { switch (layer) { case FireMapLayer.osmc: case FireMapLayer.osmcGrey: - attribution = '© OpenStreetMap contributors'; - break; + attribution = '© OpenStreetMap contributors'; + break; case FireMapLayer.esri: case FireMapLayer.esriSatellite: case FireMapLayer.esriTerrain: @@ -177,20 +189,24 @@ class _genericMapState extends State { switch (layer) { case FireMapLayer.osmc: subdomains = ['a', 'b', 'c']; - baseLayer = 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png'; + 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'; + 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}'; + 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}'; + 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}'; + baseLayer = + 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}'; } FlutterMap map = new FlutterMap( options: mapOptions, @@ -209,7 +225,7 @@ class _genericMapState extends State { : new DummyMapPluginOptions(), new MarkerLayerOptions( markers: buildMarkers( - _location, + mapState.status == FireMapStatus.viewFireNotification ? new LatLng(mapState.fireNotification.lat, mapState.fireNotification.lon): new LatLng(_location.lat, _location.lon), mapState.fires, mapState.industries, mapState.falsePos, @@ -290,9 +306,11 @@ class _genericMapState extends State { floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, bottomNavigationBar: new GenericMapBottom( - onSave: () => view.onEditConfirm(_location), - onCancel: () => view.onEditCancel(_initialLocation), - state: view.mapState, + onSave: () => view.onEditConfirm(_location), + onCancel: () => view.onEditCancel(_initialLocation), + onFalsePositive: (sealed, type) => + view.onFalsePositive(sealed, type), + state: view.mapState, scaffoldKey: _scaffoldKey, ), body: LayoutBuilder( @@ -362,28 +380,30 @@ class _genericMapState extends State { } } - List buildMarkers(YourLocation yourLocation, List fires, - List falsePos, List industries, bool isNotif) { + List buildMarkers(LatLng pos, List fires, + List falsePosList, List industries, bool isNotif) { List markers = []; + print('building markers: fires: ${fires.length} falsePos: ${falsePosList.length} industries: ${industries.length}, isNotif: ${isNotif} '); const calibrate = false; // useful when we change the fire icons size markers.add(FireMarker( - yourLocation, isNotif ? FireMarkType.fire : FireMarkType.position)); - if (calibrate) markers.add(FireMarker(yourLocation, FireMarkType.pixel)); - falsePos.forEach((fire) { - var coords = fire['geo']['coordinates']; - var loc = BasicLocation(lon: coords[0], lat: coords[1]); + pos, isNotif ? FireMarkType.fire : FireMarkType.position)); + if (calibrate) markers.add(FireMarker(pos, FireMarkType.pixel)); + falsePosList.forEach((falsePos) { + var coords = falsePos['geo']['coordinates']; + print('false pos: ${coords}'); + var loc = LatLng(coords[1], coords[0]); markers.add(FireMarker(loc, FireMarkType.falsePos)); if (calibrate) markers.add(FireMarker(loc, FireMarkType.pixel)); }); - industries.forEach((fire) { + industries.forEach((industry) { // print(fire['geo']['coordinates']); - var coords = fire['geo']['coordinates']; - var loc = BasicLocation(lon: coords[0], lat: coords[1]); + var coords = industry['geo']['coordinates']; + var loc = LatLng(coords[1], coords[0]); markers.add(FireMarker(loc, FireMarkType.industry)); if (calibrate) markers.add(FireMarker(loc, FireMarkType.pixel)); }); fires.forEach((fire) { - var loc = new BasicLocation(lat: fire['lat'], lon: fire['lon']); + var loc = new LatLng(fire['lat'], fire['lon']); markers.add( FireMarker(loc, FireMarkType.fire, () => print('marker pressed'))); markers.add(FireMarker(loc, FireMarkType.pixel)); diff --git a/lib/genericMapBottom.dart b/lib/genericMapBottom.dart index b50a76b..9cd257c 100644 --- a/lib/genericMapBottom.dart +++ b/lib/genericMapBottom.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:fires_flutter/models/yourLocation.dart'; +import 'package:fires_flutter/models/fireNotification.dart'; import 'package:flutter/material.dart'; import 'colors.dart'; @@ -14,17 +15,19 @@ import 'models/fireMapState.dart'; typedef void OnSave(); typedef void OnCancel(); -typedef void OnFalsePositive(); +typedef void OnFalsePositive(FireNotification notif, FalsePositiveType type); class GenericMapBottom extends StatelessWidget { final OnSave onSave; final OnCancel onCancel; + final OnFalsePositive onFalsePositive; final FireMapState state; final GlobalKey scaffoldKey; GenericMapBottom( {@required this.onSave, @required this.onCancel, + @required this.onFalsePositive, @required this.state, @required this.scaffoldKey}); @@ -107,7 +110,7 @@ class GenericMapBottom extends StatelessWidget { value: value, child: new Text(menuText)); }).toList(), onChanged: (value) async { - // FIXME api call + onFalsePositive(state.fireNotification, value); await new Future.delayed( new Duration(milliseconds: 500)); scaffoldKey.currentState.showSnackBar(new SnackBar( diff --git a/lib/models/fireMapState.dart b/lib/models/fireMapState.dart index 586e39b..f804988 100644 --- a/lib/models/fireMapState.dart +++ b/lib/models/fireMapState.dart @@ -1,8 +1,14 @@ -import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/fireNotification.dart'; +import 'package:fires_flutter/models/yourLocation.dart'; import 'package:meta/meta.dart'; -enum FireMapStatus { view, subscriptionConfirm, unsubscribe, edit, viewFireNotification } +enum FireMapStatus { + view, + subscriptionConfirm, + unsubscribe, + edit, + viewFireNotification +} enum FireMapLayer { osmcGrey, esriSatellite, osmc, esri, esriTerrain } @immutable @@ -18,9 +24,9 @@ class FireMapState { const FireMapState.initial() : this.status = FireMapStatus.view, - this.layer = FireMapLayer.osmcGrey, + this.layer = FireMapLayer.osmcGrey, this.yourLocation = null, - this.fireNotification = null, + this.fireNotification = null, this.numFires = 0, this.fires = const [], this.falsePos = const [], @@ -28,11 +34,11 @@ class FireMapState { FireMapState( {this.status: FireMapStatus.view, - this.layer: FireMapLayer.osmcGrey, + this.layer: FireMapLayer.osmcGrey, this.yourLocation, this.numFires, - this.fires, - this.fireNotification, + this.fires, + this.fireNotification, this.falsePos, this.industries}); @@ -63,9 +69,9 @@ class FireMapState { other is FireMapState && runtimeType == other.runtimeType && status == other.status && - layer == other.layer && + layer == other.layer && numFires == other.numFires && - fireNotification == other.fireNotification && + fireNotification == other.fireNotification && fires == other.fires && falsePos == other.falsePos && industries == other.industries && diff --git a/lib/models/firesApi.dart b/lib/models/firesApi.dart index e614171..d61014d 100644 --- a/lib/models/firesApi.dart +++ b/lib/models/firesApi.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter/material.dart'; +import 'falsePositiveTypes.dart'; import 'package:bson_objectid/bson_objectid.dart'; import 'package:fires_flutter/models/yourLocation.dart'; @@ -18,8 +19,8 @@ class FiresApi { resty.globalClient = new ht.IOClient(); } - Future createUser( - AppState state, String mobileToken, String lang) async { + Future createUser(AppState state, String mobileToken, + String lang) async { assert(state.firesApiUrl != null); assert(state.firesApiKey != null); assert(mobileToken != null); @@ -50,18 +51,18 @@ class FiresApi { if (response.statusCode == 200) { // print(response.body); final dataSubscriptions = - json.decode(response.body)['data']['subscriptions']; + json.decode(response.body)['data']['subscriptions']; List subscribed = []; for (int i = 0; i < dataSubscriptions.length; i++) { var el = dataSubscriptions[i]; var lat = el['location']['lat']; var lon = el['location']['lon']; subscribed.add(new YourLocation( - id: ObjectId.fromHexString(el['_id']['_str']), - lat: lat, - lon: lon, - subscribed: true, - distance: el['distance'])); + id: ObjectId.fromHexString(el['_id']['_str']), + lat: lat, + lon: lon, + subscribed: true, + distance: el['distance'])); } return subscribed; @@ -116,12 +117,13 @@ class FiresApi { }); } - Future getFiresInLocation( - {AppState state, double lat, double lon, int distance}) async { + Future getFiresInLocation( + {AppState state, double lat, double lon, int distance}) async { assert(state.firesApiUrl != null); assert(state.firesApiKey != null); var url = '${state.firesApiUrl}fires-in-full/${state .firesApiKey}/${lat}/${lon}/${distance}'; + if (globals.isDevelopment) print(url); return await resty.get(url).go().then((response) { if (response.statusCode == 200) { var resultDecoded = json.decode(response.body); @@ -135,13 +137,13 @@ class FiresApi { var industriesCount = industries.length; var falsePosCount = falsePos.length; print( - 'real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount'); + '(Pos: $lat, $lon) real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount'); } - return new UpdateYourLocationMapStatsAction( - numFires: numFires, - fires: fires, - falsePos: falsePos, - industries: industries); + return new UpdateFireMapStatsAction( + numFires: numFires, + fires: fires, + falsePos: falsePos, + industries: industries); } else throw Exception('Wrong response trying to get fire data'); }); @@ -159,15 +161,16 @@ class FiresApi { // print(resultDecoded['data']['union']); List union = []; final multipolygon = - json.decode(resultDecoded['data']['union']['value'])['geometry'] - ['coordinates']; + json.decode(resultDecoded['data']['union']['value'])['geometry'] + ['coordinates']; for (List polygon in multipolygon) { for (List hole in polygon) { List points = []; for (List point in hole) { points.add(new LatLng(point[1].toDouble(), point[0].toDouble())); } - union.add(new Polyline(points: points, color: color, strokeWidth: 3.0)); + union.add( + new Polyline(points: points, color: color, strokeWidth: 3.0)); } } return union; @@ -175,4 +178,32 @@ class FiresApi { throw Exception('Wrong response trying to get fire data'); }); } + + Future markFalsePositive(AppState state, String mobileToken, + String sealed, FalsePositiveType type) async { + assert(state.firesApiUrl != null); + assert(state.firesApiKey != null); + assert(mobileToken != null); + assert(sealed != null); + assert(type != null); + + final params = { + "token": state.firesApiKey, + "mobileToken": mobileToken, + "sealed": sealed, + "type": type.toString() + }; + final String url = '${state.firesApiUrl}mobile/falsepositive'; + return await resty.post(url).json(params).go().then((response) { + if (response.statusCode == 200) { + // print(response.body); + if (globals.isDevelopment) print( + json.decode(response.body)['data']['upsert']); + return true; + } else { + debugPrint(json.decode(response.body)); + return false; + } + }); + } } diff --git a/lib/redux/fetchDataMiddleware.dart b/lib/redux/fetchDataMiddleware.dart index 1b3fd30..d0c8d0e 100644 --- a/lib/redux/fetchDataMiddleware.dart +++ b/lib/redux/fetchDataMiddleware.dart @@ -97,13 +97,7 @@ void fetchDataMiddleware(Store store, action, NextDispatcher next) { } if (action is ShowFireNotificationMapAction) { - api - .getFiresInLocation( - state: store.state, - lat: action.notif.lat, - lon: action.notif.lon, - distance: 1) // FalsePositive/server/publications.js - .then((result) => store.dispatch(result)); + getFiresStatsInFire(store, action.notif); } if (action is UpdateYourLocationAction) { @@ -221,10 +215,30 @@ void fetchDataMiddleware(Store store, action, NextDispatcher next) { }); } + if (action is MarkFireAsFalsePositiveAction) { + api.markFalsePositive(store.state, store.state.user.token, action.notif.sealed, action.type).then((result) { + if (result) { + // Not necessary + // store.dispatch(new UpdatedFireNotificationAction(action.notif)); + getFiresStatsInFire(store, action.notif); + } + }); + } + // Make sure our actions continue on to the reducer. next(action); } +void getFiresStatsInFire(Store store, FireNotification notif) { + api + .getFiresInLocation( + state: store.state, + lat: notif.lat, + lon: notif.lon, + distance: 1) // FalsePositive/server/publications.js + .then((result) => store.dispatch(result)); +} + void unsubsViaApi(Store store, ObjectId id, onUnsubs) { api.unsubscribe(store.state, id.toHexString()).then((res) { onUnsubs(); diff --git a/lib/redux/fireMapActions.dart b/lib/redux/fireMapActions.dart index 6b7333d..a8c5524 100644 --- a/lib/redux/fireMapActions.dart +++ b/lib/redux/fireMapActions.dart @@ -12,13 +12,13 @@ class UpdateYourLocationMapAction extends FiresMapActions { ); } -class UpdateYourLocationMapStatsAction extends FiresMapActions { +class UpdateFireMapStatsAction extends FiresMapActions { int numFires; List fires = []; List falsePos = []; List industries = []; - UpdateYourLocationMapStatsAction( + UpdateFireMapStatsAction( {@required this.numFires, @required this.fires, @required this.falsePos, diff --git a/lib/redux/fireMapReducer.dart b/lib/redux/fireMapReducer.dart index d6790e6..1794b86 100644 --- a/lib/redux/fireMapReducer.dart +++ b/lib/redux/fireMapReducer.dart @@ -8,7 +8,7 @@ final fireMapReducer = combineReducers([ _showYourLocationMap), new TypedReducer( _showFireNotificationMap), - new TypedReducer( + new TypedReducer( _updateYourLocationMapStats), new TypedReducer(_subscribeYourLocationMap), new TypedReducer( @@ -27,7 +27,7 @@ final fireMapReducer = combineReducers([ ]); FireMapState _updateYourLocationMapStats( - FireMapState state, UpdateYourLocationMapStatsAction action) { + FireMapState state, UpdateFireMapStatsAction action) { return state.copyWith( numFires: action.numFires, fires: action.fires, diff --git a/lib/redux/fireNotificationActions.dart b/lib/redux/fireNotificationActions.dart index d42df0b..32dc82f 100644 --- a/lib/redux/fireNotificationActions.dart +++ b/lib/redux/fireNotificationActions.dart @@ -1,4 +1,5 @@ import 'package:fires_flutter/models/fireNotification.dart'; +import '../models/falsePositiveTypes.dart'; abstract class FireNotificationActions {} @@ -45,3 +46,16 @@ class ReadedFireNotificationAction extends FireNotificationActions { ReadedFireNotificationAction(this.notif); } + +class MarkFireAsFalsePositiveAction extends FireNotificationActions { + final FireNotification notif; + final FalsePositiveType type; + + MarkFireAsFalsePositiveAction(this.notif, this.type); +} + +class UpdatedFireNotificationAction extends FireNotificationActions { + final FireNotification notif; + + UpdatedFireNotificationAction(this.notif); +} \ No newline at end of file diff --git a/lib/redux/fireNotificationReducer.dart b/lib/redux/fireNotificationReducer.dart index e907e33..a3cd083 100644 --- a/lib/redux/fireNotificationReducer.dart +++ b/lib/redux/fireNotificationReducer.dart @@ -1,17 +1,19 @@ +import 'package:fires_flutter/models/fireNotification.dart'; import 'package:redux/redux.dart'; import 'actions.dart'; -import 'package:fires_flutter/models/fireNotification.dart'; final fireNotificationReducer = combineReducers>([ new TypedReducer, AddedFireNotificationAction>( _addedFireNotification), new TypedReducer, DeletedFireNotificationAction>( - _deletedFireNotification), + _deletedFireNotification), + new TypedReducer, UpdatedFireNotificationAction>( + _updatedFireNotification), new TypedReducer, DeletedAllFireNotificationAction>( - _deletedAllFireNotifications), + _deletedAllFireNotifications), new TypedReducer, ReadedFireNotificationAction>( - _readedFireNotification) + _readedFireNotification) ]); List _addedFireNotification( @@ -20,10 +22,18 @@ List _addedFireNotification( } List _deletedFireNotification( - List notifications, DeletedFireNotificationAction action) { + List notifications, + DeletedFireNotificationAction action) { return new List.from(notifications)..remove(action.notif); } +List _updatedFireNotification( + List notifications, UpdatedFireNotificationAction action) { + return notifications + .map((notif) => notif.id == action.notif.id ? action.notif : notif) + .toList(); +} + List _readedFireNotification( List notifications, ReadedFireNotificationAction action) { return notifications @@ -33,6 +43,7 @@ List _readedFireNotification( } List _deletedAllFireNotifications( - List notifications, DeletedAllFireNotificationAction action) { + List notifications, + DeletedAllFireNotificationAction action) { return new List(); }