False positives in fire notifications

This commit is contained in:
Vicente J. Ruiz Jurado 2018-08-04 11:19:54 +02:00
parent 845c38bf04
commit 76085dcb2f
7 changed files with 107 additions and 27 deletions

View file

@ -59,13 +59,19 @@ class S implements WidgetsLocalizations {
String get firesNearPlace => "Fires near other place";
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 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 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 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, suggesting other public data sources of fires to use, etc.";
String get supportThisInitiative => "Support this initiative";
String get thanksForParticipating => "Thanks for participating";
String get toDeleteThisNotification => "Slide horizontally to delete this notification";
String get toDeleteThisPlace => "Slide horizontally to delete this place";
String get toFiresNotifications => "Subscribe to fires notifications";
@ -142,6 +148,10 @@ class es extends S {
@override
String get fireNotificationTitle => "Notificación de fuego";
@override
String get itSeemsAControlledBurning => "Es una quema controlada";
@override
String get itSeemsAIndustry => "Es una industria";
@override
String get youDeletedThisPlace => "Has borrado este lugar";
@override
String get callEmergencyServicesTitle => "Llama al 112";
@ -154,6 +164,8 @@ class es extends S {
@override
String get aYear => "un año";
@override
String get itSeemsNotAtForesFire => "Parece que este fuego no es un fuego forestal.";
@override
String get UNDO => "DESHACER";
@override
String get tweetAboutAFireTitle => "Twittea";
@ -170,6 +182,8 @@ class es extends S {
@override
String get isYourUbicationEnabled => "No podemos saber tu ubicación actual. ¿Están los servicios de ubicación en tu móvil activados?";
@override
String get thanksForParticipating => "Gracias por Participar";
@override
String get fireNotificationsTitleShort => "Notificaciones";
@override
String get DELETE => "BORRAR";
@ -180,6 +194,8 @@ class es extends S {
@override
String get SHOW => "MOSTRAR";
@override
String get itSeemsAFalseAlarm => "Parece una falsa alarma";
@override
String get addedThisLocation => "Ya has añadido este lugar antes";
@override
String get typeTheNameOfAPlace => "Escribe el nombre de un lugar, zona, etc";
@ -210,6 +226,8 @@ class es extends S {
@override
String get CANCEL => "CANCELAR";
@override
String get notAWildfire => "¿No es un fuego forestal?";
@override
String inMonths(String value) => "$value meses";
@override
String inYears(String value) => "$value años";

View file

@ -292,7 +292,9 @@ class _genericMapState extends State<genericMap> {
bottomNavigationBar: new GenericMapBottom(
onSave: () => view.onEditConfirm(_location),
onCancel: () => view.onEditCancel(_initialLocation),
state: view.mapState),
state: view.mapState,
scaffoldKey: _scaffoldKey,
),
body: LayoutBuilder(
builder: (context, constraints) =>
Stack(fit: StackFit.expand, children: <Widget>[

View file

@ -7,17 +7,20 @@ import 'customMoment.dart';
import 'generated/i18n.dart';
import 'models/appState.dart';
import 'models/fireMapState.dart';
import 'package:comunes_flutter/comunes_flutter.dart';
typedef void OnSave();
typedef void OnCancel();
typedef void OnFalsePositive();
class GenericMapBottom extends StatelessWidget {
final OnSave onSave;
final OnCancel onCancel;
final FireMapState state;
final GlobalKey<ScaffoldState> scaffoldKey;
GenericMapBottom(
{@required this.onSave, @required this.onCancel, @required this.state});
{@required this.onSave, @required this.onCancel, @required this.state, @required this.scaffoldKey});
@override
Widget build(BuildContext context) {
@ -28,11 +31,11 @@ class GenericMapBottom extends StatelessWidget {
showNotch: false,
color: fires100,
mainAxisAlignment: MainAxisAlignment.center,
actions: buildActionList(loc, context, kmAround));
actions: buildActionList(loc, context, kmAround, scaffoldKey));
}
List<Widget> buildActionList(
YourLocation loc, BuildContext context, int kmAround) {
YourLocation loc, BuildContext context, int kmAround, GlobalKey<ScaffoldState> scaffoldState) {
List<Widget> actionList = new List<Widget>();
switch (state.status) {
case FireMapStatus.edit:
@ -54,8 +57,9 @@ class GenericMapBottom extends StatelessWidget {
child: new Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
children: listWithoutNulls(<Widget>[
new Text(state.fireNotification.description),
// TODO fire type (neighbout, NASA, etc)
new SizedBox(height: 5.0),
new Row(
children: <Widget>[
@ -63,10 +67,29 @@ class GenericMapBottom extends StatelessWidget {
new SizedBox(width: 5.0),
new Text(Moment
.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;
case FireMapStatus.unsubscribe:
case FireMapStatus.view:

View file

@ -113,16 +113,15 @@ class FiresApi {
});
}
Future<UpdateYourLocationMapStatsAction> getYourLocationFireStats(
AppState state, YourLocation location) async {
Future<UpdateYourLocationMapStatsAction> 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}/${location.lat}/${location.lon}/${location.distance}';
.firesApiKey}/${lat}/${lon}/${distance}';
return await resty.get(url).go().then((response) {
if (response.statusCode == 200) {
var resultDecoded = json.decode(response.body);
// print(resultDecoded);
int numFires = resultDecoded['real'];
List fires = resultDecoded['fires'];
List falsePos = resultDecoded['falsePos'];
@ -140,7 +139,7 @@ class FiresApi {
fires: fires,
falsePos: falsePos,
industries: industries);
} else throw Exception('Wrong response trying to get stats');
} else throw Exception('Wrong response trying to get fire data');
});
}
}

View file

@ -1,10 +1,11 @@
import 'dart:async';
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/yourLocation.dart';
import 'package:flutter_simple_dependency_injection/injector.dart';
import 'package:just_debounce_it/just_debounce_it.dart';
import 'package:redux/redux.dart';
import 'dart:async';
import '../models/appState.dart';
import '../models/fireNotificationsPersist.dart';
@ -87,15 +88,34 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
if (action is ShowYourLocationMapAction) {
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));
}
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.loc.subscribed)
Debounce.seconds(
2,
() => 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)));
else {
// FIXME do something?
@ -147,18 +167,22 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
localLocations.forEach((location) => location.subscribed = false);
// print('Local persisted: ${localLocations.length}');
subscribedLocations.forEach((subsLoc) {
localLocations
.firstWhere(
localLocations.firstWhere(
(localLocation) => localLocation.id == subsLoc.id, orElse: () {
localLocations.add(subsLoc);
})
.subscribed = true;
}).subscribed = true;
});
}
localLocations.forEach((yl) {
api.getYourLocationFireStats(store.state, yl).then((value) {
localLocations.forEach((yl) {
api
.getFiresInLocation(
state: store.state,
lat: yl.lat,
lon: yl.lon,
distance: yl.distance)
.then((value) {
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) {
int unread = 0;
for (FireNotification notif in fireNotifications) {
if (!notif.read) { unread++; }
if (!notif.read) {
unread++;
}
}
store.dispatch(
new FetchFireNotificationsSucceededAction(fireNotifications, unread));

View file

@ -66,5 +66,11 @@
"CLOSE": "CLOSE",
"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.",
"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"
}

View file

@ -66,5 +66,11 @@
"CLOSE": "CERRAR",
"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.",
"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"
}