diff --git a/lib/active_fires.dart b/lib/active_fires.dart index b8be979..6f1911d 100644 --- a/lib/active_fires.dart +++ b/lib/active_fires.dart @@ -113,10 +113,11 @@ class _ActiveFiresPageState extends State { : Icons.notifications_off), color: loc.subscribed ? fires600 : null, onPressed: () { - loc.subscribed = !loc.subscribed; - onToggle(loc); + final YourLocation updatedLoc = + loc.copyWith(subscribed: !loc.subscribed); + onToggle(updatedLoc); setState(() {}); - showSnackMsg(loc.subscribed + showSnackMsg(updatedLoc.subscribed ? S.of(context).subscribedToFires : S.of(context).unsubscribedToFires); }), diff --git a/lib/generic_map.dart b/lib/generic_map.dart index cfa5fa6..52d33f1 100644 --- a/lib/generic_map.dart +++ b/lib/generic_map.dart @@ -99,12 +99,12 @@ class GenericMapState extends State { store.dispatch(SubscribeAction()); }, onSubsConfirmed: (YourLocation loc) { - loc.subscribed = true; - store.dispatch(SubscribeConfirmAction(loc)); + store.dispatch( + SubscribeConfirmAction(loc.copyWith(subscribed: true))); }, onUnSubs: (YourLocation loc) { - loc.subscribed = false; - store.dispatch(UnSubscribeAction(loc)); + store.dispatch( + UnSubscribeAction(loc.copyWith(subscribed: false))); }, onSlide: (YourLocation loc) { store.dispatch(UpdateYourLocationMapAction(loc)); @@ -348,7 +348,8 @@ class GenericMapState extends State { _location?.distance ?? 0, onSlide: (int distance) { if (_location != null) { - _location!.distance = distance; + _location = _location! + .copyWith(distance: distance); view.onSlide(_location!); } }) diff --git a/lib/location_utils.dart b/lib/location_utils.dart index a00c046..b1ecfb3 100644 --- a/lib/location_utils.dart +++ b/lib/location_utils.dart @@ -18,17 +18,17 @@ Future getUserLocation( final LocationData location = await location0.getLocation(); // It seems that the lib fails with lat/lon values - final YourLocation yl = YourLocation( + YourLocation yl = YourLocation( id: ObjectId(), lat: location.latitude!, lon: location.longitude!); String address; try { address = await getReverseLocation(lat: yl.lat, lon: yl.lon); - yl.description = address; + yl = yl.copyWith(description: address); } catch (e) { try { address = await getReverseLocation(lat: yl.lat, lon: yl.lon, external: true); - yl.description = address; + yl = yl.copyWith(description: address); } catch (_) { // Ignore - fallback already attempted } diff --git a/lib/models/app_state.g.dart b/lib/models/app_state.g.dart new file mode 100644 index 0000000..15681c4 --- /dev/null +++ b/lib/models/app_state.g.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppState _$AppStateFromJson(Map json) => $checkedCreate( + 'AppState', + json, + ($checkedConvert) { + final val = AppState( + yourLocations: $checkedConvert( + 'yourLocations', + (v) => + (v as List?) + ?.map((e) => YourLocation.fromJson( + Map.from(e as Map))) + .toList() ?? + const []), + fireNotifications: $checkedConvert( + 'fireNotifications', + (v) => + (v as List?) + ?.map((e) => FireNotification.fromJson( + Map.from(e as Map))) + .toList() ?? + const []), + isLoading: $checkedConvert('isLoading', (v) => v as bool? ?? false), + ); + return val; + }, + ); + +Map _$AppStateToJson(AppState instance) => { + 'isLoading': instance.isLoading, + 'yourLocations': instance.yourLocations.map((e) => e.toJson()).toList(), + 'fireNotifications': + instance.fireNotifications.map((e) => e.toJson()).toList(), + }; diff --git a/lib/models/fire_notification.dart b/lib/models/fire_notification.dart index dbef19b..3fffa64 100644 --- a/lib/models/fire_notification.dart +++ b/lib/models/fire_notification.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:objectid/objectid.dart'; -import '../objectIdUtils.dart'; +import '../object_id_utils.dart'; import '../utils/widget_utils.dart'; part 'fire_notification.g.dart'; diff --git a/lib/models/fire_notification.g.dart b/lib/models/fire_notification.g.dart new file mode 100644 index 0000000..d20b48b --- /dev/null +++ b/lib/models/fire_notification.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'fire_notification.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FireNotification _$FireNotificationFromJson(Map json) => $checkedCreate( + 'FireNotification', + json, + ($checkedConvert) { + final val = FireNotification( + id: $checkedConvert('id', (v) => objectIdFromJson(v as String)), + lat: $checkedConvert('lat', (v) => (v as num).toDouble()), + lon: $checkedConvert('lon', (v) => (v as num).toDouble()), + description: $checkedConvert('description', (v) => v as String), + when: $checkedConvert('when', (v) => DateTime.parse(v as String)), + read: $checkedConvert('read', (v) => v as bool), + sealed: $checkedConvert('sealed', (v) => v as String), + subsId: + $checkedConvert('subsId', (v) => objectIdFromJson(v as String)), + ); + return val; + }, + ); + +Map _$FireNotificationToJson(FireNotification instance) => + { + 'id': objectIdToJson(instance.id), + 'lat': instance.lat, + 'lon': instance.lon, + 'description': instance.description, + 'when': instance.when.toIso8601String(), + 'sealed': instance.sealed, + 'subsId': objectIdToJson(instance.subsId), + 'read': instance.read, + }; diff --git a/lib/models/fires_api.dart b/lib/models/fires_api.dart index 5273065..c9b2cf0 100644 --- a/lib/models/fires_api.dart +++ b/lib/models/fires_api.dart @@ -7,10 +7,10 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import '../globals.dart' as globals; -import '../objectIdUtils.dart'; +import '../object_id_utils.dart'; import '../redux/actions.dart'; import 'app_state.dart'; -import 'falsePositiveTypes.dart'; +import 'false_positive_types.dart'; import 'your_location.dart'; class FiresApi { @@ -189,9 +189,12 @@ class FiresApi { final Map unionData = dataData['union'] as Map; final String unionValue = unionData['value'] as String; + final Map decodedJson = + json.decode(unionValue) as Map; + final Map geometry = + decodedJson['geometry'] as Map; final List multipolygon = - (json.decode(unionValue) as Map)['geometry'] - ['coordinates'] as List; + geometry['coordinates'] as List; for (final dynamic polygonDynamic in multipolygon) { final List polygon = polygonDynamic as List; for (final dynamic holeDynamic in polygon) { diff --git a/lib/models/your_location.dart b/lib/models/your_location.dart index db393e6..b975b02 100644 --- a/lib/models/your_location.dart +++ b/lib/models/your_location.dart @@ -2,7 +2,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; import 'package:objectid/objectid.dart'; -import '../objectIdUtils.dart'; +import '../object_id_utils.dart'; part 'your_location.g.dart'; diff --git a/lib/models/your_location.g.dart b/lib/models/your_location.g.dart new file mode 100644 index 0000000..21fc224 --- /dev/null +++ b/lib/models/your_location.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'your_location.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +YourLocation _$YourLocationFromJson(Map json) => $checkedCreate( + 'YourLocation', + json, + ($checkedConvert) { + final val = YourLocation( + id: $checkedConvert('id', (v) => objectIdFromJson(v as String)), + lat: $checkedConvert('lat', (v) => (v as num).toDouble()), + lon: $checkedConvert('lon', (v) => (v as num).toDouble()), + description: + $checkedConvert('description', (v) => v as String? ?? ''), + distance: + $checkedConvert('distance', (v) => (v as num?)?.toInt() ?? 10), + currentNumFires: + $checkedConvert('currentNumFires', (v) => (v as num?)?.toInt()), + subscribed: $checkedConvert('subscribed', (v) => v as bool? ?? false), + ); + return val; + }, + ); + +Map _$YourLocationToJson(YourLocation instance) => + { + 'id': objectIdToJson(instance.id), + 'lat': instance.lat, + 'lon': instance.lon, + 'description': instance.description, + 'subscribed': instance.subscribed, + 'distance': instance.distance, + 'currentNumFires': instance.currentNumFires, + }; diff --git a/lib/places_autocomplete_utils.dart b/lib/places_autocomplete_utils.dart index 7f52c16..9b6d694 100644 --- a/lib/places_autocomplete_utils.dart +++ b/lib/places_autocomplete_utils.dart @@ -65,7 +65,7 @@ class _PlaceSelectionDialogState extends State<_PlaceSelectionDialog> { }); } catch (e) { setState(() { - _errorMessage = 'Search error: ${e.toString()}'; + _errorMessage = 'Search error: $e'; _searchResults = []; }); } finally { @@ -98,7 +98,7 @@ class _PlaceSelectionDialogState extends State<_PlaceSelectionDialog> { return '${location.latitude.toStringAsFixed(4)}, ${location.longitude.toStringAsFixed(4)}'; } - void _selectLocation(Location location) async { + Future _selectLocation(Location location) async { final String description = await _getPlaceName(location); if (!mounted) { return; diff --git a/lib/redux/app_actions.dart b/lib/redux/app_actions.dart index 0b2d26c..ea0241d 100644 --- a/lib/redux/app_actions.dart +++ b/lib/redux/app_actions.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter_map/flutter_map.dart'; -import '../models/fireNotification.dart'; -import '../models/yourLocation.dart'; +import '../models/fire_notification.dart'; +import '../models/your_location.dart'; abstract class AppActions {} diff --git a/lib/redux/app_reducer.dart b/lib/redux/app_reducer.dart index 34c3372..82d0653 100644 --- a/lib/redux/app_reducer.dart +++ b/lib/redux/app_reducer.dart @@ -1,4 +1,4 @@ -import '../models/appState.dart'; +import '../models/app_state.dart'; import 'actions.dart'; AppState appReducer(AppState state, dynamic action) { diff --git a/lib/redux/fetch_data_middleware.dart b/lib/redux/fetch_data_middleware.dart index 2a98349..8c1d61f 100644 --- a/lib/redux/fetch_data_middleware.dart +++ b/lib/redux/fetch_data_middleware.dart @@ -5,13 +5,13 @@ 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 '../models/app_state.dart'; +import '../models/fire_notification.dart'; +import '../models/fire_notifications_persist.dart'; +import '../models/fires_api.dart'; +import '../models/your_location.dart'; +import '../models/your_location_persist.dart'; +import '../object_id_utils.dart'; import 'actions.dart'; // A middleware takes in 3 parameters: your Store, which you can use to @@ -157,23 +157,26 @@ void fetchDataMiddleware( // 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; - } + final List unsubscribedLocations = localLocations + .map( + (YourLocation location) => location.copyWith(subscribed: false)) + .toList(); 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; + final int index = unsubscribedLocations.indexWhere( + (YourLocation localLocation) => localLocation.id == subsLoc.id); + if (index >= 0) { + unsubscribedLocations[index] = + unsubscribedLocations[index].copyWith(subscribed: true); + } else { + unsubscribedLocations.add(subsLoc); + } } - store.dispatch(FetchYourLocationsSucceededAction(localLocations)); - persistYourLocations(localLocations); + store + .dispatch(FetchYourLocationsSucceededAction(unsubscribedLocations)); + persistYourLocations(unsubscribedLocations); - for (final YourLocation yl in localLocations) { + for (final YourLocation yl in unsubscribedLocations) { api .getFiresInLocation( state: store.state, @@ -181,8 +184,8 @@ void fetchDataMiddleware( lon: yl.lon, distance: yl.distance) .then((UpdateFireMapStatsAction value) { - yl.currentNumFires = value.numFires; - store.dispatch(UpdateYourLocationAction(yl)); + store.dispatch(UpdateYourLocationAction( + yl.copyWith(currentNumFires: value.numFires))); }); } @@ -243,8 +246,8 @@ void getFiresStatsInLocation(Store store, YourLocation loc) { distance: loc.distance) .then((UpdateFireMapStatsAction result) { store.dispatch(result); - loc.currentNumFires = result.numFires; - store.dispatch(UpdateYourLocationAction(loc)); + store.dispatch(UpdateYourLocationAction( + loc.copyWith(currentNumFires: result.numFires))); }); } diff --git a/lib/redux/fire_map_actions.dart b/lib/redux/fire_map_actions.dart index 49590de..8a785ab 100644 --- a/lib/redux/fire_map_actions.dart +++ b/lib/redux/fire_map_actions.dart @@ -1,6 +1,6 @@ -import '../models/fireMapState.dart'; -import '../models/fireNotification.dart'; -import '../models/yourLocation.dart'; +import '../models/fire_map_state.dart'; +import '../models/fire_notification.dart'; +import '../models/your_location.dart'; abstract class FiresMapActions {} diff --git a/lib/redux/fire_map_reducer.dart b/lib/redux/fire_map_reducer.dart index cc478f3..22185a5 100644 --- a/lib/redux/fire_map_reducer.dart +++ b/lib/redux/fire_map_reducer.dart @@ -1,8 +1,8 @@ import 'package:objectid/objectid.dart'; import 'package:redux/redux.dart'; -import '../models/fireMapState.dart'; -import '../models/yourLocation.dart'; +import '../models/fire_map_state.dart'; +import '../models/your_location.dart'; import 'actions.dart'; final Reducer fireMapReducer = diff --git a/lib/redux/fire_notification_actions.dart b/lib/redux/fire_notification_actions.dart index 8c894e4..71f0020 100644 --- a/lib/redux/fire_notification_actions.dart +++ b/lib/redux/fire_notification_actions.dart @@ -1,5 +1,5 @@ -import '../models/falsePositiveTypes.dart'; -import '../models/fireNotification.dart'; +import '../models/false_positive_types.dart'; +import '../models/fire_notification.dart'; abstract class FireNotificationActions {} diff --git a/lib/redux/fire_notification_reducer.dart b/lib/redux/fire_notification_reducer.dart index 402d980..1d5d4c1 100644 --- a/lib/redux/fire_notification_reducer.dart +++ b/lib/redux/fire_notification_reducer.dart @@ -1,6 +1,6 @@ import 'package:redux/redux.dart'; -import '../models/fireNotification.dart'; +import '../models/fire_notification.dart'; import 'actions.dart'; final Reducer> fireNotificationReducer = diff --git a/lib/redux/reducers.dart b/lib/redux/reducers.dart index 9795820..8f6b99e 100644 --- a/lib/redux/reducers.dart +++ b/lib/redux/reducers.dart @@ -1,12 +1,12 @@ -import '../models/appState.dart'; -import 'appReducer.dart'; -import 'errorReducer.dart'; -import 'fireMapReducer.dart'; -import 'fireNotificationReducer.dart'; -import 'loadedReducer.dart'; -import 'loadingReducer.dart'; -import 'userReducer.dart'; -import 'yourLocationsReducer.dart'; +import '../models/app_state.dart'; +import 'app_reducer.dart'; +import 'error_reducer.dart'; +import 'fire_map_reducer.dart'; +import 'fire_notification_reducer.dart'; +import 'loaded_reducer.dart'; +import 'loading_reducer.dart'; +import 'user_reducer.dart'; +import 'your_locations_reducer.dart'; // We create the State reducer by combining many smaller reducers into one! AppState appStateReducer(AppState prevState, dynamic action) { diff --git a/lib/redux/your_location_actions.dart b/lib/redux/your_location_actions.dart index 7868e22..a24b8f8 100644 --- a/lib/redux/your_location_actions.dart +++ b/lib/redux/your_location_actions.dart @@ -1,6 +1,6 @@ import 'package:objectid/objectid.dart'; -import '../models/yourLocation.dart'; +import '../models/your_location.dart'; abstract class YourLocationActions {} diff --git a/lib/redux/your_locations_reducer.dart b/lib/redux/your_locations_reducer.dart index 7ae0bdc..c1e6515 100644 --- a/lib/redux/your_locations_reducer.dart +++ b/lib/redux/your_locations_reducer.dart @@ -1,6 +1,6 @@ import 'package:redux/redux.dart'; -import '../models/yourLocation.dart'; +import '../models/your_location.dart'; import 'actions.dart'; final Reducer> yourLocationsReducer = diff --git a/lib/widgets/app_intro_page.dart b/lib/widgets/app_intro_page.dart index dd10abd..c573251 100644 --- a/lib/widgets/app_intro_page.dart +++ b/lib/widgets/app_intro_page.dart @@ -17,9 +17,11 @@ typedef OnIntroFinish = void Function(BuildContext context); typedef ListItems = List Function(BuildContext context); abstract class AppIntroPage extends StatelessWidget { - const AppIntroPage( - {Key? key, required this.items, required this.onIntroFinish}) - : super(key: key); + const AppIntroPage({ + super.key, + required this.items, + required this.onIntroFinish, + }); static const String routeName = '/appintro'; final ListItems items; @@ -43,7 +45,7 @@ class _AppIntroPageSelector extends StatelessWidget { final OnIntroFinish onFinish; void _handleArrowButtonPress(BuildContext context, int delta) { - final TabController controller = DefaultTabController.of(context)!; + final TabController controller = DefaultTabController.of(context); if (!controller.indexIsChanging) controller.animateTo( (controller.index + delta).clamp(0, items(context).length - 1)); @@ -51,7 +53,7 @@ class _AppIntroPageSelector extends StatelessWidget { @override Widget build(BuildContext context) { - final TabController controller = DefaultTabController.of(context)!; + final TabController controller = DefaultTabController.of(context); final ThemeData theme = Theme.of(context); final Color color = theme.colorScheme.secondary; diff --git a/lib/widgets/material_app_with_intro.dart b/lib/widgets/material_app_with_intro.dart index a7db9f2..5362e8b 100644 --- a/lib/widgets/material_app_with_intro.dart +++ b/lib/widgets/material_app_with_intro.dart @@ -46,17 +46,15 @@ class MaterialAppWithIntroHome extends StatefulWidget { final String prefsKey; @override - _MaterialAppWithIntroState createState() => - _MaterialAppWithIntroState(introWidget, continueWidget, prefsKey); + _MaterialAppWithIntroState createState() => _MaterialAppWithIntroState(); } class _MaterialAppWithIntroState extends State { - _MaterialAppWithIntroState( - this.introWidget, this.continueWidget, this.prefsKey); + _MaterialAppWithIntroState(); - final WidgetBuilder introWidget; - final WidgetBuilder continueWidget; - final String prefsKey; + late final WidgetBuilder introWidget = widget.introWidget; + late final WidgetBuilder continueWidget = widget.continueWidget; + late final String prefsKey = widget.prefsKey; @override void initState() { diff --git a/test/file_test.dart b/test/file_test.dart index 17bd80a..d42e4f9 100644 --- a/test/file_test.dart +++ b/test/file_test.dart @@ -1,4 +1,4 @@ -import 'package:fires_flutter/fileUtils.dart'; +import 'package:fires_flutter/file_utils.dart'; import 'package:test/test.dart'; void main() { @@ -23,8 +23,8 @@ void main() { }); test('test privacy English md page', () async { - final String answer = await getFileNameOfLang(dir: 'assets/pages', fileName: 'privacy', ext: 'md', lang: 'en'); + final String answer = await getFileNameOfLang( + dir: 'assets/pages', fileName: 'privacy', ext: 'md', lang: 'en'); expect(answer, 'assets/pages/privacy-en.md'); }); - }