False positives in fire notifications
This commit is contained in:
parent
845c38bf04
commit
76085dcb2f
7 changed files with 107 additions and 27 deletions
|
|
@ -59,13 +59,19 @@ class S implements WidgetsLocalizations {
|
||||||
String get firesNearPlace => "Fires near other place";
|
String get firesNearPlace => "Fires near other place";
|
||||||
String get getAlertsOfFiresinThatArea => "Get alerts of fires in that area";
|
String get getAlertsOfFiresinThatArea => "Get alerts of fires in that area";
|
||||||
String get isYourUbicationEnabled => "I cannot get your current location. It's your ubication enabled?";
|
String get isYourUbicationEnabled => "I cannot get your current location. It's your ubication enabled?";
|
||||||
|
String get itSeemsAControlledBurning => "It's a controlled burning";
|
||||||
|
String get itSeemsAFalseAlarm => "It seems a false alarm";
|
||||||
|
String get itSeemsAIndustry => "It's an industry";
|
||||||
|
String get itSeemsNotAtForesFire => "It seems that this is not a forest fire.";
|
||||||
String get noConnectivity => "This application needs an Internet connection to work";
|
String get noConnectivity => "This application needs an Internet connection to work";
|
||||||
String get noFiresAround => "There is no fires";
|
String get noFiresAround => "There is no fires";
|
||||||
|
String get notAWildfire => "Isn't that a forest fire?";
|
||||||
String get notPermsUbication => "We don't have permission to get your location";
|
String get notPermsUbication => "We don't have permission to get your location";
|
||||||
String get notifyAFire => "Notify a fire";
|
String get notifyAFire => "Notify a fire";
|
||||||
String get privacyPolicy => "Privacy Policy";
|
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, suggesting other public data sources of fires to use, 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 supportThisInitiative => "Support this initiative";
|
||||||
|
String get thanksForParticipating => "Thanks for participating";
|
||||||
String get toDeleteThisNotification => "Slide horizontally to delete this notification";
|
String get toDeleteThisNotification => "Slide horizontally to delete this notification";
|
||||||
String get toDeleteThisPlace => "Slide horizontally to delete this place";
|
String get toDeleteThisPlace => "Slide horizontally to delete this place";
|
||||||
String get toFiresNotifications => "Subscribe to fires notifications";
|
String get toFiresNotifications => "Subscribe to fires notifications";
|
||||||
|
|
@ -142,6 +148,10 @@ class es extends S {
|
||||||
@override
|
@override
|
||||||
String get fireNotificationTitle => "Notificación de fuego";
|
String get fireNotificationTitle => "Notificación de fuego";
|
||||||
@override
|
@override
|
||||||
|
String get itSeemsAControlledBurning => "Es una quema controlada";
|
||||||
|
@override
|
||||||
|
String get itSeemsAIndustry => "Es una industria";
|
||||||
|
@override
|
||||||
String get youDeletedThisPlace => "Has borrado este lugar";
|
String get youDeletedThisPlace => "Has borrado este lugar";
|
||||||
@override
|
@override
|
||||||
String get callEmergencyServicesTitle => "Llama al 112";
|
String get callEmergencyServicesTitle => "Llama al 112";
|
||||||
|
|
@ -154,6 +164,8 @@ class es extends S {
|
||||||
@override
|
@override
|
||||||
String get aYear => "un año";
|
String get aYear => "un año";
|
||||||
@override
|
@override
|
||||||
|
String get itSeemsNotAtForesFire => "Parece que este fuego no es un fuego forestal.";
|
||||||
|
@override
|
||||||
String get UNDO => "DESHACER";
|
String get UNDO => "DESHACER";
|
||||||
@override
|
@override
|
||||||
String get tweetAboutAFireTitle => "Twittea";
|
String get tweetAboutAFireTitle => "Twittea";
|
||||||
|
|
@ -170,6 +182,8 @@ class es extends S {
|
||||||
@override
|
@override
|
||||||
String get isYourUbicationEnabled => "No podemos saber tu ubicación actual. ¿Están los servicios de ubicación en tu móvil activados?";
|
String get isYourUbicationEnabled => "No podemos saber tu ubicación actual. ¿Están los servicios de ubicación en tu móvil activados?";
|
||||||
@override
|
@override
|
||||||
|
String get thanksForParticipating => "Gracias por Participar";
|
||||||
|
@override
|
||||||
String get fireNotificationsTitleShort => "Notificaciones";
|
String get fireNotificationsTitleShort => "Notificaciones";
|
||||||
@override
|
@override
|
||||||
String get DELETE => "BORRAR";
|
String get DELETE => "BORRAR";
|
||||||
|
|
@ -180,6 +194,8 @@ class es extends S {
|
||||||
@override
|
@override
|
||||||
String get SHOW => "MOSTRAR";
|
String get SHOW => "MOSTRAR";
|
||||||
@override
|
@override
|
||||||
|
String get itSeemsAFalseAlarm => "Parece una falsa alarma";
|
||||||
|
@override
|
||||||
String get addedThisLocation => "Ya has añadido este lugar antes";
|
String get addedThisLocation => "Ya has añadido este lugar antes";
|
||||||
@override
|
@override
|
||||||
String get typeTheNameOfAPlace => "Escribe el nombre de un lugar, zona, etc";
|
String get typeTheNameOfAPlace => "Escribe el nombre de un lugar, zona, etc";
|
||||||
|
|
@ -210,6 +226,8 @@ class es extends S {
|
||||||
@override
|
@override
|
||||||
String get CANCEL => "CANCELAR";
|
String get CANCEL => "CANCELAR";
|
||||||
@override
|
@override
|
||||||
|
String get notAWildfire => "¿No es un fuego forestal?";
|
||||||
|
@override
|
||||||
String inMonths(String value) => "$value meses";
|
String inMonths(String value) => "$value meses";
|
||||||
@override
|
@override
|
||||||
String inYears(String value) => "$value años";
|
String inYears(String value) => "$value años";
|
||||||
|
|
|
||||||
|
|
@ -292,7 +292,9 @@ class _genericMapState extends State<genericMap> {
|
||||||
bottomNavigationBar: new GenericMapBottom(
|
bottomNavigationBar: new GenericMapBottom(
|
||||||
onSave: () => view.onEditConfirm(_location),
|
onSave: () => view.onEditConfirm(_location),
|
||||||
onCancel: () => view.onEditCancel(_initialLocation),
|
onCancel: () => view.onEditCancel(_initialLocation),
|
||||||
state: view.mapState),
|
state: view.mapState,
|
||||||
|
scaffoldKey: _scaffoldKey,
|
||||||
|
),
|
||||||
body: LayoutBuilder(
|
body: LayoutBuilder(
|
||||||
builder: (context, constraints) =>
|
builder: (context, constraints) =>
|
||||||
Stack(fit: StackFit.expand, children: <Widget>[
|
Stack(fit: StackFit.expand, children: <Widget>[
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,20 @@ import 'customMoment.dart';
|
||||||
import 'generated/i18n.dart';
|
import 'generated/i18n.dart';
|
||||||
import 'models/appState.dart';
|
import 'models/appState.dart';
|
||||||
import 'models/fireMapState.dart';
|
import 'models/fireMapState.dart';
|
||||||
|
import 'package:comunes_flutter/comunes_flutter.dart';
|
||||||
|
|
||||||
typedef void OnSave();
|
typedef void OnSave();
|
||||||
typedef void OnCancel();
|
typedef void OnCancel();
|
||||||
|
typedef void OnFalsePositive();
|
||||||
|
|
||||||
class GenericMapBottom extends StatelessWidget {
|
class GenericMapBottom extends StatelessWidget {
|
||||||
final OnSave onSave;
|
final OnSave onSave;
|
||||||
final OnCancel onCancel;
|
final OnCancel onCancel;
|
||||||
final FireMapState state;
|
final FireMapState state;
|
||||||
|
final GlobalKey<ScaffoldState> scaffoldKey;
|
||||||
|
|
||||||
GenericMapBottom(
|
GenericMapBottom(
|
||||||
{@required this.onSave, @required this.onCancel, @required this.state});
|
{@required this.onSave, @required this.onCancel, @required this.state, @required this.scaffoldKey});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
@ -28,11 +31,11 @@ class GenericMapBottom extends StatelessWidget {
|
||||||
showNotch: false,
|
showNotch: false,
|
||||||
color: fires100,
|
color: fires100,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
actions: buildActionList(loc, context, kmAround));
|
actions: buildActionList(loc, context, kmAround, scaffoldKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> buildActionList(
|
List<Widget> buildActionList(
|
||||||
YourLocation loc, BuildContext context, int kmAround) {
|
YourLocation loc, BuildContext context, int kmAround, GlobalKey<ScaffoldState> scaffoldState) {
|
||||||
List<Widget> actionList = new List<Widget>();
|
List<Widget> actionList = new List<Widget>();
|
||||||
switch (state.status) {
|
switch (state.status) {
|
||||||
case FireMapStatus.edit:
|
case FireMapStatus.edit:
|
||||||
|
|
@ -54,8 +57,9 @@ class GenericMapBottom extends StatelessWidget {
|
||||||
child: new Column(
|
child: new Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: listWithoutNulls(<Widget>[
|
||||||
new Text(state.fireNotification.description),
|
new Text(state.fireNotification.description),
|
||||||
|
// TODO fire type (neighbout, NASA, etc)
|
||||||
new SizedBox(height: 5.0),
|
new SizedBox(height: 5.0),
|
||||||
new Row(
|
new Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|
@ -63,10 +67,29 @@ class GenericMapBottom extends StatelessWidget {
|
||||||
new SizedBox(width: 5.0),
|
new SizedBox(width: 5.0),
|
||||||
new Text(Moment
|
new Text(Moment
|
||||||
.now()
|
.now()
|
||||||
.from(context, state.fireNotification.when))
|
.from(context, state.fireNotification.when)),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
]))));
|
state.industries.length >= 0 || state.falsePos.length > 0 ? new Text(S.of(context).itSeemsNotAtForesFire, style: new TextStyle(color: fires600)): null,
|
||||||
|
new DropdownButton<String>(
|
||||||
|
style: new TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 18.0,
|
||||||
|
),
|
||||||
|
hint: new Text(S.of(context).notAWildfire),
|
||||||
|
items: <String>[S.of(context).itSeemsAIndustry, S.of(context).itSeemsAControlledBurning, S.of(context).itSeemsAFalseAlarm].map((String value) {
|
||||||
|
return new DropdownMenuItem <String>(
|
||||||
|
value: value,
|
||||||
|
child: new Text(value)
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
// FIXME api call
|
||||||
|
scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||||
|
content: new Text(S.of(context).thanksForParticipating),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
])))));
|
||||||
break;
|
break;
|
||||||
case FireMapStatus.unsubscribe:
|
case FireMapStatus.unsubscribe:
|
||||||
case FireMapStatus.view:
|
case FireMapStatus.view:
|
||||||
|
|
|
||||||
|
|
@ -113,16 +113,15 @@ class FiresApi {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UpdateYourLocationMapStatsAction> getYourLocationFireStats(
|
Future<UpdateYourLocationMapStatsAction> getFiresInLocation(
|
||||||
AppState state, YourLocation location) async {
|
{AppState state, double lat, double lon, int distance}) async {
|
||||||
assert(state.firesApiUrl != null);
|
assert(state.firesApiUrl != null);
|
||||||
assert(state.firesApiKey != null);
|
assert(state.firesApiKey != null);
|
||||||
var url = '${state.firesApiUrl}fires-in-full/${state
|
var url = '${state.firesApiUrl}fires-in-full/${state
|
||||||
.firesApiKey}/${location.lat}/${location.lon}/${location.distance}';
|
.firesApiKey}/${lat}/${lon}/${distance}';
|
||||||
return await resty.get(url).go().then((response) {
|
return await resty.get(url).go().then((response) {
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
var resultDecoded = json.decode(response.body);
|
var resultDecoded = json.decode(response.body);
|
||||||
// print(resultDecoded);
|
|
||||||
int numFires = resultDecoded['real'];
|
int numFires = resultDecoded['real'];
|
||||||
List fires = resultDecoded['fires'];
|
List fires = resultDecoded['fires'];
|
||||||
List falsePos = resultDecoded['falsePos'];
|
List falsePos = resultDecoded['falsePos'];
|
||||||
|
|
@ -140,7 +139,7 @@ class FiresApi {
|
||||||
fires: fires,
|
fires: fires,
|
||||||
falsePos: falsePos,
|
falsePos: falsePos,
|
||||||
industries: industries);
|
industries: industries);
|
||||||
} else throw Exception('Wrong response trying to get stats');
|
} else throw Exception('Wrong response trying to get fire data');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bson_objectid/bson_objectid.dart';
|
import 'package:bson_objectid/bson_objectid.dart';
|
||||||
import 'package:fires_flutter/models/yourLocation.dart';
|
|
||||||
import 'package:fires_flutter/models/fireNotification.dart';
|
import 'package:fires_flutter/models/fireNotification.dart';
|
||||||
|
import 'package:fires_flutter/models/yourLocation.dart';
|
||||||
import 'package:flutter_simple_dependency_injection/injector.dart';
|
import 'package:flutter_simple_dependency_injection/injector.dart';
|
||||||
import 'package:just_debounce_it/just_debounce_it.dart';
|
import 'package:just_debounce_it/just_debounce_it.dart';
|
||||||
import 'package:redux/redux.dart';
|
import 'package:redux/redux.dart';
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import '../models/appState.dart';
|
import '../models/appState.dart';
|
||||||
import '../models/fireNotificationsPersist.dart';
|
import '../models/fireNotificationsPersist.dart';
|
||||||
|
|
@ -87,15 +88,34 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
|
||||||
|
|
||||||
if (action is ShowYourLocationMapAction) {
|
if (action is ShowYourLocationMapAction) {
|
||||||
api
|
api
|
||||||
.getYourLocationFireStats(store.state, action.loc)
|
.getFiresInLocation(
|
||||||
|
state: store.state,
|
||||||
|
lat: action.loc.lat,
|
||||||
|
lon: action.loc.lon,
|
||||||
|
distance: action.loc.distance)
|
||||||
.then((result) => store.dispatch(result));
|
.then((result) => store.dispatch(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
if (action is UpdateYourLocationAction) {
|
if (action is UpdateYourLocationAction) {
|
||||||
if (action.loc.subscribed)
|
if (action.loc.subscribed)
|
||||||
Debounce.seconds(
|
Debounce.seconds(
|
||||||
2,
|
2,
|
||||||
() => api
|
() => api
|
||||||
.getYourLocationFireStats(store.state, action.loc)
|
.getFiresInLocation(
|
||||||
|
state: store.state,
|
||||||
|
lat: action.loc.lat,
|
||||||
|
lon: action.loc.lon,
|
||||||
|
distance: action.loc.distance)
|
||||||
.then((result) => store.dispatch(result)));
|
.then((result) => store.dispatch(result)));
|
||||||
else {
|
else {
|
||||||
// FIXME do something?
|
// FIXME do something?
|
||||||
|
|
@ -147,18 +167,22 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
|
||||||
localLocations.forEach((location) => location.subscribed = false);
|
localLocations.forEach((location) => location.subscribed = false);
|
||||||
// print('Local persisted: ${localLocations.length}');
|
// print('Local persisted: ${localLocations.length}');
|
||||||
subscribedLocations.forEach((subsLoc) {
|
subscribedLocations.forEach((subsLoc) {
|
||||||
localLocations
|
localLocations.firstWhere(
|
||||||
.firstWhere(
|
|
||||||
(localLocation) => localLocation.id == subsLoc.id, orElse: () {
|
(localLocation) => localLocation.id == subsLoc.id, orElse: () {
|
||||||
localLocations.add(subsLoc);
|
localLocations.add(subsLoc);
|
||||||
})
|
}).subscribed = true;
|
||||||
.subscribed = true;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
localLocations.forEach((yl) {
|
localLocations.forEach((yl) {
|
||||||
api.getYourLocationFireStats(store.state, yl).then((value) {
|
api
|
||||||
|
.getFiresInLocation(
|
||||||
|
state: store.state,
|
||||||
|
lat: yl.lat,
|
||||||
|
lon: yl.lon,
|
||||||
|
distance: yl.distance)
|
||||||
|
.then((value) {
|
||||||
yl.currentNumFires = value.numFires;
|
yl.currentNumFires = value.numFires;
|
||||||
store.dispatch(new UpdateYourLocationAction(yl));
|
store.dispatch(new UpdateYourLocationAction(yl));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -180,7 +204,9 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
|
||||||
loadFireNotifications().then((fireNotifications) {
|
loadFireNotifications().then((fireNotifications) {
|
||||||
int unread = 0;
|
int unread = 0;
|
||||||
for (FireNotification notif in fireNotifications) {
|
for (FireNotification notif in fireNotifications) {
|
||||||
if (!notif.read) { unread++; }
|
if (!notif.read) {
|
||||||
|
unread++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
new FetchFireNotificationsSucceededAction(fireNotifications, unread));
|
new FetchFireNotificationsSucceededAction(fireNotifications, unread));
|
||||||
|
|
|
||||||
|
|
@ -66,5 +66,11 @@
|
||||||
"CLOSE": "CLOSE",
|
"CLOSE": "CLOSE",
|
||||||
"noConnectivity": "This application needs an Internet connection to work",
|
"noConnectivity": "This application needs an Internet connection to work",
|
||||||
"NASAAck": "We acknowledge the use of data and imagery from LANCE FIRMS operated by the NASA/GSFC/Earth Science Data and Information System (ESDIS) with funding provided by NASA/HQ.",
|
"NASAAck": "We acknowledge the use of data and imagery from LANCE FIRMS operated by the NASA/GSFC/Earth Science Data and Information System (ESDIS) with funding provided by NASA/HQ.",
|
||||||
"appMoto": "Strengthening neighborhoods against wildfires"
|
"appMoto": "Strengthening neighborhoods against wildfires",
|
||||||
|
"itSeemsNotAtForesFire": "It seems that this is not a forest fire.",
|
||||||
|
"notAWildfire": "Isn't that a forest fire?",
|
||||||
|
"itSeemsAIndustry": "It's an industry",
|
||||||
|
"itSeemsAControlledBurning": "It's a controlled burning",
|
||||||
|
"itSeemsAFalseAlarm": "It seems a false alarm",
|
||||||
|
"thanksForParticipating": "Thanks for participating"
|
||||||
}
|
}
|
||||||
|
|
@ -66,5 +66,11 @@
|
||||||
"CLOSE": "CERRAR",
|
"CLOSE": "CERRAR",
|
||||||
"noConnectivity": "Esta aplicación necesita una conexión a Internet para funcionar",
|
"noConnectivity": "Esta aplicación necesita una conexión a Internet para funcionar",
|
||||||
"NASAAck": "Reconocemos el uso de datos e imágenes de LANCE FIRMS operadas por NASA/GSFC/Earth Science Data and Information System (ESDIS) con fondos proporcionados por NASA/HQ.",
|
"NASAAck": "Reconocemos el uso de datos e imágenes de LANCE FIRMS operadas por NASA/GSFC/Earth Science Data and Information System (ESDIS) con fondos proporcionados por NASA/HQ.",
|
||||||
"appMoto": "Fortaleciendo vecindarios contra incendios forestales"
|
"appMoto": "Fortaleciendo vecindarios contra incendios forestales",
|
||||||
|
"itSeemsNotAtForesFire": "Parece que este fuego no es un fuego forestal.",
|
||||||
|
"notAWildfire": "¿No es un fuego forestal?",
|
||||||
|
"itSeemsAIndustry": "Es una industria",
|
||||||
|
"itSeemsAControlledBurning": "Es una quema controlada",
|
||||||
|
"itSeemsAFalseAlarm": "Parece una falsa alarma",
|
||||||
|
"thanksForParticipating": "Gracias por Participar"
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue