import 'dart:async'; import 'package:flutter_map/src/layer/polyline_layer.dart'; import 'package:get_it/get_it.dart'; import 'package:objectid/objectid.dart'; import 'package:redux/redux.dart'; import '../models/appState.dart'; import '../models/fireNotification.dart'; import '../models/fireNotificationsPersist.dart'; import '../models/firesApi.dart'; import '../models/yourLocation.dart'; import '../models/yourLocationPersist.dart'; import '../objectIdUtils.dart'; import 'actions.dart'; // A middleware takes in 3 parameters: your Store, which you can use to // read state or dispatch new actions, the action that was dispatched, // and a `next` function. The first two you know about, and the `next` // function is responsible for sending the action to your Reducer, or // the next Middleware if you provide more than one. // // Middleware do not return any values themselves. They simply forward // actions on to the Reducer or swallow actions in some special cases. FiresApi api = GetIt.instance(); // Simple debounce mechanism Timer? _locationUpdateDebounceTimer; void debounceLocationUpdate(Duration duration, void Function() callback) { _locationUpdateDebounceTimer?.cancel(); _locationUpdateDebounceTimer = Timer(duration, callback); } void fetchDataMiddleware( Store store, dynamic action, NextDispatcher next) { // If our Middleware encounters a `FetchYourLocationAction` if (action is OnUserLangAction) { // I can create the user with the lang and the token createUser(store, action.lang, store.state.user.token); } if (action is OnUserTokenAction) { // I can create the user with the lang and the token createUser(store, store.state.user.lang, action.token); } if (action is EditConfirmYourLocationAction) { if (action.loc.subscribed) { // FXIME save lat/lon } else { // No subscribed (only local) } } if (action is AddYourLocationAction) { if (action.loc.subscribed) { subscribeViaApi(store, action.loc, (YourLocation sub) { store.dispatch(AddedYourLocationAction(sub)); persistYourLocations(store.state.yourLocations); }); } else { // No subscribed (only local) store.dispatch(AddedYourLocationAction(action.loc)); persistYourLocations(store.state.yourLocations); } getFiresStatsInLocation(store, action.loc); } if (action is DeleteYourLocationAction) { store.dispatch(DeletedYourLocationAction(action.loc.id)); if (action.loc.subscribed) { unsubsViaApi(store, action.loc.id, () { persistYourLocations(store.state.yourLocations); }); } else { persistYourLocations(store.state.yourLocations); } } if (action is DeleteFireNotificationAction) { store.dispatch(DeletedFireNotificationAction(action.notif)); persistFireNotifications(store.state.fireNotifications); } if (action is DeleteAllFireNotificationAction) { store.dispatch(DeletedAllFireNotificationAction()); persistFireNotifications(store.state.fireNotifications); } if (action is AddFireNotificationAction) { store.dispatch(AddedFireNotificationAction(action.notif)); persistFireNotifications(store.state.fireNotifications); } if (action is ShowYourLocationMapAction) { getFiresStatsInLocation(store, action.loc); } if (action is ShowFireNotificationMapAction) { getFiresStatsInFire(store, action.notif); } if (action is UpdateYourLocationAction) { if (action.loc.subscribed) debounceLocationUpdate( const Duration(seconds: 2), () => api .getFiresInLocation( state: store.state, lat: action.loc.lat, lon: action.loc.lon, distance: action.loc.distance) .then( (UpdateFireMapStatsAction result) => store.dispatch(result))); store.dispatch(UpdatedYourLocationAction(action.loc)); persistYourLocations(store.state.yourLocations); } if (action is SubscribeConfirmAction) { subscribeViaApi(store, action.loc, (YourLocation sub) { store.dispatch(UpdateYourLocationAction(action.loc)); persistYourLocations(store.state.yourLocations); }); } if (action is UnSubscribeAction) { unsubsViaApi(store, action.loc.id, () { store.dispatch(UpdateYourLocationAction(action.loc)); persistYourLocations(store.state.yourLocations); }); } if (action is ToggleSubscriptionAction) { if (action.loc.subscribed) { subscribeViaApi(store, action.loc, (YourLocation sub) => store.dispatch(ToggledSubscriptionAction(sub))); } else { unsubsViaApi(store, action.loc.id, () => store.dispatch(ToggledSubscriptionAction(action.loc))); } } if (action is ReadFireNotificationAction) { store.dispatch(ReadedFireNotificationAction(action.notif)); persistFireNotifications(store.state.fireNotifications); } if (action is FetchYourLocationsAction) { // Use the api to fetch the YourLocations loadYourLocations().then((List localLocations) { api .fetchYourLocations(store.state) .then((List subscribedLocations) { // If it succeeds, dispatch a success action with the YourLocations. // Our reducer will then update the State using these YourLocations. // unsubscribe all locally to sync the subs state for (final YourLocation location in localLocations) { location.subscribed = false; } for (final YourLocation subsLoc in subscribedLocations) { final YourLocation locSubs = localLocations.firstWhere( (YourLocation localLocation) => localLocation.id == subsLoc.id, orElse: () { localLocations.add(subsLoc); return subsLoc; }); locSubs.subscribed = true; } store.dispatch(FetchYourLocationsSucceededAction(localLocations)); persistYourLocations(localLocations); for (final YourLocation yl in localLocations) { api .getFiresInLocation( state: store.state, lat: yl.lat, lon: yl.lon, distance: yl.distance) .then((UpdateFireMapStatsAction value) { yl.currentNumFires = value.numFires; store.dispatch(UpdateYourLocationAction(yl)); }); } final Completer? completer = action.refreshCallback; completer?.complete(null); }); }).catchError((Object onError) { // If it fails, dispatch a failure action. The reducer will // update the state with the error. store.dispatch(FetchYourLocationsFailedAction(onError as Exception)); }); } if (action is FetchFireNotificationsAction) { loadFireNotifications().then((List fireNotifications) { int unread = 0; for (final FireNotification notif in fireNotifications) { if (!notif.read) { unread++; } } store.dispatch( FetchFireNotificationsSucceededAction(fireNotifications, unread)); persistFireNotifications(fireNotifications); }); } if (action is FetchMonitoredAreasAction) { api.getMonitoredAreas(state: store.state).then((List result) { // store.dispatch() store.dispatch(FetchMonitoredAreasSucceededAction(result)); }); } if (action is MarkFireAsFalsePositiveAction) { api .markFalsePositive(store.state, store.state.user.token, action.notif.sealed, action.type) .then((bool result) { if (result) { // Not necessary getFiresStatsInFire(store, action.notif); store.dispatch(UpdatedFireNotificationAction(action.notif)); } }); } // Make sure our actions continue on to the reducer. next(action); } void getFiresStatsInLocation(Store store, YourLocation loc) { api .getFiresInLocation( state: store.state, lat: loc.lat, lon: loc.lon, distance: loc.distance) .then((UpdateFireMapStatsAction result) { store.dispatch(result); loc.currentNumFires = result.numFires; store.dispatch(UpdateYourLocationAction(loc)); }); } void getFiresStatsInFire(Store store, FireNotification notif) { api .getFiresInLocation( state: store.state, lat: notif.lat, lon: notif.lon, distance: 1) // FalsePositive/server/publications.js .then((UpdateFireMapStatsAction result) => store.dispatch(result)); } void unsubsViaApi( Store store, ObjectId id, void Function() onUnsubs) { api.unsubscribe(store.state, id.hexString).then((bool res) { onUnsubs(); persistYourLocations(store.state.yourLocations); }); } void subscribeViaApi(Store store, YourLocation loc, void Function(YourLocation) onSubs) { api.subscribe(store.state, loc).then((String subsId) { final YourLocation sub = loc; // if (loc.id != subsId) { sub.id = objectIdFromJson(subsId); // } onSubs(sub); persistYourLocations(store.state.yourLocations); }); } void createUser(Store store, String lang, String token) { assert(token != null, 'User lang is null'); assert(token != null, 'User mobile token is null'); api.createUser(store.state, token, lang).then((String userId) { store.dispatch(OnUserCreatedAction(userId)); store.dispatch(FetchYourLocationsAction()); store.dispatch(FetchFireNotificationsAction()); store.dispatch(FetchMonitoredAreasAction()); }); }