Fire notifications
This commit is contained in:
parent
c680482b08
commit
5ecc3c3401
22 changed files with 524 additions and 31 deletions
|
|
@ -80,15 +80,6 @@ class _FireAlertState extends State<FireAlert> {
|
|||
buildTweetButton()
|
||||
])),
|
||||
]);
|
||||
final stepInc = () {
|
||||
setState(() {
|
||||
if (_currentStep < fireSteps.length - 1) {
|
||||
_currentStep += 1;
|
||||
} else {
|
||||
_currentStep = 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
appBar: new AppBar(title: new Text(S.of(context).notifyAFire)),
|
||||
|
|
|
|||
194
lib/fireNotificationList.dart
Normal file
194
lib/fireNotificationList.dart
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:comunes_flutter/comunes_flutter.dart';
|
||||
import 'package:fires_flutter/models/fireNotification.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_redux/flutter_redux.dart';
|
||||
import 'package:redux/src/store.dart';
|
||||
|
||||
import 'customMoment.dart';
|
||||
import 'generated/i18n.dart';
|
||||
import 'mainDrawer.dart';
|
||||
import 'models/appState.dart';
|
||||
import 'redux/actions.dart';
|
||||
// import 'fireNotificationMap.dart';
|
||||
|
||||
@immutable
|
||||
class _ViewModel {
|
||||
final List<FireNotification> fireNotifications;
|
||||
final TapFireNotificationFunction onTap;
|
||||
|
||||
//final OnReceivedFireNotificationFunction onReceived;
|
||||
final DeleteFireNotificationFunction onDelete;
|
||||
final DeleteAllFireNotificationFunction onDeleteAll;
|
||||
|
||||
_ViewModel(
|
||||
{@required this.onTap,
|
||||
// @required this.onReceived,
|
||||
@required this.onDelete,
|
||||
@required this.onDeleteAll,
|
||||
@required this.fireNotifications});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
other is _ViewModel &&
|
||||
runtimeType == other.runtimeType &&
|
||||
fireNotifications == other.fireNotifications;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => fireNotifications.hashCode;
|
||||
}
|
||||
|
||||
class FireNotificationList extends StatefulWidget {
|
||||
static const String routeName = '/fireNotifications';
|
||||
|
||||
@override
|
||||
_FireNotificationListState createState() => _FireNotificationListState();
|
||||
}
|
||||
|
||||
class _FireNotificationListState extends State<FireNotificationList> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
Widget _buildRow(
|
||||
BuildContext context, FireNotification notif, onDeleted, onTap) {
|
||||
return new ListTile(
|
||||
dense: true,
|
||||
leading: const Icon(Icons.whatshot),
|
||||
title: new Text(notif.description),
|
||||
subtitle: new Text(Moment.now().from(context, notif.when)),
|
||||
onLongPress: () {
|
||||
showSnackMsg(S.of(context).toDeleteThisPlace);
|
||||
},
|
||||
onTap: () {
|
||||
onTap(notif);
|
||||
});
|
||||
}
|
||||
|
||||
void showSnackMsg(String msg) {
|
||||
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||
content: new Text(msg),
|
||||
));
|
||||
}
|
||||
|
||||
Widget _buildSavedFireNotifications(BuildContext context,
|
||||
List<FireNotification> notifList, onDeleted, onTap) {
|
||||
return new RefreshIndicator(
|
||||
child: new ListView.builder(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
itemCount: notifList.length,
|
||||
itemBuilder: (BuildContext _context, int i) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return new Dismissible(
|
||||
key: new ObjectKey(notifList.elementAt(i)),
|
||||
direction: DismissDirection.horizontal,
|
||||
onDismissed: (DismissDirection direction) {
|
||||
onDeleted(notifList.elementAt(i));
|
||||
},
|
||||
background: new Container(
|
||||
color: theme.primaryColor,
|
||||
child: const ListTile(
|
||||
leading: const Icon(Icons.delete,
|
||||
color: Colors.white, size: 36.0))),
|
||||
secondaryBackground: new Container(
|
||||
color: theme.primaryColor,
|
||||
child: const ListTile(
|
||||
trailing: const Icon(Icons.delete,
|
||||
color: Colors.white, size: 36.0))),
|
||||
child: new Container(
|
||||
decoration: new BoxDecoration(
|
||||
color: theme.canvasColor,
|
||||
border: new Border(
|
||||
bottom:
|
||||
new BorderSide(color: theme.dividerColor))),
|
||||
child: _buildRow(
|
||||
context, notifList.elementAt(i), onDeleted, onTap)));
|
||||
}),
|
||||
onRefresh: _handleRefresh);
|
||||
}
|
||||
|
||||
Future<Null> _handleRefresh() async {
|
||||
await new Future.delayed(new Duration(seconds: 1));
|
||||
|
||||
setState(() {});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new StoreConnector<AppState, _ViewModel>(
|
||||
distinct: true,
|
||||
converter: (store) {
|
||||
print('New ViewModel of Fires Notifications');
|
||||
return new _ViewModel(
|
||||
onDeleteAll: () {
|
||||
store.dispatch(new DeleteAllFireNotificationAction());
|
||||
},
|
||||
onDelete: (notif) {
|
||||
store.dispatch(new DeleteFireNotificationAction(notif));
|
||||
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||
content: new Text(S.of(context).youDeletedThisPlace),
|
||||
action: new SnackBarAction(
|
||||
label: S.of(context).UNDO,
|
||||
onPressed: () {
|
||||
store.dispatch(new AddFireNotificationAction(notif));
|
||||
})));
|
||||
},
|
||||
onTap: (notif) {},
|
||||
fireNotifications: store.state.fireNotifications);
|
||||
},
|
||||
builder: (context, view) {
|
||||
var hasFireNotifications = view.fireNotifications.length > 0;
|
||||
final title = S.of(context).fireNotificationsTitle;
|
||||
print('Building Fire Notifications List');
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: new MainDrawer(context),
|
||||
appBar: new AppBar(
|
||||
title: Text(title),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
_scaffoldKey.currentState.openDrawer();
|
||||
},
|
||||
),
|
||||
actions: listWithoutNulls(<Widget>[
|
||||
hasFireNotifications
|
||||
? IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
onPressed: () => view.onDeleteAll(),
|
||||
)
|
||||
: null
|
||||
])),
|
||||
body: !hasFireNotifications
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: new Card(
|
||||
child: new Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: new CenteredColumn(children: <Widget>[
|
||||
new Icon(Icons.notifications_none,
|
||||
size: 150.0, color: Colors.black26),
|
||||
new SizedBox(height: 20.0),
|
||||
new Text(
|
||||
S.of(context).fireNotificationsDescription,
|
||||
textAlign: TextAlign.center,
|
||||
textScaleFactor: 1.1,
|
||||
style: new TextStyle(
|
||||
height: 1.5, color: Colors.black45))
|
||||
]))))
|
||||
: _buildSavedFireNotifications(context,
|
||||
view.fireNotifications, view.onDelete, view.onTap));
|
||||
});
|
||||
}
|
||||
|
||||
void gotoLocationMap(
|
||||
Store<AppState> store, FireNotification notif, BuildContext context) {
|
||||
/* store.dispatch(new ShowFireNotificationMapAction(notif));
|
||||
Navigator.push(context,
|
||||
new MaterialPageRoute(builder: (context) => new FireNotificationMap())); */
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:bson_objectid/bson_objectid.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:fires_flutter/models/fireNotification.dart';
|
||||
import 'package:redux/src/store.dart';
|
||||
|
||||
import 'models/appState.dart';
|
||||
|
|
@ -8,17 +10,36 @@ final FirebaseMessaging _firebaseMessaging = new FirebaseMessaging();
|
|||
|
||||
// https://pub.dartlang.org/packages/firebase_messaging#-example-tab-
|
||||
void firebaseConfig(Store<AppState> store) {
|
||||
_firebaseMessaging.configure(onLaunch: (Map<String, dynamic> message) {
|
||||
print('On firebase launch');
|
||||
}, onMessage: (Map<String, dynamic> message) {
|
||||
print('On firebase message');
|
||||
_firebaseMessaging.configure(onMessage: (Map<String, dynamic> message) {
|
||||
print("onMessage: $message");
|
||||
onMessage(message, store);
|
||||
//_showItemDialog(message);
|
||||
}, onLaunch: (Map<String, dynamic> message) {
|
||||
print("onLaunch: $message");
|
||||
//_navigateToItemDetail(message);
|
||||
onMessage(message, store);
|
||||
}, onResume: (Map<String, dynamic> message) {
|
||||
print('On firebase resume');
|
||||
print("onResume: $message");
|
||||
//_navigateToItemDetail(message);
|
||||
onMessage(message, store);
|
||||
});
|
||||
|
||||
getToken(store);
|
||||
}
|
||||
|
||||
void onMessage(Map<String, dynamic> message, Store<AppState> store) {
|
||||
FireNotification notif = new FireNotification(
|
||||
id: new ObjectId.fromHexString(message['id']),
|
||||
lat: double.parse(message['lat']),
|
||||
lon: double.parse(message['lon']),
|
||||
description: message['description'],
|
||||
when: DateTime.parse(message['when']));
|
||||
print(notif);
|
||||
store.dispatch(new AddFireNotificationAction(notif));
|
||||
}
|
||||
|
||||
getToken(Store<AppState> store) async {
|
||||
String token = await _firebaseMessaging.onTokenRefresh.first;
|
||||
// print(token);
|
||||
store.dispatch(new OnUserTokenAction(token));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import 'theme.dart';
|
|||
import 'privacyPage.dart';
|
||||
import 'fireAlert.dart';
|
||||
import 'supportPage.dart';
|
||||
import 'fireNotificationList.dart';
|
||||
|
||||
class FiresApp extends StatelessWidget {
|
||||
static final WidgetBuilder introWidget = (context) => new IntroPage();
|
||||
|
|
@ -28,6 +29,7 @@ class FiresApp extends StatelessWidget {
|
|||
Sandbox.routeName: (BuildContext context) => new Sandbox(),
|
||||
FireAlert.routeName: (BuildContext context) => new FireAlert(),
|
||||
SupportPage.routeName: (BuildContext context) => new SupportPage(),
|
||||
FireNotificationList.routeName: (BuildContext context) => new FireNotificationList(),
|
||||
};
|
||||
|
||||
final Store<AppState> store;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ class S implements WidgetsLocalizations {
|
|||
String get chooseAWatchRadio => "Choose a watch radio";
|
||||
String get confirm => "Confirm";
|
||||
String get errorFirePlaceDialog => "We couldn't get the location of the fire";
|
||||
String get fireNotificationsDescription => "Here you will receive fire notifications in locations you are subscribed to";
|
||||
String get fireNotificationsTitle => "Fire Notifications";
|
||||
String get firesInTheWorld => "Active fires in the world";
|
||||
String get firesInYourPlaces => "Active fires in your places";
|
||||
String get firesNearPlace => "Fires near other place";
|
||||
|
|
@ -91,6 +93,8 @@ class es extends S {
|
|||
@override
|
||||
String get alertWhenThereIsAFire => "Alerta cuando hay un fuego";
|
||||
@override
|
||||
String get fireNotificationsDescription => "Aquí recibirás las notificaciones de fuegos en los lugares a los que te subscribas";
|
||||
@override
|
||||
String get anHour => "una hora";
|
||||
@override
|
||||
String get notifyAFire => "Notificar un fuego";
|
||||
|
|
@ -131,6 +135,8 @@ class es extends S {
|
|||
@override
|
||||
String get aMinute => "un minuto";
|
||||
@override
|
||||
String get fireNotificationsTitle => "Notificaciones de fuegos";
|
||||
@override
|
||||
String get firesInYourPlaces => "Fuegos en tus lugares";
|
||||
@override
|
||||
String get isYourUbicationEnabled => "No podemos saber tu ubicación actual. ¿Están los servicios de ubicación en tu móvil activados?";
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ void mainCommon(List<Middleware<AppState>> otherMiddleware) {
|
|||
firesApiKey: secrets['firesApiKey'],
|
||||
firesApiUrl: secrets['firesApiUrl'] + "api/v1/"),
|
||||
middleware: List.from(otherMiddleware)
|
||||
..add(fetchYourLocationsMiddleware));
|
||||
..add(fetchDataMiddleware));
|
||||
|
||||
injector.map<String>((i) => store.state.firesApiUrl, key: "firesApiUrl");
|
||||
injector.map<String>((i) => store.state.firesApiKey, key: "firesApiKey");
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import 'package:comunes_flutter/comunes_flutter.dart';
|
|||
import 'privacyPage.dart';
|
||||
import 'fireAlert.dart';
|
||||
import 'supportPage.dart';
|
||||
import 'fireNotificationList.dart';
|
||||
import 'package:community_material_icon/community_material_icon.dart';
|
||||
|
||||
class MainDrawer extends Drawer {
|
||||
MainDrawer(BuildContext context, {key})
|
||||
|
|
@ -63,6 +65,15 @@ Widget mainDrawer(BuildContext context) {
|
|||
Navigator.pushNamed(context, FireAlert.routeName);
|
||||
},
|
||||
),
|
||||
new ListTile(
|
||||
leading: const Icon(Icons.notifications),
|
||||
title: new Text(S.of(context).fireNotificationsTitle),
|
||||
onTap: () {
|
||||
// Then close the drawer
|
||||
Navigator.pop(context);
|
||||
Navigator.pushNamed(context, FireNotificationList.routeName);
|
||||
},
|
||||
),
|
||||
new Divider(),
|
||||
new ListTile(
|
||||
leading: const Icon(Icons.favorite),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:comunes_flutter/comunes_flutter.dart';
|
||||
import 'package:fires_flutter/models/yourLocation.dart';
|
||||
import 'package:fires_flutter/models/fireNotification.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ class AppState extends Object with _$AppStateSerializerMixin {
|
|||
@JsonKey(ignore: true)
|
||||
final String firesApiUrl;
|
||||
final List<YourLocation> yourLocations;
|
||||
final List<FireNotification> fireNotifications;
|
||||
@JsonKey(ignore: true)
|
||||
final FireMapState fireMapState;
|
||||
|
||||
|
|
@ -37,6 +39,7 @@ class AppState extends Object with _$AppStateSerializerMixin {
|
|||
|
||||
AppState(
|
||||
{this.yourLocations: const <YourLocation>[],
|
||||
this.fireNotifications: const <FireNotification>[],
|
||||
this.user: const User.initial(),
|
||||
this.isLoading: false,
|
||||
this.isLoaded: false,
|
||||
|
|
@ -55,6 +58,7 @@ class AppState extends Object with _$AppStateSerializerMixin {
|
|||
String firesApiKey,
|
||||
String firesApiUrl,
|
||||
List<YourLocation> yourLocations,
|
||||
List<FireNotification> fireNotifications,
|
||||
FireMapState fireMapState}) {
|
||||
return new AppState(
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
|
|
@ -65,6 +69,7 @@ class AppState extends Object with _$AppStateSerializerMixin {
|
|||
firesApiKey: firesApiKey ?? this.firesApiKey,
|
||||
firesApiUrl: firesApiUrl ?? this.firesApiUrl,
|
||||
yourLocations: yourLocations ?? this.yourLocations,
|
||||
fireNotifications: fireNotifications ?? this.fireNotifications,
|
||||
fireMapState: fireMapState ?? this.fireMapState);
|
||||
}
|
||||
|
||||
|
|
@ -73,24 +78,26 @@ class AppState extends Object with _$AppStateSerializerMixin {
|
|||
return 'AppState{\nuser: ${user}\nisLoading: $isLoading\nisLoaded: $isLoaded\napiKey: ${ellipse(
|
||||
firesApiKey, 8)}\napiUrl: ${ellipse(
|
||||
firesApiUrl, 8)}\nyourLocations count: ${yourLocations
|
||||
.length}\nyourLocations: ${yourLocations}\nfireMapState: $fireMapState}';
|
||||
.length}\nyourLocations: ${yourLocations}\nfireNotifications: ${fireNotifications}\nfireMapState: $fireMapState}';
|
||||
}
|
||||
}
|
||||
|
||||
typedef void AddYourLocationFunction(YourLocation loc);
|
||||
typedef void DeleteYourLocationFunction(YourLocation loc);
|
||||
typedef void ToggleSubscriptionFunction(YourLocation loc);
|
||||
|
||||
typedef void OnLocationTapFunction(YourLocation loc);
|
||||
typedef void OnSubscribeFunction(YourLocation loc);
|
||||
typedef void OnSubscribeDistanceChangeFunction(YourLocation loc);
|
||||
typedef void OnUnSubscribeFunction(YourLocation loc);
|
||||
typedef void OnSubscribeConfirmedFunction(YourLocation loc);
|
||||
|
||||
typedef void OnLocationEdit(YourLocation loc);
|
||||
typedef void OnLocationEditing(YourLocation loc);
|
||||
typedef void OnLocationEditConfirm(YourLocation loc);
|
||||
typedef void OnLocationEditCancel(YourLocation loc);
|
||||
typedef void TapFireNotificationFunction(FireNotification notif);
|
||||
// typedef void OnReceivedFireNotificationFunction(FireNotification notif);
|
||||
typedef void DeleteFireNotificationFunction(FireNotification notif);
|
||||
typedef void DeleteAllFireNotificationFunction();
|
||||
|
||||
// unused
|
||||
// typedef void UpdateYourLocationFunction(ObjectId id, YourLocation loc);
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@ part of 'appState.dart';
|
|||
AppState _$AppStateFromJson(Map<String, dynamic> json) => new AppState(
|
||||
yourLocations: (json['yourLocations'] as List)
|
||||
.map((e) => new YourLocation.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
fireNotifications: (json['fireNotifications'] as List)
|
||||
.map((e) => new FireNotification.fromJson(e as Map<String, dynamic>))
|
||||
.toList());
|
||||
|
||||
abstract class _$AppStateSerializerMixin {
|
||||
List<YourLocation> get yourLocations;
|
||||
List<FireNotification> get fireNotifications;
|
||||
Map<String, dynamic> toJson() => new _$AppStateJsonMapWrapper(this);
|
||||
}
|
||||
|
||||
|
|
@ -23,7 +27,7 @@ class _$AppStateJsonMapWrapper extends $JsonMapWrapper {
|
|||
_$AppStateJsonMapWrapper(this._v);
|
||||
|
||||
@override
|
||||
Iterable<String> get keys => const ['yourLocations'];
|
||||
Iterable<String> get keys => const ['yourLocations', 'fireNotifications'];
|
||||
|
||||
@override
|
||||
dynamic operator [](Object key) {
|
||||
|
|
@ -31,6 +35,8 @@ class _$AppStateJsonMapWrapper extends $JsonMapWrapper {
|
|||
switch (key) {
|
||||
case 'yourLocations':
|
||||
return _v.yourLocations;
|
||||
case 'fireNotifications':
|
||||
return _v.fireNotifications;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
59
lib/models/fireNotification.dart
Normal file
59
lib/models/fireNotification.dart
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import 'package:bson_objectid/bson_objectid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'fireNotification.g.dart';
|
||||
|
||||
_objectIdFromJson(String json) {
|
||||
return new ObjectId.fromHexString(json);
|
||||
}
|
||||
|
||||
_objectIdToJson(ObjectId o) {
|
||||
return o.toString();
|
||||
}
|
||||
|
||||
@JsonSerializable(nullable: false)
|
||||
class FireNotification extends Object with _$FireNotificationSerializerMixin {
|
||||
@JsonKey(toJson: _objectIdToJson, fromJson: _objectIdFromJson)
|
||||
ObjectId id;
|
||||
final double lat;
|
||||
final double lon;
|
||||
final String description;
|
||||
final DateTime when;
|
||||
|
||||
factory FireNotification.fromJson(Map<String, dynamic> json) => _$FireNotificationFromJson(json);
|
||||
|
||||
// static FireNotification noLocation = new FireNotification(lat: 0.0, lon: 0.0, description: '', when: new DateTime(0));
|
||||
|
||||
FireNotification({this.id, @required this.lat, @required this.lon, @required this.description, @required this.when}) {
|
||||
if (this.id == null) this.id = new ObjectId();
|
||||
}
|
||||
|
||||
FireNotification copyWith({id, lat, lon, description, when}) {
|
||||
return new FireNotification(
|
||||
id: id ?? this.id,
|
||||
lat: lat ?? this.lat,
|
||||
lon: lon ?? this.lon,
|
||||
description: description ?? this.description,
|
||||
when: when ?? this.when);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is FireNotification &&
|
||||
runtimeType == other.runtimeType &&
|
||||
id == other.id &&
|
||||
lat == other.lat &&
|
||||
lon == other.lon &&
|
||||
description == other.description &&
|
||||
when == other.when;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode ^ lat.hashCode ^ lon.hashCode ^ description.hashCode ^ when.hashCode;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FireNotification {id: $id, lat: $lat, lon: $lon, description: $description, when: $when}';
|
||||
}
|
||||
}
|
||||
54
lib/models/fireNotification.g.dart
Normal file
54
lib/models/fireNotification.g.dart
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2018, Comunes Association.
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'fireNotification.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FireNotification _$FireNotificationFromJson(Map<String, dynamic> json) =>
|
||||
new FireNotification(
|
||||
id: _objectIdFromJson(json['id'] as String),
|
||||
lat: (json['lat'] as num).toDouble(),
|
||||
lon: (json['lon'] as num).toDouble(),
|
||||
description: json['description'] as String,
|
||||
when: DateTime.parse(json['when'] as String));
|
||||
|
||||
abstract class _$FireNotificationSerializerMixin {
|
||||
ObjectId get id;
|
||||
double get lat;
|
||||
double get lon;
|
||||
String get description;
|
||||
DateTime get when;
|
||||
Map<String, dynamic> toJson() => new _$FireNotificationJsonMapWrapper(this);
|
||||
}
|
||||
|
||||
class _$FireNotificationJsonMapWrapper extends $JsonMapWrapper {
|
||||
final _$FireNotificationSerializerMixin _v;
|
||||
_$FireNotificationJsonMapWrapper(this._v);
|
||||
|
||||
@override
|
||||
Iterable<String> get keys =>
|
||||
const ['id', 'lat', 'lon', 'description', 'when'];
|
||||
|
||||
@override
|
||||
dynamic operator [](Object key) {
|
||||
if (key is String) {
|
||||
switch (key) {
|
||||
case 'id':
|
||||
return _objectIdToJson(_v.id);
|
||||
case 'lat':
|
||||
return _v.lat;
|
||||
case 'lon':
|
||||
return _v.lon;
|
||||
case 'description':
|
||||
return _v.description;
|
||||
case 'when':
|
||||
return _v.when.toIso8601String();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
36
lib/models/fireNotificationsPersist.dart
Normal file
36
lib/models/fireNotificationsPersist.dart
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fires_flutter/models/fireNotification.dart';
|
||||
|
||||
import '../globals.dart' as globals;
|
||||
|
||||
final String fireNotificationKey = 'fireNotifications';
|
||||
|
||||
Future<List<FireNotification>> loadFireNotifications() async {
|
||||
return await globals.prefs.then((prefs) {
|
||||
List<String> FireNotifications = prefs.getStringList(fireNotificationKey);
|
||||
List<FireNotification> persistedList = List<FireNotification>();
|
||||
if (FireNotifications == null) {
|
||||
FireNotifications = [];
|
||||
// first run, init with empty list
|
||||
persistFireNotifications(persistedList);
|
||||
}
|
||||
FireNotifications.forEach((notificationString) {
|
||||
Map notificationMap = json.decode(notificationString);
|
||||
persistedList.add(FireNotification.fromJson(notificationMap));
|
||||
});
|
||||
return persistedList;
|
||||
});
|
||||
}
|
||||
|
||||
persistFireNotifications(List<FireNotification> notif) {
|
||||
print('Persisting $notif');
|
||||
globals.prefs.then((prefs) {
|
||||
List<String> notifAsString = [];
|
||||
notif.forEach((notification) {
|
||||
notifAsString.add(json.encode(notification.toJson()));
|
||||
});
|
||||
prefs.setStringList(fireNotificationKey, notifAsString);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
export 'appActions.dart';
|
||||
export 'yourLocationActions.dart';
|
||||
export 'fireMapActions.dart';
|
||||
export 'fireNotificationActions.dart';
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fires_flutter/models/yourLocation.dart';
|
||||
import 'package:fires_flutter/models/fireNotification.dart';
|
||||
|
||||
abstract class AppActions {}
|
||||
|
||||
|
|
@ -12,6 +13,8 @@ class FetchYourLocationsSucceededAction extends AppActions {
|
|||
FetchYourLocationsSucceededAction(this.fetchedYourLocations);
|
||||
}
|
||||
|
||||
class FetchFireNotificationsAction extends AppActions {}
|
||||
|
||||
class FetchYourLocationsFailedAction extends AppActions {
|
||||
final Exception error;
|
||||
|
||||
|
|
@ -35,3 +38,9 @@ class OnUserLangAction extends AppActions {
|
|||
|
||||
OnUserLangAction(this.lang);
|
||||
}
|
||||
|
||||
class FetchFireNotificationsSucceededAction extends AppActions {
|
||||
final List<FireNotification> fetchedFireNotifications;
|
||||
|
||||
FetchFireNotificationsSucceededAction(this.fetchedFireNotifications);
|
||||
}
|
||||
|
|
@ -5,5 +5,8 @@ AppState appReducer(AppState state, action) {
|
|||
if (action is FetchYourLocationsSucceededAction) {
|
||||
return state.copyWith(yourLocations: action.fetchedYourLocations);
|
||||
}
|
||||
if (action is FetchFireNotificationsSucceededAction) {
|
||||
return state.copyWith(fireNotifications: action.fetchedFireNotifications);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import 'package:just_debounce_it/just_debounce_it.dart';
|
|||
import 'package:redux/redux.dart';
|
||||
|
||||
import '../models/appState.dart';
|
||||
import '../models/fireNotificationsPersist.dart';
|
||||
import '../models/firesApi.dart';
|
||||
import '../models/yourLocationPersist.dart';
|
||||
import 'actions.dart';
|
||||
|
|
@ -20,8 +21,7 @@ import 'actions.dart';
|
|||
|
||||
FiresApi api = Injector.getInjector().get<FiresApi>();
|
||||
|
||||
void fetchYourLocationsMiddleware(
|
||||
Store<AppState> store, action, NextDispatcher next) {
|
||||
void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
|
||||
// If our Middleware encounters a `FetchYourLocationAction`
|
||||
|
||||
if (action is OnUserLangAction) {
|
||||
|
|
@ -46,8 +46,7 @@ void fetchYourLocationsMiddleware(
|
|||
|
||||
if (action is AddYourLocationAction) {
|
||||
if (action.loc.subscribed) {
|
||||
subscribeViaApi(store, action.loc,
|
||||
(sub) {
|
||||
subscribeViaApi(store, action.loc, (sub) {
|
||||
store.dispatch(new AddedYourLocationAction(sub));
|
||||
persistYourLocations(store.state.yourLocations);
|
||||
});
|
||||
|
|
@ -69,6 +68,21 @@ void fetchYourLocationsMiddleware(
|
|||
}
|
||||
}
|
||||
|
||||
if (action is DeleteFireNotificationAction) {
|
||||
store.dispatch(new DeletedFireNotificationAction(action.notif));
|
||||
persistFireNotifications(store.state.fireNotifications);
|
||||
}
|
||||
|
||||
if (action is DeleteAllFireNotificationAction) {
|
||||
store.dispatch(new DeletedAllFireNotificationAction());
|
||||
persistFireNotifications(store.state.fireNotifications);
|
||||
}
|
||||
|
||||
if (action is AddFireNotificationAction) {
|
||||
store.dispatch(new AddedFireNotificationAction(action.notif));
|
||||
persistFireNotifications(store.state.fireNotifications);
|
||||
}
|
||||
|
||||
if (action is ShowYourLocationMapAction) {
|
||||
api
|
||||
.getYourLocationFireStats(store.state, action.loc)
|
||||
|
|
@ -139,6 +153,15 @@ void fetchYourLocationsMiddleware(
|
|||
store.dispatch(new FetchYourLocationsFailedAction(error));
|
||||
});
|
||||
}
|
||||
|
||||
if (action is FetchFireNotificationsAction) {
|
||||
loadFireNotifications().then((fireNotifications) {
|
||||
store.dispatch(
|
||||
new FetchFireNotificationsSucceededAction(fireNotifications));
|
||||
persistFireNotifications(fireNotifications);
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure our actions continue on to the reducer.
|
||||
next(action);
|
||||
}
|
||||
|
|
@ -167,5 +190,6 @@ void createUser(store, lang, token) {
|
|||
api.createUser(store.state, token, lang).then((userId) {
|
||||
store.dispatch(new OnUserCreatedAction(userId));
|
||||
store.dispatch(new FetchYourLocationsAction());
|
||||
store.dispatch(new FetchFireNotificationsAction());
|
||||
});
|
||||
}
|
||||
|
|
|
|||
35
lib/redux/fireNotificationActions.dart
Normal file
35
lib/redux/fireNotificationActions.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:fires_flutter/models/fireNotification.dart';
|
||||
|
||||
abstract class FireNotificationActions {}
|
||||
|
||||
class DeleteFireNotificationAction extends FireNotificationActions {
|
||||
final FireNotification notif;
|
||||
|
||||
DeleteFireNotificationAction(this.notif);
|
||||
}
|
||||
|
||||
class DeleteAllFireNotificationAction extends FireNotificationActions {
|
||||
DeleteAllFireNotificationAction();
|
||||
}
|
||||
|
||||
class AddFireNotificationAction extends FireNotificationActions {
|
||||
final FireNotification notif;
|
||||
|
||||
AddFireNotificationAction(this.notif);
|
||||
}
|
||||
|
||||
class DeletedFireNotificationAction extends FireNotificationActions {
|
||||
final FireNotification notif;
|
||||
|
||||
DeletedFireNotificationAction(this.notif);
|
||||
}
|
||||
|
||||
class DeletedAllFireNotificationAction extends FireNotificationActions {
|
||||
DeletedAllFireNotificationAction();
|
||||
}
|
||||
|
||||
class AddedFireNotificationAction extends FireNotificationActions {
|
||||
final FireNotification notif;
|
||||
|
||||
AddedFireNotificationAction(this.notif);
|
||||
}
|
||||
28
lib/redux/fireNotificationReducer.dart
Normal file
28
lib/redux/fireNotificationReducer.dart
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:redux/redux.dart';
|
||||
|
||||
import 'actions.dart';
|
||||
import 'package:fires_flutter/models/fireNotification.dart';
|
||||
|
||||
final fireNotificationReducer = combineReducers<List<FireNotification>>([
|
||||
new TypedReducer<List<FireNotification>, AddedFireNotificationAction>(
|
||||
_receivedFireNotification),
|
||||
new TypedReducer<List<FireNotification>, DeletedFireNotificationAction>(
|
||||
_deletedFireNotification),
|
||||
new TypedReducer<List<FireNotification>, DeletedAllFireNotificationAction>(
|
||||
_deletedAllFireNotifications)
|
||||
]);
|
||||
|
||||
List<FireNotification> _receivedFireNotification(
|
||||
List<FireNotification> yourLocations, AddedFireNotificationAction action) {
|
||||
return new List.from(yourLocations)..add(action.notif);
|
||||
}
|
||||
|
||||
List<FireNotification> _deletedFireNotification(
|
||||
List<FireNotification> notifications, DeletedFireNotificationAction action) {
|
||||
return new List.from(notifications)..remove(action.notif);
|
||||
}
|
||||
|
||||
List<FireNotification> _deletedAllFireNotifications(
|
||||
List<FireNotification> notifications, DeletedAllFireNotificationAction action) {
|
||||
return new List<FireNotification>();
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import 'userReducer.dart';
|
|||
import 'yourLocationsReducer.dart';
|
||||
import 'actions.dart';
|
||||
import 'appReducer.dart';
|
||||
import 'fireNotificationReducer.dart';
|
||||
|
||||
// We create the State reducer by combining many smaller reducers into one!
|
||||
AppState appStateReducer(AppState prevState, action) {
|
||||
|
|
@ -15,6 +16,7 @@ AppState appStateReducer(AppState prevState, action) {
|
|||
state = appReducer(prevState, action);
|
||||
return AppState(
|
||||
yourLocations: yourLocationsReducer(state.yourLocations, action),
|
||||
fireNotifications: fireNotificationReducer(state.fireNotifications, action),
|
||||
user: userReducer(state.user, action),
|
||||
isLoading: loadingReducer(state.isLoading, action),
|
||||
isLoaded: loadedReducer(state.isLoaded, action),
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ dependencies:
|
|||
flutter_fab_dialer: "^0.0.5"
|
||||
|
||||
# firebase
|
||||
firebase_messaging: "^1.0.2"
|
||||
firebase_messaging: "^1.0.4"
|
||||
|
||||
# icons
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
|
|
|
|||
|
|
@ -51,5 +51,7 @@
|
|||
"callEmergencyServicesDescription": "You can 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, etc.",
|
||||
"fireNotificationsTitle": "Fire Notifications",
|
||||
"fireNotificationsDescription": "Here you will receive fire notifications in locations you are subscribed to"
|
||||
}
|
||||
|
|
@ -51,5 +51,7 @@
|
|||
"callEmergencyServicesDescription": "Puedes 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, etc.",
|
||||
"fireNotificationsTitle": "Notificaciones de fuegos",
|
||||
"fireNotificationsDescription": "Aquí recibirás las notificaciones de fuegos en los lugares a los que te subscribas"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue