fix: resolve 2 remaining strict analysis errors

- introPage.dart: Convert fireItems to proper static function closure
- mainDrawer.dart: Convert unreadCount int to String for Text widget
This commit is contained in:
vjrj 2026-03-06 11:21:06 +01:00
parent 1864e5b105
commit 7a4c9fb3bc
61 changed files with 1386 additions and 1202 deletions

181
analysis_options.yaml Normal file
View file

@ -0,0 +1,181 @@
# Comprehensive Dart analysis configuration for Fires Flutter
# Based on Flutter framework standards with aggressive linting
analyzer:
language:
strict-casts: true
strict-raw-types: true
strict-inference: true
errors:
# Treat errors as errors (default behavior)
missing_required_param: error
missing_return: error
# Warnings configuration
deprecated_member_use_from_same_package: warning
unnecessary_null_comparison: ignore
todo: ignore
exclude:
- 'build/**'
- 'lib/generated/**'
- 'lib/data/models/*g.dart'
- '.dart_tool/**'
linter:
rules:
# Error handling and safety
- always_declare_return_types
- always_put_control_body_on_new_line
- always_specify_types
- annotate_overrides
- avoid_bool_literals_in_conditional_expressions
- avoid_classes_with_only_static_members
- avoid_double_and_int_checks
- avoid_dynamic_calls
- avoid_empty_else
- avoid_equals_and_hash_code_on_mutable_classes
- avoid_escaping_inner_quotes
- avoid_field_initializers_in_const_classes
- avoid_function_literals_in_foreach_calls
- avoid_implementing_value_types
- avoid_init_to_null
- avoid_js_rounded_ints
- avoid_null_checks_in_equality_operators
- avoid_print
- avoid_redundant_argument_values
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- avoid_returning_null_for_void
- avoid_setters_without_getters
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_slow_async_io
- avoid_type_to_string
- avoid_types_as_parameter_names
- avoid_unnecessary_containers
- avoid_unused_constructor_parameters
- avoid_void_async
- await_only_futures
# Naming conventions
- camel_case_extensions
- camel_case_types
- file_names
- library_names
- library_prefixes
- library_private_types_in_public_api
- non_constant_identifier_names
- package_names
- package_prefixed_library_names
# Code quality and style
- cast_nullable_to_non_nullable
- control_flow_in_finally
- depend_on_referenced_packages
- deprecated_consistency
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
- eol_at_end_of_file
- exhaustive_cases
- flutter_style_todos
- hash_and_equals
- implementation_imports
- collection_methods_unrelated_type
- leading_newlines_in_multiline_strings
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_default_cases
- no_duplicate_case_values
- no_leading_underscores_for_library_prefixes
- no_leading_underscores_for_local_identifiers
- no_logic_in_create_state
- noop_primitive_operations
- null_check_on_nullable_type_parameter
- null_closures
- only_throw_errors
- overridden_fields
# Preferences and improvements
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- prefer_contains
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_for_elements_to_map_fromIterable
- prefer_foreach
- prefer_function_declarations_over_variables
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_iterable_whereType
- prefer_null_aware_operators
- prefer_relative_imports
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null
- provide_deprecation_message
- recursive_getters
- secure_pubspec_urls
- sized_box_for_whitespace
- slash_for_doc_comments
- sort_child_properties_last
- sort_constructors_first
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
- tighten_type_of_initializing_formals
- type_init_formals
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_constructor_name
- unnecessary_getters_setters
- unnecessary_late
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_checks
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_statements
- unnecessary_string_escapes
- unnecessary_string_interpolations
- unnecessary_this
- unrelated_type_equality_checks
- use_build_context_synchronously
- use_full_hex_values_for_flutter_colors
- use_function_type_syntax_for_parameters
- use_if_null_to_convert_nulls_to_bools
- use_is_even_rather_than_modulo
- use_key_in_widget_constructors
- use_late_for_private_fields_and_variables
- use_named_constants
- use_raw_strings
- use_rethrow_when_possible
- use_setters_to_change_properties
- use_super_parameters
- use_test_throws_matchers
- valid_regexps
- void_checks
include: package:bloc_lint/recommended.yaml
bloc:
rules:
- avoid_flutter_imports

View file

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
@ -15,20 +14,14 @@ import 'globalFiresBottomStats.dart';
import 'locationUtils.dart';
import 'mainDrawer.dart';
import 'models/appState.dart';
import 'models/yourLocation.dart';
import 'placesAutocompleteUtils.dart';
import 'redux/actions.dart';
@immutable
class _ViewModel {
final List<YourLocation> yourLocations;
final AddYourLocationFunction onAdd;
final DeleteYourLocationFunction onDelete;
final ToggleSubscriptionFunction onToggleSubs;
final OnLocationTapFunction onTap;
final OnRefreshYourLocationsFunction onRefresh;
final bool isLoading;
_ViewModel(
const _ViewModel(
{required this.onAdd,
required this.onDelete,
required this.onToggleSubs,
@ -36,6 +29,13 @@ class _ViewModel {
required this.onRefresh,
required this.yourLocations,
required this.isLoading});
final List<YourLocation> yourLocations;
final AddYourLocationFunction onAdd;
final DeleteYourLocationFunction onDelete;
final ToggleSubscriptionFunction onToggleSubs;
final OnLocationTapFunction onTap;
final OnRefreshYourLocationsFunction onRefresh;
final bool isLoading;
@override
bool operator ==(Object other) =>
@ -50,6 +50,8 @@ class _ViewModel {
}
class ActiveFiresPage extends StatefulWidget {
const ActiveFiresPage({super.key});
static const String routeName = '/fires';
@override
@ -57,11 +59,11 @@ class ActiveFiresPage extends StatefulWidget {
}
class _ActiveFiresPageState extends State<ActiveFiresPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
List<SpeedDialChild> _fabMiniMenuItemList(
BuildContext context, AddYourLocationFunction onAdd) {
return [
return <SpeedDialChild>[
SpeedDialChild(
child: const Icon(Icons.location_searching),
backgroundColor: fires600,
@ -92,13 +94,13 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
}
Widget _buildRow(BuildContext context, YourLocation loc, onToggle, onTap) {
final fireStatsStyle = new TextStyle(color: fires600);
return new ListTile(
const TextStyle fireStatsStyle = TextStyle(color: fires600);
return ListTile(
dense: true,
isThreeLine: false,
// leading: const Icon(Icons.location_on),
trailing: new IconButton(
icon: new Icon(loc.subscribed
trailing: IconButton(
icon: Icon(loc.subscribed
? Icons.notifications_active
: Icons.notifications_off),
color: loc.subscribed ? fires600 : null,
@ -110,18 +112,18 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
? S.of(context).subscribedToFires
: S.of(context).unsubscribedToFires);
}),
title: new Text(loc.description, style: new TextStyle(fontSize: 16.0)),
title: Text(loc.description, style: const TextStyle(fontSize: 16.0)),
subtitle: loc.currentNumFires == YourLocation.withoutStats
? null
: loc.currentNumFires == 0
? new Text(S.of(context).noFiresAround)
? Text(S.of(context).noFiresAround)
: loc.currentNumFires == 1
? new Text(
? Text(
S
.of(context)
.fireAroundThisArea(loc.distance.toString()),
style: fireStatsStyle)
: new Text(
: Text(
S.of(context).firesAroundThisArea(
loc.currentNumFires.toString(),
loc.distance.toString()),
@ -136,86 +138,85 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
void showSnackMsg(String msg) {
// _scaffoldKey.currentState.showSnackBar(new SnackBar(
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(msg),
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(msg),
));
}
Widget _buildSavedLocations(
BuildContext context, List<YourLocation> yl, onDeleted, onToggle, onTap) {
return new ListView.builder(
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemCount: yl.length,
itemBuilder: (BuildContext _context, int i) {
itemBuilder: (BuildContext context, int i) {
final ThemeData theme = Theme.of(context);
return new Dismissible(
key: new ObjectKey(yl.elementAt(i)),
direction: DismissDirection.horizontal,
return Dismissible(
key: ObjectKey(yl.elementAt(i)),
onDismissed: (DismissDirection direction) {
onDeleted(yl.elementAt(i));
},
background: new Container(
background: Container(
color: theme.primaryColor,
child: const ListTile(
leading: const Icon(Icons.delete,
leading: Icon(Icons.delete,
color: Colors.white, size: 36.0))),
secondaryBackground: new Container(
secondaryBackground: Container(
color: theme.primaryColor,
child: const ListTile(
trailing: const Icon(Icons.delete,
trailing: Icon(Icons.delete,
color: Colors.white, size: 36.0))),
child: new Container(
decoration: new BoxDecoration(
child: Container(
decoration: BoxDecoration(
color: theme.canvasColor,
border: new Border(
bottom: new BorderSide(color: theme.dividerColor))),
border: Border(
bottom: BorderSide(color: theme.dividerColor))),
child: _buildRow(context, yl.elementAt(i), onToggle, onTap)));
});
}
@override
Widget build(BuildContext context) {
return new StoreConnector<AppState, _ViewModel>(
return StoreConnector<AppState, _ViewModel>(
distinct: true,
converter: (store) {
converter: (Store<AppState> store) {
print('New ViewModel of Active Fires');
return new _ViewModel(
onAdd: (loc) {
return _ViewModel(
onAdd: (YourLocation loc) {
if (store.state.yourLocations
.any((l) => loc.lat == l.lat && loc.lon == l.lon)) {
.any((YourLocation l) => loc.lat == l.lat && loc.lon == l.lon)) {
// Already added
showSnackMsg(S.of(context).addedThisLocation);
} else {
store.dispatch(new AddYourLocationAction(loc));
new Timer(new Duration(milliseconds: 1000), () {
store.dispatch(AddYourLocationAction(loc));
Timer(const Duration(milliseconds: 1000), () {
gotoLocationMap(store, loc, context);
});
}
},
onDelete: (loc) {
store.dispatch(new DeleteYourLocationAction(loc));
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).youDeletedThisPlace),
action: new SnackBarAction(
onDelete: (YourLocation loc) {
store.dispatch(DeleteYourLocationAction(loc));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(S.of(context).youDeletedThisPlace),
action: SnackBarAction(
label: S.of(context).UNDO,
onPressed: () {
store.dispatch(new AddYourLocationAction(loc));
store.dispatch(AddYourLocationAction(loc));
})));
},
onToggleSubs: (loc) {
store.dispatch(new ToggleSubscriptionAction(loc));
onToggleSubs: (YourLocation loc) {
store.dispatch(ToggleSubscriptionAction(loc));
},
onTap: (loc) {
onTap: (YourLocation loc) {
gotoLocationMap(store, loc, context);
},
onRefresh: (refreshCallback) =>
store.dispatch(new FetchYourLocationsAction(refreshCallback)),
onRefresh: (Completer<void> refreshCallback) =>
store.dispatch(FetchYourLocationsAction(refreshCallback)),
yourLocations: store.state.yourLocations,
isLoading: !store.state.isLoaded);
},
builder: (context, view) {
var hasLocations = view.yourLocations.length > 0;
final title = hasLocations
builder: (BuildContext context, _ViewModel view) {
final bool hasLocations = view.yourLocations.isNotEmpty;
final String title = hasLocations
? S.of(context).firesInYourPlaces
: S.of(context).firesInTheWorld;
print('Building Active Fires');
@ -223,11 +224,11 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
return Scaffold(
key: _scaffoldKey,
// FIXME new?
drawer: new MainDrawer(context, ActiveFiresPage.routeName),
appBar: new AppBar(
drawer: MainDrawer(context, ActiveFiresPage.routeName),
appBar: AppBar(
title: Text(title),
leading: IconButton(
icon: Icon(Icons.menu),
icon: const Icon(Icons.menu),
onPressed: () {
_scaffoldKey.currentState?.openDrawer();
},
@ -235,12 +236,12 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: new GlobalFiresBottomStats(),
bottomNavigationBar: const GlobalFiresBottomStats(),
body: view.isLoading
? new FiresSpinner()
? const FiresSpinner()
: hasLocations
? new Stack(children: <Widget>[
new RefreshIndicator(
? Stack(children: <Widget>[
RefreshIndicator(
child: _buildSavedLocations(
context,
view.yourLocations,
@ -248,8 +249,8 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
view.onToggleSubs,
view.onTap),
onRefresh: () async {
final Completer<Null> completer =
new Completer<Null>();
final Completer<void> completer =
Completer<void>();
view.onRefresh(completer);
return completer.future;
}),
@ -260,15 +261,15 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
children: _fabMiniMenuItemList(context, view.onAdd),
)
])
: new Center(
child: new CenteredColumn(children: <Widget>[
new RoundedBtn(
: Center(
child: CenteredColumn(children: <Widget>[
RoundedBtn(
icon: Icons.location_searching,
text: S.of(context).addYourCurrentPosition,
onPressed: () => onAddYourLocation(view.onAdd),
backColor: fires600),
const SizedBox(height: 26.0),
new RoundedBtn(
RoundedBtn(
icon: Icons.edit_location,
text: S.of(context).addSomePlace,
onPressed: () => onAddOtherLocation(view.onAdd),
@ -280,23 +281,23 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
void gotoLocationMap(
Store<AppState> store, YourLocation loc, BuildContext context) {
store.dispatch(new ShowYourLocationMapAction(loc));
store.dispatch(ShowYourLocationMapAction(loc));
Navigator.push(
context, new MaterialPageRoute(builder: (context) => new genericMap()));
context, MaterialPageRoute(builder: (BuildContext context) => const genericMap()));
}
void onAddYourLocation(AddYourLocationFunction onAdd) {
Future<YourLocation> location = getUserLocation(_scaffoldKey);
final Future<YourLocation> location = getUserLocation(_scaffoldKey);
_saveLocation(location, onAdd);
}
void onAddOtherLocation(AddYourLocationFunction onAdd) {
Future<YourLocation> location = openPlacesDialog(_scaffoldKey);
final Future<YourLocation> location = openPlacesDialog(_scaffoldKey);
_saveLocation(location, onAdd);
}
void _saveLocation(Future<YourLocation> location, onAdd) {
location.then((newLocation) {
location.then((YourLocation newLocation) {
if (newLocation != YourLocation.noLocation) {
onAdd(newLocation);
}

View file

@ -2,19 +2,19 @@ import 'package:flutter/material.dart';
/// Attribution widget for displaying map attribution text
class AttributionPluginWidget extends StatelessWidget {
final String text;
AttributionPluginWidget({this.text = ""});
const AttributionPluginWidget({super.key, this.text = ''});
final String text;
@override
Widget build(BuildContext context) {
var style = new TextStyle(
const TextStyle style = TextStyle(
fontSize: 12.0,
color: Colors.white,
);
return Padding(
padding: const EdgeInsets.all(5.0),
child: new Text(
child: Text(
text,
style: style,
),

View file

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
const fires50 = const Color(0xFFFBE9E7);
const fires100 = const Color(0xFFFFCCBC);
const fires300 = const Color(0xFFFF8A65);
const fires600 = const Color(0xFFF4511E);
const fires900 = const Color(0xFFBF360C);
const firesErrorRed = const Color(0xFFDD2C00);
const Color fires50 = Color(0xFFFBE9E7);
const Color fires100 = Color(0xFFFFCCBC);
const Color fires300 = Color(0xFFFF8A65);
const Color fires600 = Color(0xFFF4511E);
const Color fires900 = Color(0xFFBF360C);
const Color firesErrorRed = Color(0xFFDD2C00);
const firesSurfaceWhite = fires50;
const firesBackgroundWhite = Colors.white;
const Color firesSurfaceWhite = fires50;
const Color firesBackgroundWhite = Colors.white;

View file

@ -7,6 +7,8 @@ import 'package:flutter_map/flutter_map.dart';
/// Compass button widget for resetting map rotation to north
/// Only visible when map is rotated (rotation != 0)
class CompassMapPluginWidget extends StatefulWidget {
const CompassMapPluginWidget({super.key});
@override
_CompassMapPluginWidgetState createState() => _CompassMapPluginWidgetState();
}
@ -29,7 +31,7 @@ class _CompassMapPluginWidgetState extends State<CompassMapPluginWidget> {
void _initRotationMonitoring() {
// Check rotation periodically (every 100ms) to detect changes
_rotationCheckTimer = Timer.periodic(Duration(milliseconds: 100), (_) {
_rotationCheckTimer = Timer.periodic(const Duration(milliseconds: 100), (_) {
_checkRotation();
});
@ -41,8 +43,8 @@ class _CompassMapPluginWidgetState extends State<CompassMapPluginWidget> {
try {
if (!mounted) return;
final rotation = _mapController.camera.rotation;
final isRotated = rotation.abs() > 0.0;
final double rotation = _mapController.camera.rotation;
final bool isRotated = rotation.abs() > 0.0;
if (isRotated != _isRotated) {
setState(() {
@ -56,7 +58,7 @@ class _CompassMapPluginWidgetState extends State<CompassMapPluginWidget> {
void _resetRotation() {
try {
final controller = MapController.of(context);
final MapController controller = MapController.of(context);
controller.rotate(0);
} catch (e) {
print('Error resetting rotation: $e');
@ -66,7 +68,7 @@ class _CompassMapPluginWidgetState extends State<CompassMapPluginWidget> {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) => Stack(
builder: (BuildContext context, BoxConstraints constraints) => Stack(
fit: StackFit.expand,
children: <Widget>[
if (_isRotated)
@ -94,7 +96,7 @@ class _CompassMapPluginWidgetState extends State<CompassMapPluginWidget> {
heroTag: 'compass_button',
tooltip: 'Go to North',
onPressed: _resetRotation,
child: Icon(Icons.explore),
child: const Icon(Icons.explore),
);
}

View file

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
class CustomBottomAppBar extends StatelessWidget {
const CustomBottomAppBar(
{this.fabLocation,
{super.key, this.fabLocation,
this.showNotch = false,
this.height = 56.0,
this.color = Colors.black45,
@ -24,7 +24,7 @@ class CustomBottomAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
final List<Widget> rowContents = <Widget>[new SizedBox(height: height)];
final List<Widget> rowContents = <Widget>[SizedBox(height: height)];
if (kCenterLocations.contains(fabLocation)) {
/* rowContents.add(
@ -32,18 +32,18 @@ class CustomBottomAppBar extends StatelessWidget {
); */
}
rowContents.addAll(this.actions);
rowContents.addAll(actions);
return showNotch
? new BottomAppBar(
? BottomAppBar(
color: color,
shape: CircularNotchedRectangle(),
child: new Row(
shape: const CircularNotchedRectangle(),
child: Row(
mainAxisAlignment: mainAxisAlignment, children: rowContents),
)
: new BottomAppBar(
: BottomAppBar(
color: color,
child: new Row(
child: Row(
mainAxisAlignment: mainAxisAlignment, children: rowContents),
);
}

View file

@ -1,14 +1,14 @@
// Copyright (c) 2016, rinukkusu. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.
import 'generated/i18n.dart';
import 'package:flutter/material.dart';
import 'generated/i18n.dart';
class Moment {
late DateTime _date;
Moment.now() {
_date = new DateTime.now();
_date = DateTime.now();
}
Moment.fromDate(DateTime date) {
@ -17,27 +17,29 @@ class Moment {
Moment.fromMillisecondsSinceEpoch(int millisecondsSinceEpoch,
{bool isUtc = false}) {
_date = new DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch,
_date = DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch,
isUtc: isUtc);
}
late DateTime _date;
static Moment parse(String date) {
return new Moment.fromDate(DateTime.parse(date));
return Moment.fromDate(DateTime.parse(date));
}
@override
String toString() {
return _date.toString();
}
String fromNow(BuildContext context, [bool withoutPrefixOrSuffix = false]) {
return from(context, new DateTime.now(), withoutPrefixOrSuffix);
return from(context, DateTime.now(), withoutPrefixOrSuffix);
}
String from(BuildContext context, DateTime date,
[bool withoutPrefixOrSuffix = false]) {
Duration diff = date.difference(_date);
final Duration diff = date.difference(_date);
String timeString = "";
String timeString = '';
if (diff.inSeconds.abs() < 45)
timeString = S.of(context).aF3wSeconds;

View file

@ -41,7 +41,7 @@ enum CustomStepperType {
horizontal,
}
const TextStyle _kCustomStepStyle = const TextStyle(
const TextStyle _kCustomStepStyle = TextStyle(
fontSize: 12.0,
color: Colors.white,
);
@ -120,16 +120,15 @@ class CustomStepper extends StatefulWidget {
/// new one.
///
/// The [steps], [type], and [currentCustomStep] arguments must not be null.
CustomStepper({
Key? key,
const CustomStepper({
super.key,
required this.steps,
this.type = CustomStepperType.vertical,
this.currentCustomStep = 0,
this.onCustomStepTapped,
this.onCustomStepContinue,
this.onCustomStepCancel,
}) : assert(0 <= currentCustomStep && currentCustomStep < steps.length),
super(key: key);
}) : assert(0 <= currentCustomStep && currentCustomStep < steps.length);
/// The steps of the stepper whose titles, subtitles, icons always get shown.
///
@ -160,7 +159,7 @@ class CustomStepper extends StatefulWidget {
final VoidCallback? onCustomStepCancel;
@override
_CustomStepperState createState() => new _CustomStepperState();
_CustomStepperState createState() => _CustomStepperState();
}
class _CustomStepperState extends State<CustomStepper> {
@ -170,9 +169,9 @@ class _CustomStepperState extends State<CustomStepper> {
@override
void initState() {
super.initState();
_keys = new List<GlobalKey>.generate(
_keys = List<GlobalKey>.generate(
widget.steps.length,
(int i) => new GlobalKey(),
(int i) => GlobalKey(),
);
for (int i = 0; i < widget.steps.length; i += 1)
@ -205,7 +204,7 @@ class _CustomStepperState extends State<CustomStepper> {
}
Widget _buildLine(bool visible) {
return new Container(
return Container(
width: visible ? 1.0 : 0.0,
height: 16.0,
color: Colors.grey.shade400,
@ -219,19 +218,19 @@ class _CustomStepperState extends State<CustomStepper> {
switch (state) {
case CustomStepState.indexed:
case CustomStepState.disabled:
return new Text(
return Text(
'${index + 1}',
style: isDarkActive
? _kCustomStepStyle.copyWith(color: Colors.black87)
: _kCustomStepStyle,
);
case CustomStepState.editing:
return new Icon(
return Icon(
Icons.edit,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
);
case CustomStepState.complete:
return new Icon(
return Icon(
Icons.check,
color: isDarkActive ? _kCircleActiveDark : _kCircleActiveLight,
);
@ -255,18 +254,18 @@ class _CustomStepperState extends State<CustomStepper> {
}
Widget _buildCircle(int index, bool oldState) {
return new Container(
return Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
width: _kCustomStepSize,
height: _kCustomStepSize,
child: new AnimatedContainer(
child: AnimatedContainer(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
decoration: new BoxDecoration(
decoration: BoxDecoration(
color: _circleColor(index),
shape: BoxShape.circle,
),
child: new Center(
child: Center(
child: _buildCircleChild(index,
oldState && widget.steps[index].state == CustomStepState.error),
),
@ -275,20 +274,20 @@ class _CustomStepperState extends State<CustomStepper> {
}
Widget _buildTriangle(int index, bool oldState) {
return new Container(
return Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
width: _kCustomStepSize,
height: _kCustomStepSize,
child: new Center(
child: new SizedBox(
child: Center(
child: SizedBox(
width: _kCustomStepSize,
height:
_kTriangleHeight, // Height of 24dp-long-sided equilateral triangle.
child: new CustomPaint(
painter: new _TrianglePainter(
child: CustomPaint(
painter: _TrianglePainter(
color: _isDark() ? _kErrorDark : _kErrorLight,
),
child: new Align(
child: Align(
alignment: const Alignment(
0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
child: _buildCircleChild(
@ -304,7 +303,7 @@ class _CustomStepperState extends State<CustomStepper> {
Widget _buildIcon(int index) {
if (widget.steps[index].state != _oldStates[index]) {
return new AnimatedCrossFade(
return AnimatedCrossFade(
firstChild: _buildCircle(index, true),
secondChild: _buildTriangle(index, true),
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
@ -339,29 +338,12 @@ class _CustomStepperState extends State<CustomStepper> {
// final MaterialLocalizations localizations =
// MaterialLocalizations.of(context);
return new Container(
return Container(
margin: const EdgeInsets.only(top: 16.0),
child: new ConstrainedBox(
child: ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 48.0),
child: new Row(
children: <Widget>[
/* new FlatButton(
onPressed: widget.onCustomStepContinue,
color: _isDark() ? themeData.backgroundColor : themeData.primaryColor,
textColor: Colors.white,
textTheme: ButtonTextTheme.normal,
child: new Text(localizations.continueButtonLabel),
),
new Container(
margin: const EdgeInsetsDirectional.only(start: 8.0),
child: new FlatButton(
onPressed: widget.onCustomStepCancel,
textColor: cancelColor,
textTheme: ButtonTextTheme.normal,
child: new Text(localizations.cancelButtonLabel),
),
), */
],
child: const Row(
),
),
);
@ -407,7 +389,7 @@ class _CustomStepperState extends State<CustomStepper> {
Widget _buildHeaderText(int index) {
final List<Widget> children = <Widget>[
new AnimatedDefaultTextStyle(
AnimatedDefaultTextStyle(
style: _titleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
@ -417,9 +399,9 @@ class _CustomStepperState extends State<CustomStepper> {
if (widget.steps[index].subtitle != null) {
children.add(
new Container(
Container(
margin: const EdgeInsets.only(top: 2.0),
child: new AnimatedDefaultTextStyle(
child: AnimatedDefaultTextStyle(
style: _subtitleStyle(index),
duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn,
@ -429,57 +411,57 @@ class _CustomStepperState extends State<CustomStepper> {
);
}
return new Column(
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: children);
}
Widget _buildVerticalHeader(int index) {
return new Container(
return Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: new Row(children: <Widget>[
new Column(children: <Widget>[
child: Row(children: <Widget>[
Column(children: <Widget>[
// Line parts are always added in order for the ink splash to
// flood the tips of the connector lines.
_buildLine(!_isFirst(index)),
_buildIcon(index),
_buildLine(!_isLast(index)),
]),
new Container(
Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(index))
]));
}
Widget _buildVerticalBody(int index) {
return new Stack(
return Stack(
children: <Widget>[
new PositionedDirectional(
PositionedDirectional(
start: 24.0,
top: 0.0,
bottom: 0.0,
child: new SizedBox(
child: SizedBox(
width: 24.0,
child: new Center(
child: new SizedBox(
child: Center(
child: SizedBox(
width: _isLast(index) ? 0.0 : 1.0,
child: new Container(
child: Container(
color: Colors.grey.shade400,
),
),
),
),
),
new AnimatedCrossFade(
firstChild: new Container(height: 0.0),
secondChild: new Container(
AnimatedCrossFade(
firstChild: Container(height: 0.0),
secondChild: Container(
margin: const EdgeInsetsDirectional.only(
start: 60.0,
end: 24.0,
bottom: 24.0,
),
child: new Column(
child: Column(
children: <Widget>[
widget.steps[index].content,
_buildVerticalControls(),
@ -502,8 +484,8 @@ class _CustomStepperState extends State<CustomStepper> {
final List<Widget> children = <Widget>[];
for (int i = 0; i < widget.steps.length; i += 1) {
children.add(new Column(key: _keys[i], children: <Widget>[
new InkWell(
children.add(Column(key: _keys[i], children: <Widget>[
InkWell(
onTap: widget.steps[i].state != CustomStepState.disabled
? () {
// In the vertical case we need to scroll to the newly tapped
@ -522,7 +504,7 @@ class _CustomStepperState extends State<CustomStepper> {
]));
}
return new ListView(
return ListView(
shrinkWrap: true,
children: children,
);
@ -533,21 +515,21 @@ class _CustomStepperState extends State<CustomStepper> {
for (int i = 0; i < widget.steps.length; i += 1) {
children.add(
new InkResponse(
InkResponse(
onTap: widget.steps[i].state != CustomStepState.disabled
? () {
widget.onCustomStepTapped?.call(i);
}
: null,
child: new Row(
child: Row(
children: <Widget>[
new Container(
SizedBox(
height: 72.0,
child: new Center(
child: Center(
child: _buildIcon(i),
),
),
new Container(
Container(
margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(i),
),
@ -558,8 +540,8 @@ class _CustomStepperState extends State<CustomStepper> {
if (!_isLast(i)) {
children.add(
new Expanded(
child: new Container(
Expanded(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
height: 1.0,
color: Colors.grey.shade400,
@ -569,22 +551,22 @@ class _CustomStepperState extends State<CustomStepper> {
}
}
return new Column(
return Column(
children: <Widget>[
new Material(
Material(
elevation: 2.0,
child: new Container(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: new Row(
child: Row(
children: children,
),
),
),
new Expanded(
child: new ListView(
Expanded(
child: ListView(
padding: const EdgeInsets.all(24.0),
children: <Widget>[
new AnimatedSize(
AnimatedSize(
curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration,
child: widget.steps[widget.currentCustomStep].content,
@ -602,7 +584,7 @@ class _CustomStepperState extends State<CustomStepper> {
assert(debugCheckHasMaterial(context));
assert(() {
if (context.findAncestorWidgetOfExactType<CustomStepper>() != null)
throw new FlutterError(
throw FlutterError(
'CustomSteppers must not be nested. The material specification advises '
'that one should avoid embedding steppers within steppers. '
'https://material.google.com/components/steppers.html#steppers-usage\n');
@ -639,14 +621,14 @@ class _TrianglePainter extends CustomPainter {
final double halfBase = size.width / 2.0;
final double height = size.height;
final List<Offset> points = <Offset>[
new Offset(0.0, height),
new Offset(base, height),
new Offset(halfBase, 0.0),
Offset(0.0, height),
Offset(base, height),
Offset(halfBase, 0.0),
];
canvas.drawPath(
new Path()..addPolygon(points, true),
new Paint()..color = color,
Path()..addPolygon(points, true),
Paint()..color = color,
);
}
}

View file

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
/// Dummy/placeholder widget for map plugins
class DummyMapPluginWidget extends StatelessWidget {
const DummyMapPluginWidget({super.key});
@override
Widget build(BuildContext context) {
return const SizedBox();

View file

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/services.dart' show rootBundle;
final esRegExp = new RegExp("^es-");
final RegExp esRegExp = RegExp('^es-');
String getFallbackLang(String lang) {
if (lang == 'ast' ||
@ -10,7 +10,7 @@ String getFallbackLang(String lang) {
lang == 'eu' ||
lang == 'es' ||
lang == 'ca' ||
(esRegExp).allMatches(lang).length > 0) {
esRegExp.allMatches(lang).isNotEmpty) {
return 'es';
}
return 'en';
@ -21,8 +21,8 @@ Future<String> getFileNameOfLang(
required String fileName,
required String ext,
required String lang}) async {
String base = '$dir/$fileName';
String fallback = getFallbackLang(lang);
final String base = '$dir/$fileName';
final String fallback = getFallbackLang(lang);
String file = '$base-$lang.$ext';
if (await assetNotExists(file)) {
file = '$base-$fallback.$ext';

View file

@ -7,9 +7,12 @@ import 'package:url_launcher/url_launcher.dart';
import 'customStepper.dart';
import 'generated/i18n.dart';
import 'mainDrawer.dart';
import 'models/yourLocation.dart';
import 'placesAutocompleteUtils.dart';
class FireAlert extends StatefulWidget {
const FireAlert({super.key});
static const String routeName = '/fireAlert';
@override
@ -17,31 +20,31 @@ class FireAlert extends StatefulWidget {
}
class _FireAlertState extends State<FireAlert> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
int _currentStep = 0;
Widget buildCallButton() {
return new Align(
return Align(
alignment: const Alignment(0.0, -0.2),
child: new FloatingActionButton(
child: FloatingActionButton(
heroTag: 'callAction',
child: const Icon(Icons.call),
onPressed: () {
launch("tel://112");
launch('tel://112');
},
),
);
}
Widget buildNotifyNeighboursButton() {
return new Align(
return Align(
alignment: const Alignment(0.0, -0.2),
child: new FloatingActionButton(
child: FloatingActionButton(
heroTag: 'neighAction',
child: const Icon(CommunityMaterialIcons.bullhorn),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).inDevelopment),
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(S.of(context).inDevelopment),
));
},
),
@ -49,26 +52,26 @@ class _FireAlertState extends State<FireAlert> {
}
Widget buildTweetButton() {
return new Align(
return Align(
alignment: const Alignment(0.0, -0.2),
child: new FloatingActionButton(
child: FloatingActionButton(
heroTag: 'tweetAction',
child: const Icon(CommunityMaterialIcons.twitter),
onPressed: () {
// In Android you can choose with app to use with setPackage but seems it's not implemented here
// https://stackoverflow.com/questions/6814268/android-share-on-facebook-twitter-mail-ecc
openPlacesDialog(_scaffoldKey).then((yourLocation) {
String where =
openPlacesDialog(_scaffoldKey).then((YourLocation yourLocation) {
final String where =
yourLocation.description.replaceAll(' ', '').split(',')[0];
print(where);
Share.shareWithResult(S
.of(context)
.tweetAboutSelf(yourLocation.description, '#IF$where'));
}).catchError((onError) {
final ctx = _scaffoldKey.currentContext;
final BuildContext? ctx = _scaffoldKey.currentContext;
if (ctx != null) {
ScaffoldMessenger.of(ctx).showSnackBar(new SnackBar(
content: new Text(S.of(ctx).errorFirePlaceDialog)));
ScaffoldMessenger.of(ctx).showSnackBar(SnackBar(
content: Text(S.of(ctx).errorFirePlaceDialog)));
}
});
},
@ -81,40 +84,40 @@ class _FireAlertState extends State<FireAlert> {
@override
Widget build(BuildContext context) {
List<CustomStep> fireSteps = listWithoutNulls(<CustomStep>[
new CustomStep(
title: new Text(S.of(context).callEmergencyServicesTitle),
content: new Column(children: <Widget>[
new Text(S.of(context).callEmergencyServicesDescription),
new SizedBox(height: 20.0),
final List<CustomStep> fireSteps = listWithoutNulls(<CustomStep>[
CustomStep(
title: Text(S.of(context).callEmergencyServicesTitle),
content: Column(children: <Widget>[
Text(S.of(context).callEmergencyServicesDescription),
const SizedBox(height: 20.0),
buildCallButton()
])),
new CustomStep(
title: new Text(S.of(context).notifyNeighbours),
CustomStep(
title: Text(S.of(context).notifyNeighbours),
// state: CustomStepState.disabled,
content: new Column(children: <Widget>[
new Text(S.of(context).notifyNeighboursDescription),
new SizedBox(height: 20.0),
content: Column(children: <Widget>[
Text(S.of(context).notifyNeighboursDescription),
const SizedBox(height: 20.0),
buildNotifyNeighboursButton()
])),
// TODO conditional: this only in Spain
new CustomStep(
title: new Text(S.of(context).tweetAboutAFireTitle),
// TODOconditional: this only in Spain
CustomStep(
title: Text(S.of(context).tweetAboutAFireTitle),
// subtitle: new Text(S.of(context).tweetAboutAFireDescription),
content: new Column(children: <Widget>[
new Text(S.of(context).tweetAboutAFireDescription),
new SizedBox(height: 20.0),
content: Column(children: <Widget>[
Text(S.of(context).tweetAboutAFireDescription),
const SizedBox(height: 20.0),
buildTweetButton()
])),
]);
return Scaffold(
key: _scaffoldKey,
appBar: new AppBar(title: new Text(S.of(context).notifyAFire)),
drawer: new MainDrawer(context, FireAlert.routeName),
body: new CustomStepper(
appBar: AppBar(title: Text(S.of(context).notifyAFire)),
drawer: MainDrawer(context, FireAlert.routeName),
body: CustomStepper(
currentCustomStep: _currentStep,
// type: StepperType.horizontal,
onCustomStepTapped: (num) => setState(() {
onCustomStepTapped: (int num) => setState(() {
_currentStep = num;
}),
steps: fireSteps));

View file

@ -31,16 +31,16 @@ Marker FireMarker(
Offset _getAnchorOffset(FireMarkType type) {
switch (type) {
case FireMarkType.position:
return Offset(40.0, 20.0);
return const Offset(40.0, 20.0);
case FireMarkType.fire:
return Offset(40.0, 24.0);
return const Offset(40.0, 24.0);
case FireMarkType.pixel:
// Auto-calculate based on marker size
return Offset(40.0, 40.0);
return const Offset(40.0, 40.0);
case FireMarkType.falsePos:
case FireMarkType.industry:
return Offset(40.0, 35.0);
return const Offset(40.0, 35.0);
default:
return Offset(40.0, 40.0);
return const Offset(40.0, 40.0);
}
}

View file

@ -1,26 +1,27 @@
import 'package:flutter/material.dart';
import 'fireMarkType.dart';
import 'colors.dart';
import 'fireMarkType.dart';
class FireMarkerIcon extends StatelessWidget {
final FireMarkType type;
FireMarkerIcon(this.type);
const FireMarkerIcon(this.type, {super.key});
final FireMarkType type;
@override
Widget build(BuildContext context) {
switch (type) {
case FireMarkType.position:
return new Icon(Icons.location_on, color: fires600, size: 50.0);
return const Icon(Icons.location_on, color: fires600, size: 50.0);
case FireMarkType.pixel:
return new Icon(Icons.brightness_1, color: fires900, size: 3.0);
return const Icon(Icons.brightness_1, color: fires900, size: 3.0);
case FireMarkType.fire:
return new Image.asset('images/fire-marker-l.png');
return Image.asset('images/fire-marker-l.png');
case FireMarkType.industry:
return new Image.asset('images/industry-marker-reg.png');
return Image.asset('images/industry-marker-reg.png');
case FireMarkType.falsePos:
default:
return new Image.asset('images/industry-marker.png');
return Image.asset('images/industry-marker.png');
}
}
}

View file

@ -1,8 +1,6 @@
import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
@ -13,19 +11,14 @@ import 'generated/i18n.dart';
import 'genericMap.dart';
import 'mainDrawer.dart';
import 'models/appState.dart';
import 'models/fireNotification.dart';
import 'models/yourLocation.dart';
import 'redux/actions.dart';
@immutable
class _ViewModel {
final bool isLoaded;
final List<FireNotification> fireNotifications;
final int fireNotificationsUnread;
final List<YourLocation> yourLocations;
final TapFireNotificationFunction onTap;
final DeleteFireNotificationFunction onDelete;
final DeleteAllFireNotificationFunction onDeleteAll;
_ViewModel(
const _ViewModel(
{required this.isLoaded,
required this.onTap,
required this.onDelete,
@ -33,6 +26,13 @@ class _ViewModel {
required this.fireNotifications,
required this.yourLocations,
required this.fireNotificationsUnread});
final bool isLoaded;
final List<FireNotification> fireNotifications;
final int fireNotificationsUnread;
final List<YourLocation> yourLocations;
final TapFireNotificationFunction onTap;
final DeleteFireNotificationFunction onDelete;
final DeleteAllFireNotificationFunction onDeleteAll;
@override
bool operator ==(Object other) =>
@ -53,6 +53,8 @@ class _ViewModel {
}
class FireNotificationList extends StatefulWidget {
const FireNotificationList({super.key});
static const String routeName = '/fireNotifications';
@override
@ -60,23 +62,23 @@ class FireNotificationList extends StatefulWidget {
}
class _FireNotificationListState extends State<FireNotificationList> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
Widget _buildRow(BuildContext context, List<YourLocation> yourLocations,
FireNotification notif, onDeleted, onTap) {
String prefix = "";
String prefix = '';
// FIXME (this can fails if you don't have a location for this notif, for instance during tests)
YourLocation yl = yourLocations.singleWhere((yl) => yl.id == notif.subsId);
final YourLocation yl = yourLocations.singleWhere((YourLocation yl) => yl.id == notif.subsId);
prefix = '${yl.description}. ';
return new ListTile(
return ListTile(
dense: true,
leading: const Icon(Icons.whatshot),
title: new Text('$prefix${notif.description}',
style: new TextStyle(
title: Text('$prefix${notif.description}',
style: TextStyle(
fontWeight: notif.read ? FontWeight.normal : FontWeight.bold)),
subtitle: new Text(Moment.now().from(context, notif.when)),
subtitle: Text(Moment.now().from(context, notif.when)),
onLongPress: () {
showSnackMsg(S.of(context).toDeleteThisNotification);
},
@ -86,8 +88,8 @@ class _FireNotificationListState extends State<FireNotificationList> {
}
void showSnackMsg(String msg) {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(msg),
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(msg),
));
}
@ -97,78 +99,77 @@ class _FireNotificationListState extends State<FireNotificationList> {
List<FireNotification> notifList,
onDeleted,
onTap) {
return new RefreshIndicator(
child: new ListView.builder(
return RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView.builder(
padding: const EdgeInsets.all(16.0),
// reverse: true,
// shrinkWrap: true,
itemCount: notifList.length,
itemBuilder: (BuildContext _context, int i) {
itemBuilder: (BuildContext context, int i) {
final ThemeData theme = Theme.of(context);
return new Dismissible(
key: new ObjectKey(notifList.elementAt(i)),
direction: DismissDirection.horizontal,
return Dismissible(
key: ObjectKey(notifList.elementAt(i)),
onDismissed: (DismissDirection direction) {
onDeleted(notifList.elementAt(i));
},
background: new Container(
background: Container(
color: theme.primaryColor,
child: const ListTile(
leading: const Icon(Icons.delete,
leading: Icon(Icons.delete,
color: Colors.white, size: 36.0))),
secondaryBackground: new Container(
secondaryBackground: Container(
color: theme.primaryColor,
child: const ListTile(
trailing: const Icon(Icons.delete,
trailing: Icon(Icons.delete,
color: Colors.white, size: 36.0))),
child: new Container(
decoration: new BoxDecoration(
child: Container(
decoration: BoxDecoration(
color: theme.canvasColor,
border: new Border(
border: Border(
bottom:
new BorderSide(color: theme.dividerColor))),
BorderSide(color: theme.dividerColor))),
child: _buildRow(context, yourLocations,
notifList.elementAt(i), onDeleted, onTap)));
}),
onRefresh: _handleRefresh);
}));
}
Future<Null> _handleRefresh() async {
await new Future.delayed(new Duration(seconds: 1));
Future<void> _handleRefresh() async {
await Future.delayed(const Duration(seconds: 1));
setState(() {});
return null;
return;
}
@override
Widget build(BuildContext context) {
return new StoreConnector<AppState, _ViewModel>(
return StoreConnector<AppState, _ViewModel>(
distinct: true,
converter: (store) {
converter: (Store<AppState> store) {
print(
'New ViewModel of Fires Notifications (unread: ${store.state.fireNotificationsUnread})');
return new _ViewModel(
return _ViewModel(
isLoaded: store.state.isLoaded,
onDeleteAll: () {
store.dispatch(new DeleteAllFireNotificationAction());
store.dispatch(DeleteAllFireNotificationAction());
},
onDelete: (notif) {
store.dispatch(new DeleteFireNotificationAction(notif));
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).youDeletedThisNotification),
action: new SnackBarAction(
onDelete: (FireNotification notif) {
store.dispatch(DeleteFireNotificationAction(notif));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(S.of(context).youDeletedThisNotification),
action: SnackBarAction(
label: S.of(context).UNDO,
onPressed: () {
store.dispatch(new AddFireNotificationAction(notif));
store.dispatch(AddFireNotificationAction(notif));
})));
},
onTap: (notif) {
onTap: (FireNotification notif) {
if (!notif.read) {
store.dispatch(new ReadFireNotificationAction(
store.dispatch(ReadFireNotificationAction(
notif.copyWith(read: true)));
}
new Timer(new Duration(milliseconds: 500), () {
Timer(const Duration(milliseconds: 500), () {
gotoMap(store, notif, context);
});
},
@ -176,48 +177,46 @@ class _FireNotificationListState extends State<FireNotificationList> {
fireNotifications: store.state.fireNotifications,
fireNotificationsUnread: store.state.fireNotificationsUnread);
},
builder: (context, view) {
var hasFireNotifications = view.fireNotifications.length > 0;
final title = S.of(context).fireNotificationsTitle;
builder: (BuildContext context, _ViewModel view) {
final bool hasFireNotifications = view.fireNotifications.isNotEmpty;
final String title = S.of(context).fireNotificationsTitle;
print('Building Fire Notifications List');
return Scaffold(
key: _scaffoldKey,
drawer: new MainDrawer(context, FireNotificationList.routeName),
appBar: new AppBar(
drawer: MainDrawer(context, FireNotificationList.routeName),
appBar: AppBar(
title: Text(title),
leading: IconButton(
icon: Icon(Icons.menu),
icon: const Icon(Icons.menu),
onPressed: () {
_scaffoldKey.currentState?.openDrawer();
},
),
actions: (<Widget?>[
hasFireNotifications
? IconButton(
icon: Icon(Icons.delete),
onPressed: () => _showConfirmDialog(view))
: null
]).where((w) => w != null).cast<Widget>().toList()),
actions: <Widget?>[
if (hasFireNotifications) IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _showConfirmDialog(view)) else null
].where((Widget? w) => w != null).cast<Widget>().toList()),
body: !view.isLoaded
? new FiresSpinner()
? const FiresSpinner()
: !hasFireNotifications
? Padding(
padding: const EdgeInsets.all(20.0),
child: new Card(
child: new Padding(
child: Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: new CenteredColumn(children: <Widget>[
new Icon(Icons.notifications_none,
child: CenteredColumn(children: <Widget>[
const Icon(Icons.notifications_none,
size: 150.0, color: Colors.black26),
new SizedBox(height: 20.0),
new Text(
const SizedBox(height: 20.0),
Text(
S
.of(context)
.fireNotificationsDescription,
textAlign: TextAlign.center,
textScaleFactor: 1.1,
style: new TextStyle(
style: const TextStyle(
height: 1.3, color: Colors.black45))
]))))
: _buildSavedFireNotifications(
@ -231,29 +230,29 @@ class _FireNotificationListState extends State<FireNotificationList> {
void gotoMap(
Store<AppState> store, FireNotification notif, BuildContext context) {
store.dispatch(new ShowFireNotificationMapAction(notif));
store.dispatch(ShowFireNotificationMapAction(notif));
Navigator.push(
context, new MaterialPageRoute(builder: (context) => new genericMap()));
context, MaterialPageRoute(builder: (BuildContext context) => const genericMap()));
}
_showConfirmDialog(_ViewModel view) {
return showDialog<Null>(
Future<void> _showConfirmDialog(_ViewModel view) {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return new AlertDialog(
title: new Text(S.of(context).areYouSureTitle),
content: new SingleChildScrollView(
child: new ListBody(
return AlertDialog(
title: Text(S.of(context).areYouSureTitle),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
new Text(
Text(
S.of(context).deleteAllFireNotificationsAlertDescription)
],
),
),
actions: <Widget>[
new TextButton(
child: new Text(S.of(context).DELETE),
TextButton(
child: Text(S.of(context).DELETE),
onPressed: () {
view.onDeleteAll();
Navigator.of(context).pop();

View file

@ -20,7 +20,7 @@ import 'theme.dart';
import 'themeDev.dart';
class FiresApp extends StatefulWidget {
FiresApp(this.store);
const FiresApp(this.store, {super.key});
final Store<AppState> store;
@ -29,48 +29,48 @@ class FiresApp extends StatefulWidget {
}
class _FiresAppState extends State<FiresApp> {
// globals.getIt.registerSingleton
_FiresAppState(this.store);
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
static final WidgetBuilder introWidget = (context) => new IntroPage();
static final WidgetBuilder continueWidget = (context) => new HomePage();
GlobalKey<NavigatorState>();
static Widget introWidget(BuildContext context) => IntroPage();
static Widget continueWidget(BuildContext context) => const HomePage();
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
IntroPage.routeName: introWidget,
HomePage.routeName: continueWidget,
PrivacyPage.routeName: (BuildContext context) => new PrivacyPage(context),
ActiveFiresPage.routeName: (BuildContext context) => new ActiveFiresPage(),
Sandbox.routeName: (BuildContext context) => new Sandbox(),
FireAlert.routeName: (BuildContext context) => new FireAlert(),
SupportPage.routeName: (BuildContext context) => new SupportPage(),
PrivacyPage.routeName: (BuildContext context) => PrivacyPage(context),
ActiveFiresPage.routeName: (BuildContext context) => const ActiveFiresPage(),
Sandbox.routeName: (BuildContext context) => const Sandbox(),
FireAlert.routeName: (BuildContext context) => const FireAlert(),
SupportPage.routeName: (BuildContext context) => const SupportPage(),
FireNotificationList.routeName: (BuildContext context) =>
new FireNotificationList(),
const FireNotificationList(),
MonitoredAreasPage.routeName: (BuildContext context) =>
new MonitoredAreasPage()
const MonitoredAreasPage()
};
final Store<AppState> store;
// globals.getIt.registerSingleton
_FiresAppState(this.store);
@override
Widget build(BuildContext context) {
StatefulWidget home = new MaterialAppWithIntroHome(
final StatefulWidget home = MaterialAppWithIntroHome(
introWidget, continueWidget, 'showInitialWizard-2018-06-27-01');
return new StoreProvider<AppState>(
store: this.store,
child: new MaterialApp(
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
navigatorKey: navigatorKey,
localizationsDelegates: [
localizationsDelegates: <LocalizationsDelegate<dynamic>> [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
localeResolutionCallback:
S.delegate.resolution(fallback: new Locale("en", "")),
S.delegate.resolution(fallback: const Locale('en', '')),
home: home,
onGenerateTitle: (context) {
onGenerateTitle: (BuildContext context) {
print('MaterialApp onGenerateTitle');
return S.of(context).appName;
},

View file

@ -3,8 +3,10 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'colors.dart';
class FiresSpinner extends StatelessWidget {
const FiresSpinner({super.key});
@override
Widget build(BuildContext context) {
return new SpinKitPulse(color: fires600);
return const SpinKitPulse(color: fires600);
}
}

View file

@ -1,11 +1,11 @@
import 'dart:core';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:latlong2/latlong.dart';
import 'package:redux/src/store.dart';
import 'package:share_plus/share_plus.dart';
import 'attributionMapPlugin.dart';
@ -21,6 +21,9 @@ import 'globals.dart' as globals;
import 'layerSelectorMapPlugin.dart';
import 'locationUtils.dart';
import 'models/appState.dart';
import 'models/falsePositiveTypes.dart';
import 'models/fireNotification.dart';
import 'models/yourLocation.dart';
import 'redux/actions.dart';
import 'sentryReport.dart';
import 'slider.dart';
@ -28,21 +31,8 @@ import 'zoomMapPlugin.dart';
@immutable
class _ViewModel {
final String serverUrl;
final String lang;
final FireMapState mapState;
final OnSubscribeFunction onSubs;
final OnSubscribeConfirmedFunction onSubsConfirmed;
final OnUnSubscribeFunction onUnSubs;
final OnSubscribeDistanceChangeFunction onSlide;
final OnLocationEdit onEdit;
final OnLocationEditConfirm onEditConfirm;
final OnLocationEditCancel onEditCancel;
final OnLocationEditing onEditing;
final OnFalsePositive onFalsePositive;
final OnFirePressedInMap onFirePressed;
_ViewModel(
const _ViewModel(
{required this.mapState,
required this.serverUrl,
required this.lang,
@ -56,6 +46,19 @@ class _ViewModel {
required this.onFalsePositive,
required this.onFirePressed,
required this.onEditCancel});
final String serverUrl;
final String lang;
final FireMapState mapState;
final OnSubscribeFunction onSubs;
final OnSubscribeConfirmedFunction onSubsConfirmed;
final OnUnSubscribeFunction onUnSubs;
final OnSubscribeDistanceChangeFunction onSlide;
final OnLocationEdit onEdit;
final OnLocationEditConfirm onEditConfirm;
final OnLocationEditCancel onEditCancel;
final OnLocationEditing onEditing;
final OnFalsePositive onFalsePositive;
final OnFirePressedInMap onFirePressed;
@override
bool operator ==(Object other) =>
@ -71,6 +74,8 @@ class _ViewModel {
}
class genericMap extends StatefulWidget {
const genericMap({super.key});
@override
_genericMapState createState() => _genericMapState();
}
@ -78,49 +83,49 @@ class genericMap extends StatefulWidget {
class _genericMapState extends State<genericMap> {
// This needs to be stateful so when resizes don't get a new globalkey
// https://github.com/flutter/flutter/issues/1632#issuecomment-180478202
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
YourLocation? _location;
YourLocation? _initialLocation;
@override
Widget build(BuildContext context) {
return new StoreConnector<AppState, _ViewModel>(
return StoreConnector<AppState, _ViewModel>(
distinct: true,
onInitialBuild: (store) {
onInitialBuild: (_ViewModel store) {
_initialLocation = _location?.copyWith();
},
converter: (store) {
converter: (Store<AppState> store) {
print('New map viewer');
return new _ViewModel(
onSubs: (loc) {
store.dispatch(new SubscribeAction());
return _ViewModel(
onSubs: (YourLocation loc) {
store.dispatch(SubscribeAction());
},
onSubsConfirmed: (loc) {
onSubsConfirmed: (YourLocation loc) {
loc.subscribed = true;
store.dispatch(new SubscribeConfirmAction(loc));
store.dispatch(SubscribeConfirmAction(loc));
},
onUnSubs: (loc) {
onUnSubs: (YourLocation loc) {
loc.subscribed = false;
store.dispatch(new UnSubscribeAction(loc));
store.dispatch(UnSubscribeAction(loc));
},
onSlide: (loc) {
store.dispatch(new UpdateYourLocationMapAction(loc));
onSlide: (YourLocation loc) {
store.dispatch(UpdateYourLocationMapAction(loc));
},
onEdit: (loc) => store.dispatch(new EditYourLocationAction(loc)),
onEditing: (loc) {
store.dispatch(new UpdateYourLocationMapAction(loc));
onEdit: (YourLocation loc) => store.dispatch(EditYourLocationAction(loc)),
onEditing: (YourLocation loc) {
store.dispatch(UpdateYourLocationMapAction(loc));
},
onEditCancel: (loc) =>
store.dispatch(new EditCancelYourLocationAction(loc)),
onEditConfirm: (loc) {
store.dispatch(new UpdateYourLocationAction(loc));
store.dispatch(new UpdateYourLocationMapAction(loc));
store.dispatch(new EditConfirmYourLocationAction(loc));
onEditCancel: (YourLocation loc) =>
store.dispatch(EditCancelYourLocationAction(loc)),
onEditConfirm: (YourLocation loc) {
store.dispatch(UpdateYourLocationAction(loc));
store.dispatch(UpdateYourLocationMapAction(loc));
store.dispatch(EditConfirmYourLocationAction(loc));
},
onFalsePositive: (notif, type) => store
.dispatch(new MarkFireAsFalsePositiveAction(notif, type)),
onFirePressed: (LatLng latLng, DateTime when, type) {
onFalsePositive: (FireNotification notif, FalsePositiveType type) => store
.dispatch(MarkFireAsFalsePositiveAction(notif, type)),
onFirePressed: (LatLng latLng, DateTime when, String type) {
_showFireDialog(latLng, when, type);
},
serverUrl: store.state.serverUrl,
@ -128,24 +133,23 @@ class _genericMapState extends State<genericMap> {
lang: store.state.user.lang,
mapState: store.state.fireMapState);
},
builder: (context, view) {
YourLocation? location = view.mapState.yourLocation;
builder: (BuildContext context, _ViewModel view) {
final YourLocation? location = view.mapState.yourLocation;
_location = location?.copyWith();
print('New map builder with ${_location?.description}');
FireMapState mapState = view.mapState;
FireMapStatus status = mapState.status;
FireMapLayer layer = mapState.layer;
final FireMapState mapState = view.mapState;
final FireMapStatus status = mapState.status;
final FireMapLayer layer = mapState.layer;
print('Build map with status: $status and layer $layer');
double maxZoom = 18.0; // works?
const double maxZoom = 18.0; // works?
MapOptions mapOptions = new MapOptions(
final MapOptions mapOptions = MapOptions(
initialCenter:
new LatLng(_location?.lat ?? 0.0, _location?.lon ?? 0.0),
initialZoom: 13.0,
LatLng(_location?.lat ?? 0.0, _location?.lon ?? 0.0),
maxZoom: maxZoom,
onTap: (tapPosition, latLng) {
onTap: (TapPosition tapPosition, LatLng latLng) {
if (status == FireMapStatus.edit && _location != null) {
_location = _location!
.copyWith(lat: latLng.latitude, lon: latLng.longitude);
@ -161,19 +165,19 @@ class _genericMapState extends State<genericMap> {
// mapController.fitBounds(bounds);
// mapController.center
final btnText = status == FireMapStatus.view
final String btnText = status == FireMapStatus.view
? S.of(context).toFiresNotifications
: status == FireMapStatus.subscriptionConfirm
? S.of(context).confirm
: S.of(context).unsubscribe;
final btnIcon = status == FireMapStatus.view
final IconData btnIcon = status == FireMapStatus.view
? Icons.notifications_active
: status == FireMapStatus.subscriptionConfirm
? Icons.check
: Icons.notifications_off;
String baseLayer;
List<String> subdomains = [];
List<String> subdomains = <String>[];
String attribution;
switch (layer) {
case FireMapLayer.osmc:
@ -188,14 +192,14 @@ class _genericMapState extends State<genericMap> {
/* tiles from: https://github.com/dceejay/RedMap/blob/1914ed3b9ce4e8a496049849a93282730b4fff02/worldmap/index.html */
switch (layer) {
case FireMapLayer.osmc:
subdomains = ['a', 'b', 'c'];
subdomains = <String>['a', 'b', 'c'];
baseLayer =
'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png';
break;
case FireMapLayer.osmcGrey:
baseLayer =
'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png';
subdomains = ['a', 'b', 'c', 'd'];
subdomains = <String>['a', 'b', 'c', 'd'];
break;
case FireMapLayer.esri:
baseLayer =
@ -209,29 +213,27 @@ class _genericMapState extends State<genericMap> {
baseLayer =
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer/tile/{z}/{y}/{x}';
}
FlutterMap map = new FlutterMap(
final FlutterMap map = FlutterMap(
options: mapOptions,
children: [
new TileLayer(
children: <Widget>[
TileLayer(
maxZoom: maxZoom,
urlTemplate: baseLayer,
subdomains: subdomains,
userAgentPackageName: 'com.example.fires_flutter',
additionalOptions: {
additionalOptions: const <String, String> {
// 'opacity': '0.1',
},
),
globals.isDevelopment
? new ZoomMapPluginWidget()
: new DummyMapPluginWidget(),
new CompassMapPluginWidget(),
new MarkerLayer(
if (globals.isDevelopment) const ZoomMapPluginWidget() else const DummyMapPluginWidget(),
const CompassMapPluginWidget(),
MarkerLayer(
markers: buildMarkers(
mapState.status == FireMapStatus.viewFireNotification &&
mapState.fireNotification != null
? new LatLng(mapState.fireNotification!.lat,
? LatLng(mapState.fireNotification!.lat,
mapState.fireNotification!.lon)
: new LatLng(_location!.lat, _location!.lon),
: LatLng(_location!.lat, _location!.lon),
mapState.fires,
mapState.industries,
mapState.falsePos,
@ -239,8 +241,8 @@ class _genericMapState extends State<genericMap> {
view.onFirePressed),
),
// new AttributionPluginWidget(text: "© OpenStreetMap contributors"),
new LayerSelectorMapPluginWidget(),
new AttributionPluginWidget(text: attribution),
const LayerSelectorMapPluginWidget(),
AttributionPluginWidget(text: attribution),
],
);
// mapController.
@ -250,35 +252,33 @@ class _genericMapState extends State<genericMap> {
;
}); */
// Do something with it
return new Scaffold(
return Scaffold(
key: _scaffoldKey,
resizeToAvoidBottomInset: true,
appBar: new AppBar(
appBar: AppBar(
title: status == FireMapStatus.edit
? new TextField(
? TextField(
// autofocus: true,
key: new Key('LocationDescField'),
key: const Key('LocationDescField'),
keyboardType: TextInputType.text,
decoration: new InputDecoration(),
controller: new TextEditingController.fromValue(
new TextEditingValue(
controller: TextEditingController.fromValue(
TextEditingValue(
text: _location!.description,
selection: new TextSelection.collapsed(
selection: TextSelection.collapsed(
offset: _location!.description.length))),
onChanged: (newDesc) {
debugPrint("OnChanged");
onChanged: (String newDesc) {
debugPrint('OnChanged');
_location = _location!.copyWith(description: newDesc);
},
onSubmitted: (newDesc) {
debugPrint("OnSubmitted");
onSubmitted: (String newDesc) {
debugPrint('OnSubmitted');
_location = _location!.copyWith(description: newDesc);
view.onEditConfirm(_location!);
},
)
: status == FireMapStatus.viewFireNotification
? new Text(S.of(context).fireNotificationTitle)
: new Text(_location!.description),
? Text(S.of(context).fireNotificationTitle)
: Text(_location!.description),
actions: buildAppBarActions(status, view, _location!),
),
floatingActionButton: status == FireMapStatus.edit ||
@ -302,9 +302,9 @@ class _genericMapState extends State<genericMap> {
}
},
// https://github.com/flutter/flutter/issues/17583
heroTag: "firesmap" + _location!.id.hexString,
icon: new Icon(btnIcon, color: fires600),
label: new Text(
heroTag: 'firesmap${_location!.id.hexString}',
icon: Icon(btnIcon, color: fires600),
label: Text(
btnText,
style: const TextStyle(color: fires600),
),
@ -312,20 +312,20 @@ class _genericMapState extends State<genericMap> {
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
bottomNavigationBar: new GenericMapBottom(
bottomNavigationBar: GenericMapBottom(
onSave: () => view.onEditConfirm(_location!),
onCancel: () =>
view.onEditCancel(_initialLocation ?? _location!),
onFalsePositive: (sealed, type) =>
onFalsePositive: (FireNotification sealed, FalsePositiveType type) =>
view.onFalsePositive(sealed, type),
state: view.mapState,
scaffoldKey: _scaffoldKey,
),
body: LayoutBuilder(
builder: (context, constraints) =>
builder: (BuildContext context, BoxConstraints constraints) =>
Stack(fit: StackFit.expand, children: <Widget>[
// Material(color: Colors.yellowAccent),
new Opacity(
Opacity(
opacity:
status == FireMapStatus.subscriptionConfirm ||
status == FireMapStatus.edit
@ -336,7 +336,7 @@ class _genericMapState extends State<genericMap> {
top: constraints.maxHeight - 200,
right: 10.0,
left: 10.0,
child: new CenteredRow(
child: CenteredRow(
// Fit sample:
// https://github.com/apptreesoftware/flutter_map/blob/master/flutter_map_example/lib/pages/map_controller.dart
children: status ==
@ -345,18 +345,18 @@ class _genericMapState extends State<genericMap> {
_location != null &&
_location!.subscribed)
? <Widget>[
new FireDistanceSlider(
FireDistanceSlider(
initialValue:
(_location?.distance ?? 0)
.round(),
onSlide: (distance) {
_location?.distance ?? 0
,
onSlide: (int distance) {
if (_location != null) {
_location!.distance = distance;
view.onSlide(_location!);
}
})
]
: []),
: <Widget>[]),
)
])));
});
@ -368,20 +368,20 @@ class _genericMapState extends State<genericMap> {
case FireMapStatus.view:
case FireMapStatus.unsubscribe:
return <Widget>[
new IconButton(
icon: new Icon(Icons.edit),
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => view.onEdit(location))
];
case FireMapStatus.edit:
return <Widget>[
new IconButton(
icon: new Icon(Icons.save),
IconButton(
icon: const Icon(Icons.save),
onPressed: () => view.onEditConfirm(_location!))
];
case FireMapStatus.viewFireNotification:
return <Widget>[
new IconButton(
icon: new Icon(Icons.share),
IconButton(
icon: const Icon(Icons.share),
onPressed: () {
Share.share(
'${view.mapState.fireNotification?.description ?? 'Fire'}. ${view.serverUrl}fire/${view.mapState.fireNotification?.sealed ?? ''}');
@ -399,15 +399,15 @@ class _genericMapState extends State<genericMap> {
List<dynamic> industries,
bool isNotif,
OnFirePressedInMap onFirePressed) {
List<Marker> markers = [];
final List<Marker> markers = <Marker>[];
print(
'building markers: fires: ${fires.length} falsePos: ${falsePosList.length} industries: ${industries.length}, isNotif: $isNotif ');
// const calibrate = false; // useful when we change the fire icons size
falsePosList.forEach((falsePos) {
for (final falsePos in falsePosList) {
try {
var coords = falsePos['geo']['coordinates'] as List<dynamic>;
final List<dynamic> coords = falsePos['geo']['coordinates'] as List<dynamic>;
// print('false pos: ${coords}');
var loc = LatLng(
final LatLng loc = LatLng(
(coords[1] as num).toDouble(), (coords[0] as num).toDouble());
markers.add(FireMarker(loc, FireMarkType.falsePos, () {
_showFalsePositiveDialog(loc);
@ -417,12 +417,12 @@ class _genericMapState extends State<genericMap> {
print('Failed to process $falsePos');
reportError(e, stackTrace);
}
});
industries.forEach((industry) {
}
for (final industry in industries) {
try {
// print(fire['geo']['coordinates']);
var coords = industry['geo']['coordinates'] as List<dynamic>;
var loc = LatLng(
final List<dynamic> coords = industry['geo']['coordinates'] as List<dynamic>;
final LatLng loc = LatLng(
(coords[1] as num).toDouble(), (coords[0] as num).toDouble());
markers.add(FireMarker(loc, FireMarkType.industry, () {
_showIndustryDialog(loc);
@ -432,14 +432,14 @@ class _genericMapState extends State<genericMap> {
print('Failed to process $industry');
reportError(e, stackTrace);
}
});
fires.forEach((fire) {
}
for (final fire in fires) {
try {
var loc = new LatLng(
final LatLng loc = LatLng(
(fire['lat'] as num).toDouble(), (fire['lon'] as num).toDouble());
markers.add(FireMarker(loc, FireMarkType.fire, () {
onFirePressed(loc, DateTime.parse(fire['when'].toString()),
(fire['type'] as String));
fire['type'] as String);
print('fire $fire pressed');
}));
markers.add(FireMarker(loc, FireMarkType.pixel));
@ -447,7 +447,7 @@ class _genericMapState extends State<genericMap> {
print('Failed to process $fire');
reportError(e, stackTrace);
}
});
}
markers.add(
FireMarker(pos, isNotif ? FireMarkType.fire : FireMarkType.position));
// if (calibrate) markers.add(FireMarker(pos, FireMarkType.pixel));
@ -455,20 +455,20 @@ class _genericMapState extends State<genericMap> {
}
void _showFireDialog(LatLng pos, DateTime date, String type) {
var when = Moment.fromDate(date).fromNow(context);
final String when = Moment.fromDate(date).fromNow(context);
getReverseLocation(lat: pos.latitude, lon: pos.longitude)
.then((reverseLoc) {
String by = type == 'vecinal'
.then((String reverseLoc) {
final String by = type == 'vecinal'
? S.of(context).byOurUsers
: S.of(context).byNASAsatellites;
String fireDesc =
final String fireDesc =
S.of(context).additionalInfoAboutFire(reverseLoc, when, by);
showDialog<bool>(
context: _scaffoldKey.currentContext!,
builder: (_) => new AlertDialog(
content: new Text(fireDesc),
builder: (_) => AlertDialog(
content: Text(fireDesc),
actions: <Widget>[
new TextButton(
TextButton(
child: Text(S.of(context).CLOSE),
onPressed: () {
Navigator.pop(context);
@ -481,17 +481,17 @@ class _genericMapState extends State<genericMap> {
void _showIndustryDialog(LatLng pos) {
getReverseLocation(lat: pos.latitude, lon: pos.longitude)
.then((reverseLoc) {
String industryDesc = '${S.of(context).itSeemsAIndustry}\n\n'
.then((String reverseLoc) {
final String industryDesc = '${S.of(context).itSeemsAIndustry}\n\n'
'Type: Industry\n'
'Location: $reverseLoc';
showDialog<bool>(
context: _scaffoldKey.currentContext!,
builder: (_) => new AlertDialog(
builder: (_) => AlertDialog(
title: Text(S.of(context).notAWildfire),
content: new Text(industryDesc),
content: Text(industryDesc),
actions: <Widget>[
new TextButton(
TextButton(
child: Text(S.of(context).CLOSE),
onPressed: () {
Navigator.pop(context);
@ -504,17 +504,17 @@ class _genericMapState extends State<genericMap> {
void _showFalsePositiveDialog(LatLng pos) {
getReverseLocation(lat: pos.latitude, lon: pos.longitude)
.then((reverseLoc) {
String falseDesc = '${S.of(context).itSeemsNotAtForesFire}\n\n'
.then((String reverseLoc) {
final String falseDesc = '${S.of(context).itSeemsNotAtForesFire}\n\n'
'Type: False Positive\n'
'Location: $reverseLoc';
showDialog<bool>(
context: _scaffoldKey.currentContext!,
builder: (_) => new AlertDialog(
builder: (_) => AlertDialog(
title: Text(S.of(context).notAWildfire),
content: new Text(falseDesc),
content: Text(falseDesc),
actions: <Widget>[
new TextButton(
TextButton(
child: Text(S.of(context).CLOSE),
onPressed: () {
Navigator.pop(context);

View file

@ -1,8 +1,6 @@
import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'colors.dart';
@ -12,90 +10,87 @@ import 'generated/i18n.dart';
import 'models/appState.dart';
import 'models/falsePositiveTypes.dart';
import 'models/fireMapState.dart';
import 'models/fireNotification.dart';
import 'models/yourLocation.dart';
typedef void OnSave();
typedef void OnCancel();
typedef void OnFalsePositive(FireNotification notif, FalsePositiveType type);
typedef OnSave = void Function();
typedef OnCancel = void Function();
typedef OnFalsePositive = void Function(FireNotification notif, FalsePositiveType type);
class GenericMapBottom extends StatelessWidget {
const GenericMapBottom(
{super.key, required this.onSave,
required this.onCancel,
required this.onFalsePositive,
required this.state,
required this.scaffoldKey});
final OnSave onSave;
final OnCancel onCancel;
final OnFalsePositive onFalsePositive;
final FireMapState state;
final GlobalKey<ScaffoldState> scaffoldKey;
GenericMapBottom(
{required this.onSave,
required this.onCancel,
required this.onFalsePositive,
required this.state,
required this.scaffoldKey});
@override
Widget build(BuildContext context) {
final YourLocation? locOrNull = state.yourLocation;
if (locOrNull == null) {
return SizedBox.shrink();
return const SizedBox.shrink();
}
final YourLocation loc = locOrNull;
int kmAround = loc.distance;
return new CustomBottomAppBar(
final int kmAround = loc.distance;
return CustomBottomAppBar(
fabLocation: FloatingActionButtonLocation.centerFloat,
showNotch: false,
color: fires100,
mainAxisAlignment: MainAxisAlignment.center,
actions: buildActionList(loc, context, kmAround, scaffoldKey));
}
List<Widget> buildActionList(YourLocation loc, BuildContext context,
int kmAround, GlobalKey<ScaffoldState> scaffoldState) {
List<Widget> actionList = [];
final List<Widget> actionList = <Widget>[];
switch (state.status) {
case FireMapStatus.edit:
actionList.add(TextButton(
onPressed: onSave,
child: new Text(S.of(context).SAVE,
child: Text(S.of(context).SAVE,
style: Theme.of(context).textTheme.labelLarge)));
actionList.add(TextButton(
onPressed: onCancel,
child: new Text(S.of(context).CANCEL,
child: Text(S.of(context).CANCEL,
style: Theme.of(context).textTheme.labelLarge)));
break;
case FireMapStatus.subscriptionConfirm:
break;
case FireMapStatus.viewFireNotification:
final notif = state.fireNotification;
final FireNotification? notif = state.fireNotification;
if (notif != null) {
actionList.add(new Flexible(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: new Column(
actionList.add(Flexible(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: listWithoutNulls(<Widget?>[
new Text(notif.description),
// TODO fire type (neighbout, NASA, etc)
new SizedBox(height: 5.0),
new Row(
Text(notif.description),
// TODOfire type (neighbout, NASA, etc)
const SizedBox(height: 5.0),
Row(
children: <Widget>[
new Icon(Icons.access_time),
new SizedBox(width: 5.0),
new Text(Moment.now().from(context, notif.when)),
const Icon(Icons.access_time),
const SizedBox(width: 5.0),
Text(Moment.now().from(context, notif.when)),
],
),
state.industries.length > 0 || state.falsePos.length > 0
? new Padding(
padding: new EdgeInsets.only(top: 10.0),
child: new Text(
if (state.industries.isNotEmpty || state.falsePos.isNotEmpty) Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Text(
S.of(context).itSeemsNotAtForesFire,
style: new TextStyle(color: fires600)))
: null,
new DropdownButton<FalsePositiveType>(
style: new TextStyle(
style: const TextStyle(color: fires600))) else null,
DropdownButton<FalsePositiveType>(
style: const TextStyle(
color: Colors.black,
// fontSize: 18.0,
),
hint: new Text(S.of(context).notAWildfire),
hint: Text(S.of(context).notAWildfire),
items: FalsePositiveType.values
.map((FalsePositiveType value) {
String menuText;
@ -110,18 +105,18 @@ class GenericMapBottom extends StatelessWidget {
case FalsePositiveType.falsealarm:
menuText = S.of(context).itSeemsAFalseAlarm;
}
return new DropdownMenuItem<FalsePositiveType>(
value: value, child: new Text(menuText));
return DropdownMenuItem<FalsePositiveType>(
value: value, child: Text(menuText));
}).toList(),
onChanged: (FalsePositiveType? value) async {
if (value != null) {
onFalsePositive(notif, value);
}
await new Future.delayed(
new Duration(milliseconds: 500));
await Future.delayed(
const Duration(milliseconds: 500));
ScaffoldMessenger.of(context)
.showSnackBar(new SnackBar(
content: new Text(
.showSnackBar(SnackBar(
content: Text(
S.of(context).thanksForParticipating),
));
}),
@ -130,7 +125,7 @@ class GenericMapBottom extends StatelessWidget {
break;
case FireMapStatus.unsubscribe:
case FireMapStatus.view:
actionList.add(new Text(state.numFires > 0
actionList.add(Text(state.numFires > 0
? loc.currentNumFires == 1
? S.of(context).fireAroundThisArea(loc.distance.toString())
: S.of(context).firesAroundThisArea(

View file

@ -10,6 +10,8 @@ import 'customMoment.dart';
import 'generated/i18n.dart';
class GlobalFiresBottomStats extends StatefulWidget {
const GlobalFiresBottomStats({super.key});
@override
_GlobalFiresBottomStatsState createState() => _GlobalFiresBottomStatsState();
}
@ -17,20 +19,20 @@ class GlobalFiresBottomStats extends StatefulWidget {
class _GlobalFiresBottomStatsState extends State<GlobalFiresBottomStats> {
late String lastCheck;
int activeFires = 0;
final firesApiUrl = GetIt.instance<String>(instanceName: "firesApiUrl");
final String firesApiUrl = GetIt.instance<String>(instanceName: 'firesApiUrl');
@override
void initState() {
super.initState();
http.read(Uri.parse('${firesApiUrl}status/last-fire-check')).then((result) {
http.read(Uri.parse('${firesApiUrl}status/last-fire-check')).then((String result) {
try {
var now = Moment.now();
var last = DateTime.parse(json.decode(result)['value'] as String);
final Moment now = Moment.now();
final DateTime last = DateTime.parse(json.decode(result)['value'] as String);
http
.read(Uri.parse('${firesApiUrl}status/active-fires-count'))
.then((result) {
.then((String result) {
try {
int count = (json.decode(result)['total'] as num).toInt();
final int count = (json.decode(result)['total'] as num).toInt();
setState(() {
lastCheck = now.from(context, last);
activeFires = count;
@ -51,21 +53,19 @@ class _GlobalFiresBottomStatsState extends State<GlobalFiresBottomStats> {
Widget build(BuildContext context) {
final List<Widget> actionWidgets = <Widget>[];
if (activeFires > 0) {
actionWidgets.add(new Column(
actionWidgets.add(Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
new Text(
children: <Widget>[
Text(
S.of(context).activeFiresWorldWide(activeFires.toString())),
new Text(S.of(context).updatedLastCheck(lastCheck))
Text(S.of(context).updatedLastCheck(lastCheck))
]));
}
return new CustomBottomAppBar(
return CustomBottomAppBar(
fabLocation: FloatingActionButtonLocation.centerDocked,
showNotch: true,
color: fires100,
mainAxisAlignment: MainAxisAlignment.center,
actions: actionWidgets);
}
}

View file

@ -2,8 +2,6 @@ import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/objectIdUtils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:get_it/get_it.dart';
@ -17,12 +15,14 @@ import 'firesSpinner.dart';
import 'generated/i18n.dart';
import 'mainDrawer.dart';
import 'models/appState.dart';
import 'models/fireNotification.dart';
import 'objectIdUtils.dart';
import 'redux/actions.dart';
class _ViewModel {
final bool isLoaded;
_ViewModel({required this.isLoaded});
final bool isLoaded;
@override
bool operator ==(Object other) =>
@ -36,21 +36,21 @@ class _ViewModel {
}
class HomePage extends StatefulWidget {
static const String routeName = '/home';
HomePage();
const HomePage({super.key});
static const String routeName = '/home';
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final Store<AppState> store = GetIt.instance<Store<AppState>>();
final List<FireNotification> newNotifications = [];
final List<FireNotification> newNotifications = <FireNotification>[];
// Platform messages are asynchronous, so we initialize in an async method.
Future<Null> initConnectivity() async {
Future<void> initConnectivity() async {
// Connectivity checking removed - no longer needed
}
@ -70,9 +70,9 @@ class _HomePageState extends State<HomePage> {
// Listen for messages when app is in foreground
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
debugPrint(
"onMessage in fireApp (isLoaded: ${store.state.isLoaded}): $message");
'onMessage in fireApp (isLoaded: ${store.state.isLoaded}): $message');
if (message.data.isNotEmpty) {
final notif = _notifForMessage(message.data, store.state.isLoaded);
final FireNotification? notif = _notifForMessage(message.data, store.state.isLoaded);
if (notif != null) {
_showItemDialog(message.data, notif);
}
@ -82,7 +82,7 @@ class _HomePageState extends State<HomePage> {
// Listen for messages when app is opened from background
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
debugPrint(
"onMessageOpenedApp (isLoaded: ${store.state.isLoaded}): $message");
'onMessageOpenedApp (isLoaded: ${store.state.isLoaded}): $message');
if (message.data.isNotEmpty) {
_notifForMessage(message.data, store.state.isLoaded);
_navigateToItemDetail(message.data);
@ -92,7 +92,7 @@ class _HomePageState extends State<HomePage> {
// Check if app was terminated and opened by notification tap
_firebaseMessaging.getInitialMessage().then((RemoteMessage? message) {
if (message != null) {
debugPrint("App opened by notification: $message");
debugPrint('App opened by notification: $message');
if (message.data.isNotEmpty) {
_navigateToItemDetail(message.data);
}
@ -103,39 +103,39 @@ class _HomePageState extends State<HomePage> {
void _getFirebaseToken() {
_firebaseMessaging.getToken().then((String? token) {
if (token != null) {
store.dispatch(new OnUserTokenAction(token));
store.dispatch(OnUserTokenAction(token));
setState(() {});
}
});
}
final _homeFont = const TextStyle(
final TextStyle _homeFont = const TextStyle(
fontSize: 50.0,
fontWeight: FontWeight.w600,
);
final _btnFont = const TextStyle(
final TextStyle _btnFont = const TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600,
);
@override
Widget build(BuildContext context) {
return new StoreConnector<AppState, _ViewModel>(
return StoreConnector<AppState, _ViewModel>(
distinct: true,
converter: (store) {
bool isLoaded = store.state.isLoaded;
converter: (Store<AppState> store) {
final bool isLoaded = store.state.isLoaded;
if (isLoaded && newNotifications.isNotEmpty) {
newNotifications.forEach((notif) {
store.dispatch(new AddFireNotificationAction(notif));
});
for (final FireNotification notif in newNotifications) {
store.dispatch(AddFireNotificationAction(notif));
}
newNotifications.clear();
}
return new _ViewModel(isLoaded: store.state.isLoaded);
return _ViewModel(isLoaded: store.state.isLoaded);
},
builder: (context, view) {
return new Scaffold(
builder: (BuildContext context, _ViewModel view) {
return Scaffold(
key: _scaffoldKey,
drawer: new MainDrawer(context, HomePage.routeName),
drawer: MainDrawer(context, HomePage.routeName),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
floatingActionButton: Column(
@ -160,7 +160,7 @@ class _HomePageState extends State<HomePage> {
},
heroTag: 'notifyFire',
backgroundColor: fires600,
label: new Text(S.of(context).notifyAFire,
label: Text(S.of(context).notifyAFire,
style: _btnFont),
icon:
const Icon(Icons.notifications_active, size: 32.0),
@ -168,41 +168,40 @@ class _HomePageState extends State<HomePage> {
),
]),
body: !view.isLoaded
? new FiresSpinner()
: new SafeArea(
? const FiresSpinner()
: SafeArea(
child: Center(
child: new CenteredColumn(children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.start,
child: CenteredColumn(children: <Widget>[
Row(
children: <Widget>[
new IconButton(
IconButton(
onPressed: () {
_scaffoldKey.currentState?.openDrawer();
},
icon: new Icon(Icons.menu,
icon: const Icon(Icons.menu,
size: 30.0, color: Colors.black38)),
]),
new Expanded(
child: new FractionallySizedBox(
Expanded(
child: FractionallySizedBox(
alignment: FractionalOffset.center,
heightFactor: 0.7,
child: new Image.asset('images/logo-200.png',
child: Image.asset('images/logo-200.png',
fit: BoxFit.fitHeight))),
new Expanded(
child: new FractionallySizedBox(
Expanded(
child: FractionallySizedBox(
alignment: FractionalOffset.topCenter,
heightFactor: 1.0,
child: new Column(
child: Column(
children: <Widget>[
new Padding(
Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 20.0),
child: FittedBox(
child: new Text(S.of(context).appName,
fit: BoxFit.scaleDown,
child: Text(S.of(context).appName,
maxLines: 2,
textAlign: TextAlign.center,
style: _homeFont),
fit: BoxFit.scaleDown,
)),
],
)))
@ -212,14 +211,14 @@ class _HomePageState extends State<HomePage> {
}
void _showDialog(String message) {
final context = _scaffoldKey.currentContext;
final BuildContext? context = _scaffoldKey.currentContext;
if (context == null) return;
showDialog<bool>(
context: context,
builder: (_) => new AlertDialog(
content: new Text(message),
builder: (_) => AlertDialog(
content: Text(message),
actions: <Widget>[
new TextButton(
TextButton(
child: Text(S.of(context).CLOSE),
onPressed: () {
Navigator.pop(context);
@ -230,29 +229,29 @@ class _HomePageState extends State<HomePage> {
}
void _showItemDialog(Map<String, dynamic> message, FireNotification notif) {
final context = _scaffoldKey.currentContext;
final BuildContext? context = _scaffoldKey.currentContext;
if (context == null) return;
showDialog<bool>(
context: context,
builder: (_) => _buildDialog(context, notif),
).then((bool? shouldNavigate) {
if (shouldNavigate == true) {
if (shouldNavigate ?? false) {
_navigateToItemDetail(message);
}
}).catchError((e) => print("$e"));
}).catchError((e) => print('$e'));
}
Widget _buildDialog(BuildContext context, FireNotification item) {
return new AlertDialog(
content: new Text(item.description),
return AlertDialog(
content: Text(item.description),
actions: <Widget>[
new TextButton(
TextButton(
child: Text(S.of(context).CLOSE),
onPressed: () {
Navigator.pop(context, false);
},
),
new TextButton(
TextButton(
child: Text(S.of(context).SHOW),
onPressed: () {
Navigator.pop(context, true);
@ -263,7 +262,7 @@ class _HomePageState extends State<HomePage> {
}
void _navigateToItemDetail(Map<String, dynamic> message) {
final context = _scaffoldKey.currentContext;
final BuildContext? context = _scaffoldKey.currentContext;
if (context == null) return;
// Clear away dialogs
Navigator.popUntil(context, (Route<dynamic> route) => route is PageRoute);
@ -280,7 +279,7 @@ class _HomePageState extends State<HomePage> {
Map<String, dynamic> message, bool isLoaded) {
FireNotification? notif;
try {
notif = new FireNotification(
notif = FireNotification(
id: objectIdFromJson(message['id'] as String),
subsId: objectIdFromJson(message['subsId'] as String),
lat: double.parse(message['lat'] as String),
@ -296,7 +295,7 @@ class _HomePageState extends State<HomePage> {
// if our store is loaded, we just dispatch the notification, if not, we wait til is loaded
if (notif != null) {
if (isLoaded) {
store.dispatch(new AddFireNotificationAction(notif));
store.dispatch(AddFireNotificationAction(notif));
} else {
newNotifications.add(notif);
}

View file

@ -1,13 +1,18 @@
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart';
import 'homePage.dart';
import 'generated/i18n.dart';
import 'homePage.dart';
class IntroPage extends AppIntroPage {
IntroPage({super.key})
: super(
items: _fireItems,
onIntroFinish: (BuildContext context) =>
Navigator.pushNamed(context, HomePage.routeName));
static const String routeName = '/intro';
static final fireItems = (BuildContext context) => <AppIntroItem>[
static final _fireItems = (BuildContext context) => <AppIntroItem>[
AppIntroItem(
icon: Icons.location_on, title: S.of(context).chooseAPlace),
AppIntroItem(
@ -20,11 +25,4 @@ class IntroPage extends AppIntroPage {
icon: Icons.notifications_active,
title: S.of(context).alertWhenThereIsAFire)
];
IntroPage({Key? key})
: super(
items: fireItems,
onIntroFinish: (context) =>
Navigator.pushNamed(context, HomePage.routeName),
key: key);
}

View file

@ -4,23 +4,24 @@ import 'package:get_it/get_it.dart';
import 'package:redux/redux.dart';
import 'models/appState.dart';
import 'models/fireMapState.dart';
import 'redux/fireMapActions.dart';
/// Layer selector widget for changing map layers
class LayerSelectorMapPluginWidget extends StatelessWidget {
const LayerSelectorMapPluginWidget({super.key});
@override
Widget build(BuildContext context) {
Store<AppState> store = GetIt.instance<Store<AppState>>();
final Store<AppState> store = GetIt.instance<Store<AppState>>();
return LayoutBuilder(
builder: (context, constraints) =>
builder: (BuildContext context, BoxConstraints constraints) =>
Stack(fit: StackFit.expand, children: <Widget>[
Positioned(
top: constraints.maxHeight - 60,
left: 10.0,
child: new CenteredRow(
child: CenteredRow(
children: <Widget>[
new Column(
Column(
children: <Widget>[_LayerSelectorButton(store)],
)
],
@ -30,7 +31,7 @@ class LayerSelectorMapPluginWidget extends StatelessWidget {
}
Widget _LayerSelectorButton(Store<AppState> store) {
final key = GlobalKey<PopupMenuButtonState<FireMapLayer>>();
final GlobalKey<PopupMenuButtonState<FireMapLayer>> key = GlobalKey<PopupMenuButtonState<FireMapLayer>>();
return PopupMenuButton<FireMapLayer>(
key: key,
@ -40,17 +41,17 @@ class LayerSelectorMapPluginWidget extends StatelessWidget {
},
itemBuilder: (BuildContext context) {
return FireMapLayer.values.map((FireMapLayer layer) {
final isSelected = store.state.fireMapState.layer == layer;
final bool isSelected = store.state.fireMapState.layer == layer;
return PopupMenuItem<FireMapLayer>(
value: layer,
child: Row(
children: [
children: <Widget>[
Icon(
isSelected ? Icons.check : Icons.check,
color: isSelected ? Colors.blue : Colors.transparent,
size: 20,
),
SizedBox(width: 8),
const SizedBox(width: 8),
Text(
_getLayerName(layer),
style: TextStyle(
@ -66,7 +67,7 @@ class LayerSelectorMapPluginWidget extends StatelessWidget {
child: FloatingActionButton(
backgroundColor: Colors.black26,
mini: true,
child: Icon(Icons.layers),
child: const Icon(Icons.layers),
onPressed: () {
key.currentState?.showButtonMenu();
},

View file

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:geocoding/geocoding.dart' as geo;
@ -8,42 +7,43 @@ import 'package:location/location.dart';
import 'package:objectid/objectid.dart';
import 'generated/i18n.dart';
import 'models/yourLocation.dart';
Future<YourLocation> getUserLocation(
GlobalKey<ScaffoldState> scaffoldKey) async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
Location _location = new Location();
final Location location0 = Location();
LocationData location = await _location.getLocation();
final LocationData location = await location0.getLocation();
// It seems that the lib fails with lat/lon values
var yl = new YourLocation(
final YourLocation yl = YourLocation(
id: ObjectId(), lat: location.latitude!, lon: location.longitude!);
var address;
String address;
try {
address = await getReverseLocation(lat: yl.lat, lon: yl.lon);
yl.description = address as String;
yl.description = address;
} catch (e) {
try {
address =
await getReverseLocation(lat: yl.lat, lon: yl.lon, external: true);
yl.description = address as String;
yl.description = address;
} catch (e) {
print('We cannot reverse geolocate');
}
}
return yl;
} on PlatformException catch (e) {
final context = scaffoldKey.currentContext;
final BuildContext? context = scaffoldKey.currentContext;
if (context != null) {
if (e.code == 'PERMISSION_DENIED') {
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).notPermsUbication),
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(S.of(context).notPermsUbication),
));
} else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {}
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).isYourUbicationEnabled),
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(S.of(context).isYourUbicationEnabled),
));
}
return YourLocation.noLocation;
@ -53,19 +53,19 @@ Future<YourLocation> getUserLocation(
Future<String> getReverseLocation(
{required double lat, required double lon, bool external = false}) async {
try {
List<geo.Placemark> placemarks =
final List<geo.Placemark> placemarks =
await geo.placemarkFromCoordinates(lat, lon);
if (placemarks.isNotEmpty) {
var first = placemarks.first;
String address =
final geo.Placemark first = placemarks.first;
final String address =
'${first.street}, ${first.locality}, ${first.administrativeArea}, ${first.country}';
print("${first.name} : $address");
print('${first.name} : $address');
return address;
} else {
return 'Unable to determine address';
}
} catch (e) {
print("Error in reverse geocoding: $e");
throw Exception("Failed to reverse geocode: $e");
print('Error in reverse geocoding: $e');
throw Exception('Failed to reverse geocode: $e');
}
}

View file

@ -16,12 +16,12 @@ import 'redux/reducers.dart';
import 'sentryReport.dart';
Future<PackageInfo> loadPackageInfo() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo;
}
Future<Map<String, dynamic>> loadSecrets() async {
return await SecretLoader(
return SecretLoader(
secretPath: globals.isDevelopment
? 'assets/private-settings-dev.json'
: 'assets/private-settings.json')
@ -34,29 +34,29 @@ Future<void> mainCommon(List<Middleware<AppState>> otherMiddleware) async {
// Initialize Firebase before any Firebase-dependent code runs
await Firebase.initializeApp();
final getIt = GetIt.instance;
final GetIt getIt = GetIt.instance;
getIt.registerSingleton<FiresApi>(FiresApi());
loadPackageInfo().then((packageInfo) {
loadPackageInfo().then((PackageInfo packageInfo) {
globals.appVersion = packageInfo.version;
print('Running version ${packageInfo.version}');
loadSecrets().then((secrets) {
final store = Store<AppState>(appStateReducer,
loadSecrets().then((Map<String, dynamic> secrets) {
final Store<AppState> store = Store<AppState>(appStateReducer,
initialState: AppState(
gmapKey: secrets['gmapKey'] as String,
firesApiKey: secrets['firesApiKey'] as String,
serverUrl: secrets['firesApiUrl'] as String,
firesApiUrl: (secrets['firesApiUrl'] as String) + "api/v1/"),
firesApiUrl: "${secrets['firesApiUrl'] as String}api/v1/"),
middleware: List.from(otherMiddleware)..add(fetchDataMiddleware));
getIt.registerSingleton<Store<AppState>>(store);
getIt.registerSingleton<String>(store.state.firesApiUrl,
instanceName: "firesApiUrl");
instanceName: 'firesApiUrl');
getIt.registerSingleton<String>(store.state.firesApiKey,
instanceName: "firesApiKey");
instanceName: 'firesApiKey');
getIt.registerSingleton<String>(store.state.serverUrl,
instanceName: "serverUrl");
instanceName: 'serverUrl');
getIt.registerSingleton<String>(store.state.gmapKey,
instanceName: "gmapKey");
instanceName: 'gmapKey');
// https://flutter.io/cookbook/maintenance/error-reporting/
runZonedGuarded<Future<void>>(() async {
@ -68,7 +68,7 @@ Future<void> mainCommon(List<Middleware<AppState>> otherMiddleware) async {
});
// Listen to store changes, and re-render when the state is updated
store.onChange.listen((state) {
store.onChange.listen((AppState state) {
// print('Store onChange');
});
FlutterError.onError = (FlutterErrorDetails details) {

View file

@ -8,7 +8,7 @@ enum LogLevel { none, actions, all }
void main() async {
globals.isDevelopment = true;
debugPrint("Is development!");
debugPrint('Is development!');
// Simple logging middleware para desarrollo
Middleware<dynamic> createLoggingMiddleware() {
@ -18,10 +18,10 @@ void main() async {
};
}
LogLevel logRedux = LogLevel.actions;
const LogLevel logRedux = LogLevel.actions;
List<Middleware<dynamic>> devMiddlewares =
logRedux == LogLevel.actions ? [] : [createLoggingMiddleware()];
final List<Middleware<dynamic>> devMiddlewares =
logRedux == LogLevel.actions ? <Middleware<dynamic>>[] : <Middleware<dynamic>>[createLoggingMiddleware()];
// In development, Sentry is disabled, so we skip initialization
await mainCommon(devMiddlewares);

View file

@ -1,7 +1,7 @@
import 'package:badges/badges.dart' as badges_pkg;
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/src/store.dart';
import 'activeFires.dart';
import 'colors.dart';
@ -17,11 +17,10 @@ import 'supportPage.dart';
@immutable
class _ViewModel {
final int unreadCount;
_ViewModel({
const _ViewModel({
required this.unreadCount,
});
final int unreadCount;
@override
bool operator ==(Object other) {
@ -36,65 +35,65 @@ class _ViewModel {
}
class MainDrawer extends Drawer {
MainDrawer(BuildContext context, String currentRoute, {Key? key})
: super(key: key, child: mainDrawer(context, currentRoute));
MainDrawer(BuildContext context, String currentRoute, {super.key})
: super(child: mainDrawer(context, currentRoute));
}
Widget mainDrawer(BuildContext context, String currentRoute) {
return new StoreConnector<AppState, _ViewModel>(
return StoreConnector<AppState, _ViewModel>(
distinct: true,
converter: (store) {
return new _ViewModel(unreadCount: store.state.fireNotificationsUnread);
converter: (Store<AppState> store) {
return _ViewModel(unreadCount: store.state.fireNotificationsUnread);
},
builder: (context, view) {
final bottomTextStyle =
new TextStyle(fontSize: 12.0, color: Colors.grey);
return new ListView(
builder: (BuildContext context, _ViewModel view) {
const TextStyle bottomTextStyle =
TextStyle(fontSize: 12.0, color: Colors.grey);
return ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: (<Widget?>[
new GestureDetector(
children: <Widget?>[
GestureDetector(
onTap: () {
Navigator.popAndPushNamed(context, '/');
},
child: new DrawerHeader(
child: new Column(
child: DrawerHeader(
decoration: const BoxDecoration(
color: fires300,
),
child: Column(
children: <Widget>[
new Image.asset(
Image.asset(
'images/logo-200.png',
fit: BoxFit.scaleDown,
height: 80.0,
),
const SizedBox(height: 20.0),
new Text(S.of(context).appName,
style: new TextStyle(
Text(S.of(context).appName,
style: const TextStyle(
fontSize: 24.0,
color: Colors.white,
)),
],
),
decoration: new BoxDecoration(
color: fires300,
),
),
),
new ListTile(
ListTile(
leading: const Icon(Icons.whatshot),
title: new Text(S.of(context).activeFires),
title: Text(S.of(context).activeFires),
selected: currentRoute == ActiveFiresPage.routeName,
onTap: () {
Navigator.popAndPushNamed(context, ActiveFiresPage.routeName);
},
),
new ListTile(
ListTile(
leading: const Icon(Icons.notifications_active),
selected: currentRoute == FireAlert.routeName,
title: new Text(S.of(context).notifyAFire),
title: Text(S.of(context).notifyAFire),
onTap: () {
Navigator.popAndPushNamed(context, FireAlert.routeName);
},
),
new ListTile(
ListTile(
leading: const Icon(Icons.notifications),
selected: currentRoute == FireNotificationList.routeName,
title: Text(S.of(context).fireNotificationsTitleShort),
@ -107,10 +106,10 @@ Widget mainDrawer(BuildContext context, String currentRoute) {
position: badges_pkg.BadgePosition.topEnd(
top: -10, end: -12),
badgeContent: Text(
'${view.unreadCount.toString()}',
style: TextStyle(color: Colors.white),
view.unreadCount.toString(),
style: const TextStyle(color: Colors.white),
),
child: Icon(Icons.notifications))
child: const Icon(Icons.notifications))
])),
// Text(S.of(context).fireNotificationsTitleShort),
@ -119,43 +118,44 @@ Widget mainDrawer(BuildContext context, String currentRoute) {
context, FireNotificationList.routeName);
},
),
new ListTile(
ListTile(
leading: const Icon(Icons.map),
selected: currentRoute == MonitoredAreasPage.routeName,
title: new Text(S.of(context).monitoredAreasTitle),
title: Text(S.of(context).monitoredAreasTitle),
onTap: () {
Navigator.popAndPushNamed(
context, MonitoredAreasPage.routeName);
},
),
new Divider(),
new ListTile(
const Divider(),
ListTile(
leading: const Icon(Icons.favorite),
selected: currentRoute == SupportPage.routeName,
title: new Text(S.of(context).supportThisInitiative),
title: Text(S.of(context).supportThisInitiative),
onTap: () {
Navigator.popAndPushNamed(context, SupportPage.routeName);
},
),
new ListTile(
ListTile(
leading: const Icon(Icons.lock),
selected: currentRoute == PrivacyPage.routeName,
title: new Text(S.of(context).privacyPolicy),
title: Text(S.of(context).privacyPolicy),
onTap: () {
Navigator.popAndPushNamed(context, PrivacyPage.routeName);
},
),
globals.isDevelopment
? new ListTile(
leading: const Icon(Icons.bug_report),
title: new Text('Sandbox'),
selected: currentRoute == Sandbox.routeName,
onTap: () {
Navigator.popAndPushNamed(context, Sandbox.routeName);
},
)
: null,
new AboutListTile(
if (globals.isDevelopment)
ListTile(
leading: const Icon(Icons.bug_report),
title: const Text('Sandbox'),
selected: currentRoute == Sandbox.routeName,
onTap: () {
Navigator.popAndPushNamed(context, Sandbox.routeName);
},
)
else
null,
AboutListTile(
icon: globals.appIcon,
applicationName: S.of(context).appName,
applicationVersion: globals.appVersion,
@ -163,13 +163,13 @@ Widget mainDrawer(BuildContext context, String currentRoute) {
applicationLegalese:
S.of(context).appLicense(DateTime.now().year.toString()),
aboutBoxChildren: <Widget>[
new SizedBox(height: 10.0),
new Text(S.of(context).appMoto),
const SizedBox(height: 10.0),
Text(S.of(context).appMoto),
// , style: new TextStyle(fontStyle: FontStyle.italic)),
new SizedBox(height: 10.0),
new Text(S.of(context).NASAAck, style: bottomTextStyle),
const SizedBox(height: 10.0),
Text(S.of(context).NASAAck, style: bottomTextStyle),
// More ?
])
]).where((w) => w != null).cast<Widget>().toList());
].where((Widget? w) => w != null).cast<Widget>().toList());
});
}

View file

@ -1,25 +1,27 @@
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:redux/src/store.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'globals.dart' as globals;
import 'mainCommon.dart';
import 'models/appState.dart';
Future<void> main() async {
globals.isDevelopment = false;
// Load secrets to get Sentry DSN
final secrets = await loadSecrets();
final Map<String, dynamic> secrets = await loadSecrets();
await SentryFlutter.init(
(options) {
(SentryFlutterOptions options) {
options.dsn = secrets['sentryDSN'] as String?;
},
appRunner: () => mainCommon([]),
appRunner: () => mainCommon(<Middleware<AppState>>[]),
);
}
Future<Map<String, dynamic>> loadSecrets() async {
return await SecretLoader(
return SecretLoader(
secretPath: globals.isDevelopment
? 'assets/private-settings-dev.json'
: 'assets/private-settings.json')

View file

@ -8,40 +8,40 @@ import 'globals.dart' as globals;
import 'mainDrawer.dart';
abstract class MarkdownPage extends StatefulWidget {
const MarkdownPage({super.key, required this.title, required this.route, required this.file});
final String title;
final Future<String> file;
final String route;
MarkdownPage({required this.title, required this.route, required this.file});
@override
_MarkdownPageState createState() =>
_MarkdownPageState(title: this.title, file: this.file, route: this.route);
_MarkdownPageState(title: title, file: file, route: route);
}
class _MarkdownPageState extends State<MarkdownPage> {
final String title;
final String route;
final Future<String> file;
String pageData = "";
_MarkdownPageState(
{required this.title, required this.file, required this.route});
final String title;
final String route;
final Future<String> file;
String pageData = '';
@override
Widget build(BuildContext context) {
this.file.then((fileSync) {
file.then((String fileSync) {
rootBundle
.loadString(fileSync, cache: !globals.isDevelopment)
.then((pageData) {
.then((String pageData) {
setState(() {
this.pageData = pageData;
});
});
});
return new Scaffold(
appBar: new AppBar(title: new Text(title ?? '')),
drawer: new MainDrawer(context, route),
body: new Markdown(data: pageData));
return Scaffold(
appBar: AppBar(title: Text(title ?? '')),
drawer: MainDrawer(context, route),
body: Markdown(data: pageData));
}
}

View file

@ -1,8 +1,6 @@
import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:json_annotation/json_annotation.dart';
@ -10,7 +8,9 @@ import 'package:latlong2/latlong.dart';
import 'package:meta/meta.dart';
import 'fireMapState.dart';
import 'fireNotification.dart';
import 'user.dart';
import 'yourLocation.dart';
export 'fireMapState.dart';
@ -19,6 +19,25 @@ part 'appState.g.dart';
@immutable
@JsonSerializable(nullable: false)
class AppState {
const AppState(
{this.yourLocations = const <YourLocation>[],
this.fireNotifications = const <FireNotification>[],
this.fireNotificationsUnread = 0,
this.user = const User.initial(),
this.isLoading = false,
this.isLoaded = false,
this.error = '',
this.gmapKey = '',
this.firesApiKey = '',
this.firesApiUrl = '',
this.serverUrl = '',
this.monitoredAreas = const <Polyline>[],
this.fireMapState = const FireMapState.initial()});
@JsonKey(ignore: true)
factory AppState.fromJson(Map<String, dynamic> json) =>
_$AppStateFromJson(json);
@JsonKey(ignore: true)
final bool isLoading;
@JsonKey(ignore: true)
@ -44,25 +63,6 @@ class AppState {
@JsonKey(ignore: true)
final FireMapState fireMapState;
@JsonKey(ignore: true)
factory AppState.fromJson(Map<String, dynamic> json) =>
_$AppStateFromJson(json);
AppState(
{this.yourLocations = const <YourLocation>[],
this.fireNotifications = const <FireNotification>[],
this.fireNotificationsUnread = 0,
this.user = const User.initial(),
this.isLoading = false,
this.isLoaded = false,
this.error = '',
this.gmapKey = '',
this.firesApiKey = '',
this.firesApiUrl = '',
this.serverUrl = '',
this.monitoredAreas = const <Polyline>[],
this.fireMapState = const FireMapState.initial()});
AppState copyWith(
{bool? isLoading,
bool? isLoaded,
@ -77,7 +77,7 @@ class AppState {
int? fireNotificationsUnread,
FireMapState? fireMapState,
List<Polyline>? monitoredAreas}) {
return new AppState(
return AppState(
isLoading: isLoading ?? this.isLoading,
isLoaded: isLoaded ?? this.isLoaded,
user: user ?? this.user,
@ -100,24 +100,24 @@ class AppState {
}
}
typedef void AddYourLocationFunction(YourLocation loc);
typedef void DeleteYourLocationFunction(YourLocation loc);
typedef void OnRefreshYourLocationsFunction(Completer<Null> callback);
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 OnFirePressedInMap(LatLng latLng, DateTime when, String source);
typedef AddYourLocationFunction = void Function(YourLocation loc);
typedef DeleteYourLocationFunction = void Function(YourLocation loc);
typedef OnRefreshYourLocationsFunction = void Function(Completer<void> callback);
typedef ToggleSubscriptionFunction = void Function(YourLocation loc);
typedef OnLocationTapFunction = void Function(YourLocation loc);
typedef OnSubscribeFunction = void Function(YourLocation loc);
typedef OnSubscribeDistanceChangeFunction = void Function(YourLocation loc);
typedef OnUnSubscribeFunction = void Function(YourLocation loc);
typedef OnSubscribeConfirmedFunction = void Function(YourLocation loc);
typedef OnLocationEdit = void Function(YourLocation loc);
typedef OnLocationEditing = void Function(YourLocation loc);
typedef OnLocationEditConfirm = void Function(YourLocation loc);
typedef OnLocationEditCancel = void Function(YourLocation loc);
typedef TapFireNotificationFunction = void Function(FireNotification notif);
typedef OnFirePressedInMap = void Function(LatLng latLng, DateTime when, String source);
// typedef void OnReceivedFireNotificationFunction(FireNotification notif);
typedef void DeleteFireNotificationFunction(FireNotification notif);
typedef void DeleteAllFireNotificationFunction();
typedef DeleteFireNotificationFunction = void Function(FireNotification notif);
typedef DeleteAllFireNotificationFunction = void Function();
// unused
// typedef void UpdateYourLocationFunction(ObjectId id, YourLocation loc);

View file

@ -1,18 +1,26 @@
class BasicLocation implements Comparable<BasicLocation> {
// static BasicLocation noLocation = new BasicLocation(lat: 0.0, lon: 0.0);
BasicLocation({required this.lat, required this.lon, this.description});
BasicLocation.fromJson(Map<String, dynamic> json)
: lat = (json['lat'] as num).toDouble(),
lon = (json['lon'] as num).toDouble(),
description = json['description'] as String?;
final double lat;
final double lon;
final String? description;
// static BasicLocation noLocation = new BasicLocation(lat: 0.0, lon: 0.0);
BasicLocation({required this.lat, required this.lon, this.description}) {}
@override
int compareTo(BasicLocation other) {
return lat == other.lat && lon == other.lon ? 1 : 0;
}
bool operator ==(o) => o is BasicLocation && o.lat == lat && o.lon == lon;
@override
bool operator ==(Object o) => o is BasicLocation && o.lat == lat && o.lon == lon;
@override
int get hashCode {
int hash = 1;
hash = hash * 17 + lat.hashCode;
@ -20,12 +28,7 @@ class BasicLocation implements Comparable<BasicLocation> {
return hash;
}
BasicLocation.fromJson(Map<String, dynamic> json)
: lat = (json['lat'] as num).toDouble(),
lon = (json['lon'] as num).toDouble(),
description = json['description'] as String?;
Map<String, dynamic> toJson() => {
Map<String, dynamic> toJson() => <String, dynamic>{
'lat': lat,
'lon': lon,
'description': description,

View file

@ -1,7 +1,8 @@
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:meta/meta.dart';
import 'fireNotification.dart';
import 'yourLocation.dart';
enum FireMapStatus {
view,
subscriptionConfirm,
@ -14,6 +15,26 @@ enum FireMapLayer { osmcGrey, esriSatellite, osmc, esri, esriTerrain }
@immutable
class FireMapState {
const FireMapState(
{this.status = FireMapStatus.view,
this.layer = FireMapLayer.osmcGrey,
this.yourLocation,
this.numFires = 0,
this.fires = const <dynamic>[],
this.fireNotification,
this.falsePos = const <dynamic>[],
this.industries = const <dynamic>[]});
const FireMapState.initial()
: status = FireMapStatus.view,
layer = FireMapLayer.osmcGrey,
yourLocation = null,
fireNotification = null,
numFires = 0,
fires = const <dynamic>[],
falsePos = const <dynamic>[],
industries = const <dynamic>[];
final FireMapStatus status;
final FireMapLayer layer;
final int numFires;
@ -23,26 +44,6 @@ class FireMapState {
final List<dynamic> industries;
final YourLocation? yourLocation;
const FireMapState.initial()
: this.status = FireMapStatus.view,
this.layer = FireMapLayer.osmcGrey,
this.yourLocation = null,
this.fireNotification = null,
this.numFires = 0,
this.fires = const [],
this.falsePos = const [],
this.industries = const [];
FireMapState(
{this.status = FireMapStatus.view,
this.layer = FireMapLayer.osmcGrey,
this.yourLocation,
this.numFires = 0,
this.fires = const [],
this.fireNotification,
this.falsePos = const [],
this.industries = const []});
FireMapState copyWith({
FireMapStatus? status,
FireMapLayer? layer,
@ -53,9 +54,9 @@ class FireMapState {
List<dynamic>? falsePos,
List<dynamic>? industries,
}) {
return new FireMapState(
return FireMapState(
yourLocation: yourLocation ?? this.yourLocation,
fireNotification: fireNotication ?? this.fireNotification,
fireNotification: fireNotication ?? fireNotification,
numFires: numFires ?? this.numFires,
fires: fires ?? this.fires,
falsePos: falsePos ?? this.falsePos,

View file

@ -9,6 +9,19 @@ part 'fireNotification.g.dart';
@JsonSerializable(nullable: false)
class FireNotification {
FireNotification(
{required this.id,
required this.lat,
required this.lon,
required this.description,
required this.when,
required this.read,
required this.sealed,
required this.subsId});
factory FireNotification.fromJson(Map<String, dynamic> json) =>
_$FireNotificationFromJson(json);
@JsonKey(toJson: objectIdToJson, fromJson: objectIdFromJson)
ObjectId id;
final double lat;
@ -20,19 +33,6 @@ class FireNotification {
final ObjectId subsId;
final bool read;
factory FireNotification.fromJson(Map<String, dynamic> json) =>
_$FireNotificationFromJson(json);
FireNotification(
{required this.id,
required this.lat,
required this.lon,
required this.description,
required this.when,
required this.read,
required this.sealed,
required this.subsId}) {}
FireNotification copyWith(
{ObjectId? id,
double? lat,
@ -42,7 +42,7 @@ class FireNotification {
DateTime? when,
String? sealed,
ObjectId? subsId}) {
return new FireNotification(
return FireNotification(
id: id ?? this.id,
lat: lat ?? this.lat,
lon: lon ?? this.lon,
@ -83,7 +83,7 @@ class FireNotification {
return 'FireNotification {id: $id, lat: $lat, lon: $lon, when: $when, read: $read, subsId: $subsId, sealed ${ellipse(sealed)}';
}
static final Map<String, Route<Null>> routes = <String, Route<Null>>{};
static final Map<String, Route<void>> routes = <String, Route<void>>{};
Map<String, dynamic> toJson() => _$FireNotificationToJson(this);
/*

View file

@ -2,30 +2,31 @@ import 'dart:async';
import 'dart:convert';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:shared_preferences/src/shared_preferences_legacy.dart';
import '../globals.dart' as globals;
import 'fireNotification.dart';
final String fireNotificationKey = 'fireNotifications';
const String fireNotificationKey = 'fireNotifications';
Future<List<FireNotification>> loadFireNotifications() async {
return await globals.prefs.then((prefs) {
List<String>? FireNotifications = prefs.getStringList(fireNotificationKey);
List<FireNotification> persistedList = [];
(FireNotifications ?? []).forEach((notificationString) {
Map<String, dynamic> notificationMap =
return globals.prefs.then((SharedPreferences prefs) {
final List<String>? FireNotifications = prefs.getStringList(fireNotificationKey);
final List<FireNotification> persistedList = <FireNotification>[];
for (final String notificationString in (FireNotifications ?? <String>[])) {
final Map<String, dynamic> notificationMap =
json.decode(notificationString) as Map<String, dynamic>;
persistedList.add(FireNotification.fromJson(notificationMap));
});
}
return persistedList;
});
}
persistFireNotifications(List<FireNotification> notif) {
void persistFireNotifications(List<FireNotification> notif) {
// print('Persisting $notif');
globals.prefs.then((prefs) {
List<String> notifAsString = [];
notif.where(notNull).toList().forEach((notification) {
globals.prefs.then((SharedPreferences prefs) {
final List<String> notifAsString = <String>[];
notif.where(notNull).toList().forEach((FireNotification notification) {
notifAsString.add(json.encode(notification.toJson()));
});
prefs.setStringList(fireNotificationKey, notifAsString);

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
@ -12,49 +11,50 @@ import '../objectIdUtils.dart';
import '../redux/actions.dart';
import 'appState.dart';
import 'falsePositiveTypes.dart';
import 'yourLocation.dart';
class FiresApi {
late final Dio _dio;
FiresApi() {
_dio = Dio();
}
late final Dio _dio;
Future<String> createUser(
AppState state, String mobileToken, String lang) async {
final params = {
"token": state.firesApiKey,
"mobileToken": mobileToken,
"lang": lang
final Map<String, String> params = <String, String>{
'token': state.firesApiKey,
'mobileToken': mobileToken,
'lang': lang
};
final String url = '${state.firesApiUrl}mobile/users';
try {
final response = await _dio.post(url, data: params);
final Response<dynamic> response = await _dio.post(url, data: params);
if (response.statusCode == 200) {
return response.data['data']['userId'] as String;
} else {
throw "Unexpected error on create user";
throw 'Unexpected error on create user';
}
} catch (e) {
throw "Error creating user: $e";
throw 'Error creating user: $e';
}
}
Future<List<YourLocation>> fetchYourLocations(AppState state) async {
final apiKey = state.firesApiKey;
final mobileToken = state.user.token;
final String apiKey = state.firesApiKey;
final String mobileToken = state.user.token;
final String url =
'${state.firesApiUrl}mobile/subscriptions/all/$apiKey/$mobileToken';
try {
final response = await _dio.get(url);
final Response<dynamic> response = await _dio.get(url);
if (response.statusCode == 200) {
final dataSubscriptions =
final List<dynamic> dataSubscriptions =
response.data['data']['subscriptions'] as List<dynamic>;
List<YourLocation> subscribed = [];
final List<YourLocation> subscribed = <YourLocation>[];
for (int i = 0; i < dataSubscriptions.length; i++) {
var el = dataSubscriptions[i] as Map<String, dynamic>;
var lat = (el['location']['lat'] as num).toDouble();
var lon = (el['location']['lon'] as num).toDouble();
final Map<String, dynamic> el = dataSubscriptions[i] as Map<String, dynamic>;
final double lat = (el['location']['lat'] as num).toDouble();
final double lon = (el['location']['lon'] as num).toDouble();
subscribed.add(YourLocation(
id: objectIdFromJson(el['_id']['_str'] as String),
lat: lat,
@ -64,50 +64,50 @@ class FiresApi {
}
return subscribed;
} else {
throw "Unexpected error fetching your locations";
throw 'Unexpected error fetching your locations';
}
} catch (e) {
throw "Error fetching locations: $e";
throw 'Error fetching locations: $e';
}
}
Future<String> subscribe(AppState state, YourLocation loc) async {
final params = {
"token": state.firesApiKey,
"mobileToken": state.user.token,
"id": loc.id.hexString,
"lat": loc.lat,
"lon": loc.lon,
"distance": loc.distance
final Map<String, Object> params = <String, Object>{
'token': state.firesApiKey,
'mobileToken': state.user.token,
'id': loc.id.hexString,
'lat': loc.lat,
'lon': loc.lon,
'distance': loc.distance
};
final String url = '${state.firesApiUrl}mobile/subscriptions';
try {
final response = await _dio.post(url, data: params);
final Response<dynamic> response = await _dio.post(url, data: params);
if (response.statusCode == 200) {
return response.data['data']['subsId'] as String;
} else {
print(response.data);
throw "Unexpected error on subscribe";
throw 'Unexpected error on subscribe';
}
} catch (e) {
throw "Error subscribing: $e";
throw 'Error subscribing: $e';
}
}
Future<bool> unsubscribe(AppState state, String subsId) async {
final apiKey = state.firesApiKey;
final mobileToken = state.user.token;
final String apiKey = state.firesApiKey;
final String mobileToken = state.user.token;
final String url =
'${state.firesApiUrl}mobile/subscriptions/$apiKey/$mobileToken/$subsId';
try {
final response = await _dio.delete(url);
final Response<dynamic> response = await _dio.delete(url);
if (response.statusCode == 200) {
return true;
} else {
throw "Unexpected error on unsubscribe";
throw 'Unexpected error on unsubscribe';
}
} catch (e) {
throw "Error unsubscribing: $e";
throw 'Error unsubscribing: $e';
}
}
@ -116,22 +116,22 @@ class FiresApi {
required double lat,
required double lon,
required int distance}) async {
var url =
final String url =
'${state.firesApiUrl}fires-in-full/${state.firesApiKey}/$lat/$lon/$distance';
if (globals.isDevelopment) print(url);
try {
final response = await _dio.get(url);
final Response<dynamic> response = await _dio.get(url);
if (response.statusCode == 200) {
var resultDecoded = response.data;
int numFires = (resultDecoded['real'] as num).toInt();
List<dynamic> fires = resultDecoded['fires'] as List<dynamic>;
List<dynamic> falsePos = resultDecoded['falsePos'] as List<dynamic>;
List<dynamic> industries = resultDecoded['industries'] as List<dynamic>;
final resultDecoded = response.data;
final int numFires = (resultDecoded['real'] as num).toInt();
final List<dynamic> fires = resultDecoded['fires'] as List<dynamic>;
final List<dynamic> falsePos = resultDecoded['falsePos'] as List<dynamic>;
final List<dynamic> industries = resultDecoded['industries'] as List<dynamic>;
if (globals.isDevelopment) {
var firesCount = fires.length;
var industriesCount = industries.length;
var falsePosCount = falsePos.length;
final int firesCount = fires.length;
final int industriesCount = industries.length;
final int falsePosCount = falsePos.length;
print(
'(Pos: $lat, $lon) real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount');
}
@ -148,25 +148,25 @@ class FiresApi {
}
Future<List<Polyline>> getMonitoredAreas({required AppState state}) async {
var url =
final String url =
'${state.firesApiUrl}status/subs-public-union/${state.firesApiKey}';
var color = const Color(0xFF145A32);
const Color color = Color(0xFF145A32);
try {
final response = await _dio.get(url);
final Response<dynamic> response = await _dio.get(url);
if (response.statusCode == 200) {
var resultDecoded = response.data;
List<Polyline> union = [];
final multipolygon =
final resultDecoded = response.data;
final List<Polyline> union = <Polyline>[];
final List<dynamic> multipolygon =
(json.decode(resultDecoded['data']['union']['value'] as String)
as Map<String, dynamic>)['geometry']['coordinates']
as List<dynamic>;
for (dynamic polygonDynamic in multipolygon) {
var polygon = polygonDynamic as List<dynamic>;
for (dynamic holeDynamic in polygon) {
var hole = holeDynamic as List<dynamic>;
List<LatLng> points = [];
for (dynamic pointDynamic in hole) {
var point = pointDynamic as List<dynamic>;
for (final dynamic polygonDynamic in multipolygon) {
final List<dynamic> polygon = polygonDynamic as List<dynamic>;
for (final dynamic holeDynamic in polygon) {
final List<dynamic> hole = holeDynamic as List<dynamic>;
final List<LatLng> points = <LatLng>[];
for (final dynamic pointDynamic in hole) {
final List<dynamic> point = pointDynamic as List<dynamic>;
points.add(LatLng(
(point[1] as num).toDouble(), (point[0] as num).toDouble()));
}
@ -183,15 +183,15 @@ class FiresApi {
Future<bool> markFalsePositive(AppState state, String mobileToken,
String sealed, FalsePositiveType type) async {
final params = {
"token": state.firesApiKey,
"mobileToken": mobileToken,
"sealed": sealed,
"type": type.toString().split('.')[1]
final Map<String, String> params = <String, String>{
'token': state.firesApiKey,
'mobileToken': mobileToken,
'sealed': sealed,
'type': type.toString().split('.')[1]
};
final String url = '${state.firesApiUrl}mobile/falsepositive';
try {
final response = await _dio.post(url, data: params);
final Response<dynamic> response = await _dio.post(url, data: params);
if (response.statusCode == 200) {
if (globals.isDevelopment) print(response.data['data']['upsert']);
return true;
@ -200,7 +200,7 @@ class FiresApi {
return false;
}
} catch (e) {
debugPrint("Error marking false positive: $e");
debugPrint('Error marking false positive: $e');
return false;
}
}

View file

@ -1,21 +1,21 @@
import 'package:meta/meta.dart';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:meta/meta.dart';
@immutable
class User {
final String userId;
final String lang;
final String token;
const User({required this.userId, required this.lang, required this.token});
const User.initial()
: userId = '',
lang = '',
token = '';
const User({required this.userId, required this.lang, required this.token});
final String userId;
final String lang;
final String token;
User copyWith({String? userId, String? lang, String? token}) {
return new User(
return User(
userId: userId ?? this.userId,
token: token ?? this.token,
lang: lang ?? this.lang);

View file

@ -1,30 +1,12 @@
import 'package:fires_flutter/objectIdUtils.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:objectid/objectid.dart';
import '../objectIdUtils.dart';
part 'yourLocation.g.dart';
@JsonSerializable(nullable: false)
class YourLocation {
@JsonKey(toJson: objectIdToJson, fromJson: objectIdFromJson)
ObjectId id;
final double lat;
final double lon;
String description;
bool subscribed;
int distance;
late int currentNumFires;
factory YourLocation.fromJson(Map<String, dynamic> json) =>
_$YourLocationFromJson(json);
static late final YourLocation noLocation;
static const int? withoutStats = null;
static void _initNoLocation() {
noLocation = new YourLocation(
id: ObjectId(), lat: 0.0, lon: 0.0, description: '', subscribed: false);
}
YourLocation(
{required this.id,
@ -36,6 +18,25 @@ class YourLocation {
this.subscribed = false}) {
this.currentNumFires = currentNumFires ?? 0;
}
factory YourLocation.fromJson(Map<String, dynamic> json) =>
_$YourLocationFromJson(json);
@JsonKey(toJson: objectIdToJson, fromJson: objectIdFromJson)
ObjectId id;
final double lat;
final double lon;
String description;
bool subscribed;
int distance;
late int currentNumFires;
static late final YourLocation noLocation;
static const int? withoutStats = null;
static void _initNoLocation() {
noLocation = YourLocation(
id: ObjectId(), lat: 0.0, lon: 0.0);
}
Map<String, dynamic> toJson() => _$YourLocationToJson(this);
YourLocation copyWith(
@ -46,7 +47,7 @@ class YourLocation {
int? distance,
int? currentNumFires,
bool? subscribed}) {
return new YourLocation(
return YourLocation(
id: id ?? this.id,
lat: lat ?? this.lat,
lon: lon ?? this.lon,

View file

@ -2,30 +2,31 @@ import 'dart:async';
import 'dart:convert';
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:shared_preferences/src/shared_preferences_legacy.dart';
import '../globals.dart' as globals;
import 'yourLocation.dart';
final String locationKey = 'yourlocations';
const String locationKey = 'yourlocations';
Future<List<YourLocation>> loadYourLocations() async {
return await globals.prefs.then((prefs) {
List<String>? yourLocations = prefs.getStringList(locationKey);
List<YourLocation> persistedList = [];
(yourLocations ?? []).forEach((locationString) {
Map<String, dynamic> locationMap =
return globals.prefs.then((SharedPreferences prefs) {
final List<String>? yourLocations = prefs.getStringList(locationKey);
final List<YourLocation> persistedList = <YourLocation>[];
for (final String locationString in (yourLocations ?? <String>[])) {
final Map<String, dynamic> locationMap =
json.decode(locationString) as Map<String, dynamic>;
persistedList.add(YourLocation.fromJson(locationMap));
});
}
return persistedList;
});
}
persistYourLocations(List<YourLocation> yl) {
void persistYourLocations(List<YourLocation> yl) {
// debugPrint('Persisting $yl');
globals.prefs.then((prefs) {
List<String> ylAsString = [];
yl.where(notNull).toList().forEach((location) {
globals.prefs.then((SharedPreferences prefs) {
final List<String> ylAsString = <String>[];
yl.where(notNull).toList().forEach((YourLocation location) {
ylAsString.add(json.encode(location.toJson()));
});
prefs.setStringList(locationKey, ylAsString);

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:latlong2/latlong.dart';
import 'package:redux/src/store.dart';
import 'colors.dart';
import 'compassMapPlugin.dart';
@ -9,12 +10,11 @@ import 'customBottomAppBar.dart';
import 'generated/i18n.dart';
import 'mainDrawer.dart';
import 'models/appState.dart';
import 'firesSpinner.dart';
class _ViewModel {
List<Polyline> monitoredAreas;
_ViewModel(this.monitoredAreas);
List<Polyline> monitoredAreas;
@override
bool operator ==(Object other) =>
@ -28,65 +28,63 @@ class _ViewModel {
}
class MonitoredAreasPage extends StatelessWidget {
static const String routeName = "monitoredAreasMap";
const MonitoredAreasPage({super.key});
static const String routeName = 'monitoredAreasMap';
@override
Widget build(BuildContext context) {
return new StoreConnector<AppState, _ViewModel>(
return StoreConnector<AppState, _ViewModel>(
distinct: true,
converter: (store) {
return new _ViewModel(store.state.monitoredAreas);
converter: (Store<AppState> store) {
return _ViewModel(store.state.monitoredAreas);
},
builder: (context, view) {
return new Scaffold(
builder: (BuildContext context, _ViewModel view) {
return Scaffold(
appBar:
new AppBar(title: new Text(S.of(context).monitoredAreasTitle)),
drawer: new MainDrawer(context, MonitoredAreasPage.routeName),
bottomNavigationBar: new CustomBottomAppBar(
AppBar(title: Text(S.of(context).monitoredAreasTitle)),
drawer: MainDrawer(context, MonitoredAreasPage.routeName),
bottomNavigationBar: CustomBottomAppBar(
fabLocation: FloatingActionButtonLocation.centerDocked,
showNotch: true,
color: fires100,
mainAxisAlignment: MainAxisAlignment.center,
actions: <Widget>[
new Flexible(
child: new Padding(
padding: new EdgeInsets.only(left: 10.0, right: 10.0),
child: new Column(
Flexible(
child: Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
new Text(S.of(context).mapPrivacy,
style: new TextStyle(
children: <Widget>[
Text(S.of(context).mapPrivacy,
style: const TextStyle(
fontStyle: FontStyle.italic,
color: Colors.black38))
])))
]),
body: !(view.monitoredAreas is List)
? new FiresSpinner()
: new Padding(
padding: new EdgeInsets.all(10.0),
child: new Column(
children: [
new Padding(
padding: new EdgeInsets.only(
top: 8.0, bottom: 8.0, left: 0.0, right: 0.0),
child: new Text(S.of(context).inGreenMonitoredAreas),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
top: 8.0, bottom: 8.0),
child: Text(S.of(context).inGreenMonitoredAreas),
),
new Flexible(
child: new FlutterMap(
options: new MapOptions(
initialCenter: new LatLng(53.5775, 3.106111),
Flexible(
child: FlutterMap(
options: const MapOptions(
initialCenter: LatLng(53.5775, 3.106111),
initialZoom: 1.0,
),
children: [
new TileLayer(
children: <Widget>[
TileLayer(
urlTemplate:
"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c', 'd'],
'https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png',
subdomains: const <String>['a', 'b', 'c', 'd'],
userAgentPackageName:
'com.example.fires_flutter'),
new CompassMapPluginWidget(),
new PolylineLayer(
const CompassMapPluginWidget(),
PolylineLayer(
polylines: view.monitoredAreas,
)
],

View file

@ -1,10 +1,10 @@
import 'package:objectid/objectid.dart';
ObjectId objectIdFromJson(String json) {
return new ObjectId.fromHexString(
json.replaceFirst("ObjectId(", "").replaceAll(")", ""));
return ObjectId.fromHexString(
json.replaceFirst('ObjectId(', '').replaceAll(')', ''));
}
objectIdToJson(ObjectId o) {
String objectIdToJson(ObjectId o) {
return o.toString();
}

View file

@ -1,8 +1,9 @@
import 'dart:async';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'models/yourLocation.dart';
/// Open a places dialog for selecting a location.
/// Currently returns a default location as the google_places_autocomplete package
@ -10,9 +11,9 @@ import 'package:flutter/material.dart';
/// TODO: Implement with a modern null-safe places API integration
Future<YourLocation> openPlacesDialog(GlobalKey<ScaffoldState> sc) async {
// Show a snackbar informing the user that this feature is not yet available
final messenger = ScaffoldMessenger.of(sc.currentContext!);
final ScaffoldMessengerState messenger = ScaffoldMessenger.of(sc.currentContext!);
messenger.showSnackBar(
SnackBar(
const SnackBar(
content: Text(
'Place selection is currently unavailable. Please use manual location entry.'),
),

View file

@ -5,9 +5,8 @@ import 'generated/i18n.dart';
import 'markdownPage.dart';
class PrivacyPage extends MarkdownPage {
static const String routeName = '/privacy';
PrivacyPage(BuildContext context)
PrivacyPage(BuildContext context, {super.key})
: super(
title: S.of(context).privacyPolicy,
route: routeName,
@ -16,4 +15,5 @@ class PrivacyPage extends MarkdownPage {
fileName: 'privacy',
ext: 'md',
lang: Localizations.localeOf(context).languageCode));
static const String routeName = '/privacy';
}

View file

@ -1,4 +1,4 @@
export 'appActions.dart';
export 'yourLocationActions.dart';
export 'fireMapActions.dart';
export 'fireNotificationActions.dart';
export 'fireNotificationActions.dart';
export 'yourLocationActions.dart';

View file

@ -1,23 +1,25 @@
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'dart:async';
import 'package:flutter_map/flutter_map.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_map/flutter_map.dart';
import '../models/fireNotification.dart';
import '../models/yourLocation.dart';
abstract class AppActions {}
class FetchYourLocationsAction extends AppActions {
Completer<Null>? refreshCallback;
FetchYourLocationsAction([this.refreshCallback]);
Completer<void>? refreshCallback;
}
class PersistAppStateAction extends AppActions {}
class FetchYourLocationsSucceededAction extends AppActions {
final List<YourLocation> fetchedYourLocations;
FetchYourLocationsSucceededAction(this.fetchedYourLocations);
final List<YourLocation> fetchedYourLocations;
}
class FetchFireNotificationsAction extends AppActions {}
@ -25,45 +27,45 @@ class FetchFireNotificationsAction extends AppActions {}
class FetchMonitoredAreasAction extends AppActions {}
class FetchYourLocationsFailedAction extends AppActions {
final Exception error;
FetchYourLocationsFailedAction(this.error);
final Exception error;
}
class OnUserTokenAction extends AppActions {
final String token;
OnUserTokenAction(this.token);
final String token;
}
class OnUserCreatedAction extends AppActions {
final String userId;
OnUserCreatedAction(this.userId);
final String userId;
}
class OnUserLangAction extends AppActions {
final String lang;
OnUserLangAction(this.lang);
final String lang;
}
class FetchFireNotificationsSucceededAction extends AppActions {
final List<FireNotification> fetchedFireNotifications;
final int unreadCount;
FetchFireNotificationsSucceededAction(
this.fetchedFireNotifications, this.unreadCount);
final List<FireNotification> fetchedFireNotifications;
final int unreadCount;
}
class FetchMonitoredAreasSucceededAction extends AppActions {
final List<Polyline> monitoredAreas;
FetchMonitoredAreasSucceededAction(this.monitoredAreas);
final List<Polyline> monitoredAreas;
}
class OnConnectivityChanged extends AppActions {
final List<ConnectivityResult> connectionStatus;
OnConnectivityChanged(this.connectionStatus);
final List<ConnectivityResult> connectionStatus;
}

View file

@ -1,5 +1,5 @@
import 'actions.dart';
import '../models/appState.dart';
import 'actions.dart';
AppState appReducer(AppState state, action) {
if (action is FetchYourLocationsSucceededAction) {

View file

@ -1,14 +1,15 @@
import 'dart:async';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart';
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';
@ -55,20 +56,20 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
if (action is AddYourLocationAction) {
if (action.loc.subscribed) {
subscribeViaApi(store, action.loc, (sub) {
store.dispatch(new AddedYourLocationAction(sub));
subscribeViaApi(store, action.loc, (YourLocation sub) {
store.dispatch(AddedYourLocationAction(sub));
persistYourLocations(store.state.yourLocations);
});
} else {
// No subscribed (only local)
store.dispatch(new AddedYourLocationAction(action.loc));
store.dispatch(AddedYourLocationAction(action.loc));
persistYourLocations(store.state.yourLocations);
}
getFiresStatsInLocation(store, action.loc);
}
if (action is DeleteYourLocationAction) {
store.dispatch(new DeletedYourLocationAction(action.loc.id));
store.dispatch(DeletedYourLocationAction(action.loc.id));
if (action.loc.subscribed) {
unsubsViaApi(store, action.loc.id, () {
persistYourLocations(store.state.yourLocations);
@ -79,17 +80,17 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
}
if (action is DeleteFireNotificationAction) {
store.dispatch(new DeletedFireNotificationAction(action.notif));
store.dispatch(DeletedFireNotificationAction(action.notif));
persistFireNotifications(store.state.fireNotifications);
}
if (action is DeleteAllFireNotificationAction) {
store.dispatch(new DeletedAllFireNotificationAction());
store.dispatch(DeletedAllFireNotificationAction());
persistFireNotifications(store.state.fireNotifications);
}
if (action is AddFireNotificationAction) {
store.dispatch(new AddedFireNotificationAction(action.notif));
store.dispatch(AddedFireNotificationAction(action.notif));
persistFireNotifications(store.state.fireNotifications);
}
@ -104,28 +105,28 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
if (action is UpdateYourLocationAction) {
if (action.loc.subscribed)
debounceLocationUpdate(
Duration(seconds: 2),
const Duration(seconds: 2),
() => api
.getFiresInLocation(
state: store.state,
lat: action.loc.lat,
lon: action.loc.lon,
distance: action.loc.distance)
.then((result) => store.dispatch(result)));
store.dispatch(new UpdatedYourLocationAction(action.loc));
.then((UpdateFireMapStatsAction result) => store.dispatch(result)));
store.dispatch(UpdatedYourLocationAction(action.loc));
persistYourLocations(store.state.yourLocations);
}
if (action is SubscribeConfirmAction) {
subscribeViaApi(store, action.loc, (sub) {
store.dispatch(new UpdateYourLocationAction(action.loc));
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(new UpdateYourLocationAction(action.loc));
store.dispatch(UpdateYourLocationAction(action.loc));
persistYourLocations(store.state.yourLocations);
});
}
@ -133,21 +134,21 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
if (action is ToggleSubscriptionAction) {
if (action.loc.subscribed) {
subscribeViaApi(store, action.loc,
(sub) => store.dispatch(new ToggledSubscriptionAction(sub)));
(YourLocation sub) => store.dispatch(ToggledSubscriptionAction(sub)));
} else {
unsubsViaApi(store, action.loc.id,
() => store.dispatch(new ToggledSubscriptionAction(action.loc)));
() => store.dispatch(ToggledSubscriptionAction(action.loc)));
}
}
if (action is ReadFireNotificationAction) {
store.dispatch(new ReadedFireNotificationAction(action.notif));
store.dispatch(ReadedFireNotificationAction(action.notif));
persistFireNotifications(store.state.fireNotifications);
}
if (action is FetchYourLocationsAction) {
// Use the api to fetch the YourLocations
loadYourLocations().then((localLocations) {
loadYourLocations().then((List<YourLocation> localLocations) {
api
.fetchYourLocations(store.state)
.then((List<YourLocation> subscribedLocations) {
@ -155,61 +156,63 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
// Our reducer will then update the State using these YourLocations.
print('Subscribed to: ${subscribedLocations.length}');
// unsubscribe all locally to sync the subs state
localLocations.forEach((location) => location.subscribed = false);
for (final YourLocation location in localLocations) {
location.subscribed = false;
}
print('Local persisted: ${localLocations.length}');
subscribedLocations.forEach((subsLoc) {
var locSubs = localLocations.firstWhere(
(localLocation) => localLocation.id == subsLoc.id, orElse: () {
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(new FetchYourLocationsSucceededAction(localLocations));
store.dispatch(FetchYourLocationsSucceededAction(localLocations));
persistYourLocations(localLocations);
localLocations.forEach((yl) {
for (final YourLocation yl in localLocations) {
api
.getFiresInLocation(
state: store.state,
lat: yl.lat,
lon: yl.lon,
distance: yl.distance)
.then((value) {
.then((UpdateFireMapStatsAction value) {
yl.currentNumFires = value.numFires;
store.dispatch(new UpdateYourLocationAction(yl));
store.dispatch(UpdateYourLocationAction(yl));
});
});
}
final Completer<Null>? completer = action.refreshCallback;
final Completer<void>? completer = action.refreshCallback;
completer?.complete(null);
});
}).catchError((Exception onError) {
// If it fails, dispatch a failure action. The reducer will
// update the state with the error.
store.dispatch(new FetchYourLocationsFailedAction(onError));
store.dispatch(FetchYourLocationsFailedAction(onError));
});
}
if (action is FetchFireNotificationsAction) {
loadFireNotifications().then((fireNotifications) {
loadFireNotifications().then((List<FireNotification> fireNotifications) {
int unread = 0;
for (FireNotification notif in fireNotifications) {
for (final FireNotification notif in fireNotifications) {
if (!notif.read) {
unread++;
}
}
store.dispatch(
new FetchFireNotificationsSucceededAction(fireNotifications, unread));
FetchFireNotificationsSucceededAction(fireNotifications, unread));
persistFireNotifications(fireNotifications);
});
}
if (action is FetchMonitoredAreasAction) {
api.getMonitoredAreas(state: store.state).then((result) {
api.getMonitoredAreas(state: store.state).then((List<Polyline> result) {
// store.dispatch()
store.dispatch(new FetchMonitoredAreasSucceededAction(result));
store.dispatch(FetchMonitoredAreasSucceededAction(result));
});
}
@ -217,11 +220,11 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
api
.markFalsePositive(store.state, store.state.user.token,
action.notif.sealed, action.type)
.then((result) {
.then((bool result) {
if (result) {
// Not necessary
getFiresStatsInFire(store, action.notif);
store.dispatch(new UpdatedFireNotificationAction(action.notif));
store.dispatch(UpdatedFireNotificationAction(action.notif));
}
});
}
@ -237,10 +240,10 @@ void getFiresStatsInLocation(Store<AppState> store, YourLocation loc) {
lat: loc.lat,
lon: loc.lon,
distance: loc.distance)
.then((result) {
.then((UpdateFireMapStatsAction result) {
store.dispatch(result);
loc.currentNumFires = result.numFires;
store.dispatch(new UpdateYourLocationAction(loc));
store.dispatch(UpdateYourLocationAction(loc));
});
}
@ -251,11 +254,11 @@ void getFiresStatsInFire(Store<AppState> store, FireNotification notif) {
lat: notif.lat,
lon: notif.lon,
distance: 1) // FalsePositive/server/publications.js
.then((result) => store.dispatch(result));
.then((UpdateFireMapStatsAction result) => store.dispatch(result));
}
void unsubsViaApi(Store<AppState> store, ObjectId id, onUnsubs) {
api.unsubscribe(store.state, id.hexString).then((res) {
api.unsubscribe(store.state, id.hexString).then((bool res) {
onUnsubs();
persistYourLocations(store.state.yourLocations);
});
@ -263,8 +266,8 @@ void unsubsViaApi(Store<AppState> store, ObjectId id, onUnsubs) {
void subscribeViaApi(
Store<AppState> store, YourLocation loc, Function(YourLocation) onSubs) {
api.subscribe(store.state, loc).then((subsId) {
YourLocation sub = loc;
api.subscribe(store.state, loc).then((String subsId) {
final YourLocation sub = loc;
// if (loc.id != subsId) {
sub.id = objectIdFromJson(subsId);
// }
@ -274,12 +277,12 @@ void subscribeViaApi(
}
void createUser(Store<AppState> 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((userId) {
store.dispatch(new OnUserCreatedAction(userId));
store.dispatch(new FetchYourLocationsAction());
store.dispatch(new FetchFireNotificationsAction());
store.dispatch(new FetchMonitoredAreasAction());
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());
});
}

View file

@ -1,61 +1,61 @@
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/fireMapState.dart';
import '../models/fireMapState.dart';
import '../models/fireNotification.dart';
import '../models/yourLocation.dart';
abstract class FiresMapActions {}
class UpdateYourLocationMapAction extends FiresMapActions {
final YourLocation loc;
UpdateYourLocationMapAction(
this.loc,
);
final YourLocation loc;
}
class UpdateFireMapStatsAction extends FiresMapActions {
int numFires;
List<dynamic> fires = [];
List<dynamic> falsePos = [];
List<dynamic> industries = [];
UpdateFireMapStatsAction(
{required this.numFires,
required this.fires,
required this.falsePos,
required this.industries});
int numFires;
List<dynamic> fires = <dynamic>[];
List<dynamic> falsePos = <dynamic>[];
List<dynamic> industries = <dynamic>[];
}
class ShowYourLocationMapAction extends FiresMapActions {
YourLocation loc;
ShowYourLocationMapAction(this.loc);
YourLocation loc;
}
class ShowFireNotificationMapAction extends FiresMapActions {
FireNotification notif;
ShowFireNotificationMapAction(this.notif);
FireNotification notif;
}
class EditYourLocationAction extends FiresMapActions {
YourLocation loc;
EditYourLocationAction(this.loc);
YourLocation loc;
}
class EditConfirmYourLocationAction extends FiresMapActions {
YourLocation loc;
EditConfirmYourLocationAction(this.loc);
YourLocation loc;
}
class EditCancelYourLocationAction extends FiresMapActions {
YourLocation loc;
EditCancelYourLocationAction(this.loc);
YourLocation loc;
}
class SelectMapLayerAction extends FiresMapActions {
final FireMapLayer layer;
SelectMapLayerAction(this.layer);
final FireMapLayer layer;
}

View file

@ -1,28 +1,29 @@
import 'package:redux/redux.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:objectid/objectid.dart';
import 'package:redux/redux.dart';
import '../models/fireMapState.dart';
import '../models/yourLocation.dart';
import 'actions.dart';
final fireMapReducer = combineReducers<FireMapState>([
new TypedReducer<FireMapState, ShowYourLocationMapAction>(
final Reducer<FireMapState> fireMapReducer = combineReducers<FireMapState>(<Reducer<FireMapState>>[
TypedReducer<FireMapState, ShowYourLocationMapAction>(
_showYourLocationMap),
new TypedReducer<FireMapState, ShowFireNotificationMapAction>(
TypedReducer<FireMapState, ShowFireNotificationMapAction>(
_showFireNotificationMap),
new TypedReducer<FireMapState, UpdateFireMapStatsAction>(
TypedReducer<FireMapState, UpdateFireMapStatsAction>(
_updateYourLocationMapStats),
new TypedReducer<FireMapState, SubscribeAction>(_subscribeYourLocationMap),
new TypedReducer<FireMapState, SubscribeConfirmAction>(
TypedReducer<FireMapState, SubscribeAction>(_subscribeYourLocationMap),
TypedReducer<FireMapState, SubscribeConfirmAction>(
_subscribeConfirmYourLocationMap),
new TypedReducer<FireMapState, UnSubscribeAction>(
TypedReducer<FireMapState, UnSubscribeAction>(
_unsubscribeYourLocationMap),
new TypedReducer<FireMapState, EditYourLocationAction>(_editYourLocationMap),
new TypedReducer<FireMapState, EditConfirmYourLocationAction>(
TypedReducer<FireMapState, EditYourLocationAction>(_editYourLocationMap),
TypedReducer<FireMapState, EditConfirmYourLocationAction>(
_editConfirmYourLocationMap),
new TypedReducer<FireMapState, EditCancelYourLocationAction>(
TypedReducer<FireMapState, EditCancelYourLocationAction>(
_editCancelYourLocationMap),
new TypedReducer<FireMapState, SelectMapLayerAction>(_selectMapLayer),
new TypedReducer<FireMapState, UpdateYourLocationMapAction>(
TypedReducer<FireMapState, SelectMapLayerAction>(_selectMapLayer),
TypedReducer<FireMapState, UpdateYourLocationMapAction>(
_updateYourLocationMap)
]);
@ -46,14 +47,13 @@ FireMapState _showYourLocationMap(
status: action.loc.subscribed
? FireMapStatus.unsubscribe
: FireMapStatus.view,
yourLocation: action.loc,
fireNotication: null);
yourLocation: action.loc);
}
FireMapState _showFireNotificationMap(
FireMapState state, ShowFireNotificationMapAction action) {
// TODO: use here you real location?
YourLocation pseudoLoc = new YourLocation(
final YourLocation pseudoLoc = YourLocation(
id: ObjectId(),
lat: action.notif.lat,
lon: action.notif.lon,

View file

@ -1,12 +1,12 @@
import 'package:fires_flutter/models/fireNotification.dart';
import '../models/falsePositiveTypes.dart';
import '../models/fireNotification.dart';
abstract class FireNotificationActions {}
class DeleteFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
DeleteFireNotificationAction(this.notif);
final FireNotification notif;
}
class DeleteAllFireNotificationAction extends FireNotificationActions {
@ -14,15 +14,15 @@ class DeleteAllFireNotificationAction extends FireNotificationActions {
}
class AddFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
AddFireNotificationAction(this.notif);
final FireNotification notif;
}
class DeletedFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
DeletedFireNotificationAction(this.notif);
final FireNotification notif;
}
class DeletedAllFireNotificationAction extends FireNotificationActions {
@ -30,32 +30,32 @@ class DeletedAllFireNotificationAction extends FireNotificationActions {
}
class AddedFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
AddedFireNotificationAction(this.notif);
final FireNotification notif;
}
class ReadFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
ReadFireNotificationAction(this.notif);
final FireNotification notif;
}
class ReadedFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
ReadedFireNotificationAction(this.notif);
final FireNotification notif;
}
class MarkFireAsFalsePositiveAction extends FireNotificationActions {
final FireNotification notif;
final FalsePositiveType type;
MarkFireAsFalsePositiveAction(this.notif, this.type);
final FireNotification notif;
final FalsePositiveType type;
}
class UpdatedFireNotificationAction extends FireNotificationActions {
final FireNotification notif;
UpdatedFireNotificationAction(this.notif);
final FireNotification notif;
}

View file

@ -1,44 +1,44 @@
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:redux/redux.dart';
import '../models/fireNotification.dart';
import 'actions.dart';
final fireNotificationReducer = combineReducers<List<FireNotification>>([
new TypedReducer<List<FireNotification>, AddedFireNotificationAction>(
final Reducer<List<FireNotification>> fireNotificationReducer = combineReducers<List<FireNotification>>(<Reducer<List<FireNotification>>>[
TypedReducer<List<FireNotification>, AddedFireNotificationAction>(
_addedFireNotification),
new TypedReducer<List<FireNotification>, DeletedFireNotificationAction>(
TypedReducer<List<FireNotification>, DeletedFireNotificationAction>(
_deletedFireNotification),
new TypedReducer<List<FireNotification>, UpdatedFireNotificationAction>(
TypedReducer<List<FireNotification>, UpdatedFireNotificationAction>(
_updatedFireNotification),
new TypedReducer<List<FireNotification>, DeletedAllFireNotificationAction>(
TypedReducer<List<FireNotification>, DeletedAllFireNotificationAction>(
_deletedAllFireNotifications),
new TypedReducer<List<FireNotification>, ReadedFireNotificationAction>(
TypedReducer<List<FireNotification>, ReadedFireNotificationAction>(
_readedFireNotification)
]);
List<FireNotification> _addedFireNotification(
List<FireNotification> notifications, AddedFireNotificationAction action) {
return new List.from(notifications)..insert(0, action.notif);
return List.from(notifications)..insert(0, action.notif);
}
List<FireNotification> _deletedFireNotification(
List<FireNotification> notifications,
DeletedFireNotificationAction action) {
return new List.from(notifications)..remove(action.notif);
return List.from(notifications)..remove(action.notif);
}
List<FireNotification> _updatedFireNotification(
List<FireNotification> notifications,
UpdatedFireNotificationAction action) {
return notifications
.map((notif) => notif.id == action.notif.id ? action.notif : notif)
.map((FireNotification notif) => notif.id == action.notif.id ? action.notif : notif)
.toList();
}
List<FireNotification> _readedFireNotification(
List<FireNotification> notifications, ReadedFireNotificationAction action) {
return notifications
.map((yourLocation) =>
.map((FireNotification yourLocation) =>
yourLocation.id == action.notif.id ? action.notif : yourLocation)
.toList();
}
@ -46,5 +46,5 @@ List<FireNotification> _readedFireNotification(
List<FireNotification> _deletedAllFireNotifications(
List<FireNotification> notifications,
DeletedAllFireNotificationAction action) {
return [];
return <FireNotification>[];
}

View file

@ -10,7 +10,7 @@ import 'yourLocationsReducer.dart';
// We create the State reducer by combining many smaller reducers into one!
AppState appStateReducer(AppState prevState, action) {
var state = appReducer(prevState, action);
final AppState state = appReducer(prevState, action);
return AppState(
yourLocations: yourLocationsReducer(state.yourLocations, action),
fireNotifications:

View file

@ -1,5 +1,5 @@
import 'actions.dart';
import '../models/user.dart';
import 'actions.dart';
User userReducer(User user, action) {
if (action is OnUserCreatedAction) return user.copyWith(userId: action.userId);

View file

@ -1,66 +1,67 @@
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:objectid/objectid.dart';
import '../models/yourLocation.dart';
abstract class YourLocationActions {}
class AddYourLocationAction extends YourLocationActions {
YourLocation loc;
AddYourLocationAction(this.loc);
YourLocation loc;
}
class AddedYourLocationAction extends YourLocationActions {
YourLocation loc;
AddedYourLocationAction(this.loc);
YourLocation loc;
}
class DeleteYourLocationAction extends YourLocationActions {
YourLocation loc;
DeleteYourLocationAction(this.loc);
YourLocation loc;
}
class DeletedYourLocationAction extends YourLocationActions {
ObjectId id;
DeletedYourLocationAction(this.id);
ObjectId id;
}
class UpdateYourLocationAction extends YourLocationActions {
YourLocation loc;
UpdateYourLocationAction(this.loc);
YourLocation loc;
}
class UpdatedYourLocationAction extends YourLocationActions {
YourLocation loc;
UpdatedYourLocationAction(this.loc);
YourLocation loc;
}
class ToggleSubscriptionAction extends YourLocationActions {
YourLocation loc;
ToggleSubscriptionAction(this.loc);
YourLocation loc;
}
class ToggledSubscriptionAction extends YourLocationActions {
YourLocation loc;
ToggledSubscriptionAction(this.loc);
YourLocation loc;
}
class SubscribeAction extends YourLocationActions {}
class SubscribeConfirmAction extends YourLocationActions {
YourLocation loc;
SubscribeConfirmAction(this.loc);
YourLocation loc;
}
class UnSubscribeAction extends YourLocationActions {
YourLocation loc;
UnSubscribeAction(this.loc);
YourLocation loc;
}

View file

@ -1,35 +1,35 @@
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:redux/redux.dart';
import '../models/yourLocation.dart';
import 'actions.dart';
final yourLocationsReducer = combineReducers<List<YourLocation>>([
new TypedReducer<List<YourLocation>, AddedYourLocationAction>(
final Reducer<List<YourLocation>> yourLocationsReducer = combineReducers<List<YourLocation>>(<Reducer<List<YourLocation>>>[
TypedReducer<List<YourLocation>, AddedYourLocationAction>(
_addedYourLocation),
new TypedReducer<List<YourLocation>, DeletedYourLocationAction>(
TypedReducer<List<YourLocation>, DeletedYourLocationAction>(
_deletedYourLocation),
new TypedReducer<List<YourLocation>, UpdatedYourLocationAction>(
TypedReducer<List<YourLocation>, UpdatedYourLocationAction>(
_updatedYourLocation),
new TypedReducer<List<YourLocation>, ToggledSubscriptionAction>(
TypedReducer<List<YourLocation>, ToggledSubscriptionAction>(
_toggledSubscriptionAction)
]);
List<YourLocation> _addedYourLocation(
List<YourLocation> yourLocations, AddedYourLocationAction action) {
return new List.from(yourLocations)..add(action.loc);
return List.from(yourLocations)..add(action.loc);
}
List<YourLocation> _deletedYourLocation(
List<YourLocation> yourLocations, DeletedYourLocationAction action) {
return yourLocations
.where((yourLocation) => yourLocation.id != action.id)
.where((YourLocation yourLocation) => yourLocation.id != action.id)
.toList();
}
List<YourLocation> _updatedYourLocation(
List<YourLocation> yourLocations, UpdatedYourLocationAction action) {
return yourLocations
.map((yourLocation) =>
.map((YourLocation yourLocation) =>
yourLocation.id == action.loc.id ? action.loc : yourLocation)
.toList();
}
@ -37,7 +37,7 @@ List<YourLocation> _updatedYourLocation(
List<YourLocation> _toggledSubscriptionAction(
List<YourLocation> yourLocations, ToggledSubscriptionAction action) {
return yourLocations
.map((yourLocation) =>
.map((YourLocation yourLocation) =>
yourLocation.id == action.loc.id ? action.loc : yourLocation)
.toList();
}

View file

@ -7,31 +7,32 @@ new RandomColorBlock( child
*/
class Sandbox extends StatelessWidget {
const Sandbox({super.key});
static const String routeName = '/sandbox';
@override
Widget build(BuildContext context) {
//showDialog(context: context, child: builder(context));
return Scaffold(
appBar: new AppBar(
title: new TextField(
appBar: AppBar(
title: TextField(
autofocus: true,
controller: new TextEditingController(text: "kk"),
decoration: new InputDecoration(),
onSubmitted: (todoText) {},
controller: TextEditingController(text: 'kk'),
onSubmitted: (String todoText) {},
)),
body: new ElevatedButton(
child: new Text('Press'),
body: ElevatedButton(
child: const Text('Press'),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return new SimpleDialog(
title: new Text('title'),
builder: (BuildContext context) {
return const SimpleDialog(
title: Text('title'),
children: <Widget>[
new Padding(
padding: const EdgeInsets.only(left: 16.0),
child: new Text('hhh'),
Padding(
padding: EdgeInsets.only(left: 16.0),
child: Text('hhh'),
),
]);
});

View file

@ -4,9 +4,9 @@ import 'package:sentry_flutter/sentry_flutter.dart';
import 'globals.dart' as globals;
var useSentry = !globals.isDevelopment;
bool useSentry = !globals.isDevelopment;
Future<Null> reportError(dynamic error, dynamic stackTrace) async {
Future<void> reportError(dynamic error, dynamic stackTrace) async {
// Print the exception to the console
print('Caught error: $error');

View file

@ -4,61 +4,61 @@ import 'package:flutter/material.dart';
import 'colors.dart';
import 'generated/i18n.dart';
typedef void SlideCallback(int distance);
typedef SlideCallback = void Function(int distance);
class FireDistanceSlider extends StatefulWidget {
const FireDistanceSlider({super.key, required this.initialValue, required this.onSlide});
final int initialValue;
final SlideCallback onSlide;
FireDistanceSlider({required this.initialValue, required this.onSlide});
@override
_FireDistanceSliderState createState() => new _FireDistanceSliderState(
_FireDistanceSliderState createState() => _FireDistanceSliderState(
initialValue: initialValue, onSlide: onSlide);
}
class _FireDistanceSliderState extends State<FireDistanceSlider> {
_FireDistanceSliderState({int initialValue = 10, required this.onSlide}) {
_sliderValue = initialValue;
}
late int _sliderValue;
final SlideCallback onSlide;
_FireDistanceSliderState({int initialValue = 10, required this.onSlide}) {
this._sliderValue = initialValue;
}
Widget sizeText(int sliderValue) => new Text(
Widget sizeText(int sliderValue) => Text(
S.of(context).subscribeToValueAroundThisArea(sliderValue.toString()),
style: new TextStyle(color: Colors.black87));
style: const TextStyle(color: Colors.black87));
Widget warningText(int sliderValue) => _sliderValue >= 50
? new Text(S.of(context).warningThisIsAVeryLargeArea,
style: new TextStyle(color: fires900))
: new Text('');
? Text(S.of(context).warningThisIsAVeryLargeArea,
style: const TextStyle(color: fires900))
: const Text('');
@override
Widget build(BuildContext context) {
var slider = new Slider(
final Slider slider = Slider(
value: _sliderValue + 0.0,
activeColor: fires900,
inactiveColor: Colors.black45,
min: 1.0,
max: 100.0,
divisions: 99,
label: '${_sliderValue.round()}',
label: '$_sliderValue',
onChanged: (double value) {
_sliderValue = value.round();
onSlide(_sliderValue);
setState(() {});
},
);
return new Column(
return Column(
mainAxisSize: MainAxisSize.min,
children: listWithoutNulls(<Widget>[
new SizedBox(height: 50.0),
new Row(mainAxisSize: MainAxisSize.max, children: <Widget>[slider]),
const SizedBox(height: 50.0),
Row(children: <Widget>[slider]),
// new SizedBox(height: 5.0),
new Column(children: <Widget>[
Column(children: <Widget>[
sizeText(_sliderValue),
new SizedBox(height: 5.0),
const SizedBox(height: 5.0),
warningText(_sliderValue)
])
]),

View file

@ -1,13 +1,15 @@
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart';
import 'package:in_app_review/in_app_review.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'generated/i18n.dart';
import 'mainDrawer.dart';
class SupportPage extends StatefulWidget {
const SupportPage({super.key});
static const String routeName = '/support';
@override
@ -15,27 +17,27 @@ class SupportPage extends StatefulWidget {
}
class _SupportPageState extends State<SupportPage> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
Widget buildSupportButton() {
return new Align(
return Align(
alignment: const Alignment(0.0, -0.2),
child: new OutlinedButton.icon(
child: OutlinedButton.icon(
icon: const Icon(Icons.favorite_border),
label: new Text(S.of(context).comunesSupportBtn),
label: Text(S.of(context).comunesSupportBtn),
onPressed: () {
launch("https://comunes.org/");
launch('https://comunes.org/');
},
),
);
}
Widget buildShareButton() {
return new Align(
return Align(
alignment: const Alignment(0.0, -0.2),
child: new OutlinedButton.icon(
child: OutlinedButton.icon(
icon: const Icon(Icons.share),
label: new Text(S.of(context).shareAppBtn),
label: Text(S.of(context).shareAppBtn),
onPressed: () {
Share.shareWithResult(
'https://play.google.com/store/apps/details?id=org.comunes.fires');
@ -45,11 +47,11 @@ class _SupportPageState extends State<SupportPage> {
}
Widget buildStarButton() {
return new Align(
return Align(
alignment: const Alignment(0.0, -0.2),
child: new OutlinedButton.icon(
child: OutlinedButton.icon(
icon: const Icon(Icons.star_border),
label: new Text(S.of(context).starAppBtn),
label: Text(S.of(context).starAppBtn),
onPressed: () {
InAppReview.instance.requestReview();
},
@ -58,14 +60,14 @@ class _SupportPageState extends State<SupportPage> {
}
Widget buildTranslateButton() {
return new Align(
return Align(
alignment: const Alignment(0.0, -0.2),
child: new OutlinedButton.icon(
child: OutlinedButton.icon(
icon: const Icon(Icons.translate),
label: new Text(S.of(context).translateBtn),
label: Text(S.of(context).translateBtn),
onPressed: () {
launch(
"https://translate.comunes.org/projects/todos-contra-el-fuego/");
'https://translate.comunes.org/projects/todos-contra-el-fuego/');
},
),
);
@ -76,24 +78,24 @@ class _SupportPageState extends State<SupportPage> {
return Scaffold(
key: _scaffoldKey,
appBar:
new AppBar(title: new Text(S.of(context).supportThisInitiative)),
drawer: new MainDrawer(context, SupportPage.routeName),
AppBar(title: Text(S.of(context).supportThisInitiative)),
drawer: MainDrawer(context, SupportPage.routeName),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: new CenteredColumn(children: <Widget>[
new Flexible(
child: new CenteredColumn(children: <Widget>[
new Text(
child: CenteredColumn(children: <Widget>[
Flexible(
child: CenteredColumn(children: <Widget>[
Text(
S.of(context).supportPageDescription,
textAlign: TextAlign.center,
),
new SizedBox(height: 20.0),
const SizedBox(height: 20.0),
buildSupportButton(),
new SizedBox(height: 20.0),
const SizedBox(height: 20.0),
buildTranslateButton(),
new SizedBox(height: 20.0),
const SizedBox(height: 20.0),
buildStarButton(),
new SizedBox(height: 20.0),
const SizedBox(height: 20.0),
buildShareButton()
]))
]),

View file

@ -10,16 +10,14 @@ ThemeData _buildFiresTheme() {
primaryColor: fires600,
scaffoldBackgroundColor: firesSurfaceWhite,
cardColor: firesBackgroundWhite,
textSelectionTheme: TextSelectionThemeData(selectionColor: fires300),
colorScheme: ColorScheme.light(
textSelectionTheme: const TextSelectionThemeData(selectionColor: fires300),
colorScheme: const ColorScheme.light(
primary: fires600,
secondary: fires900,
error: firesErrorRed,
surface: firesSurfaceWhite,
onSurface: fires900,
onPrimary: Colors.white,
onSecondary: Colors.white,
onError: Colors.white,
),
//TODO: Add the text themes (103)
//TODO: Add the icon themes (103)

View file

@ -10,16 +10,14 @@ ThemeData _buildFiresTheme() {
primaryColor: Colors.pink,
scaffoldBackgroundColor: firesSurfaceWhite,
cardColor: firesBackgroundWhite,
textSelectionTheme: TextSelectionThemeData(selectionColor: fires300),
colorScheme: ColorScheme.light(
textSelectionTheme: const TextSelectionThemeData(selectionColor: fires300),
colorScheme: const ColorScheme.light(
primary: Colors.pink,
secondary: fires900,
error: firesErrorRed,
surface: firesSurfaceWhite,
onSurface: fires900,
onPrimary: Colors.white,
onSecondary: Colors.white,
onError: Colors.white,
),
//TODO: Add the text themes (103)
//TODO: Add the icon themes (103)

View file

@ -1,20 +1,23 @@
import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
/// Zoom control widget for flutter_map v6+
class ZoomMapPluginWidget extends StatelessWidget {
const ZoomMapPluginWidget({super.key});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) =>
builder: (BuildContext context, BoxConstraints constraints) =>
Stack(fit: StackFit.expand, children: <Widget>[
Positioned(
top: constraints.maxHeight - 100,
right: 10.0,
child: new CenteredRow(
child: CenteredRow(
children: <Widget>[
new Column(
Column(
children: <Widget>[
_zoomButton(context, Icons.zoom_in, 1),
_zoomButton(context, Icons.zoom_out, -1)
@ -27,12 +30,12 @@ class ZoomMapPluginWidget extends StatelessWidget {
}
IconButton _zoomButton(BuildContext context, IconData zoomIcon, int inc) {
return new IconButton(
return IconButton(
icon: Icon(zoomIcon),
onPressed: () {
final controller = MapController.of(context);
var currentZoom = controller.camera.zoom;
var currentCenter = controller.camera.center;
final MapController controller = MapController.of(context);
final double currentZoom = controller.camera.zoom;
final LatLng currentCenter = controller.camera.center;
controller.move(currentCenter, currentZoom + inc);
});
}

View file

@ -1,29 +1,29 @@
import 'package:test/test.dart';
import 'package:fires_flutter/fileUtils.dart';
import 'package:test/test.dart';
void main() {
test('test es-ES fallback', () {
String answer = getFallbackLang('es-ES');
final String answer = getFallbackLang('es-ES');
expect(answer, 'es');
});
test('test en-GB fallback', () {
String answer = getFallbackLang('en-GB');
final String answer = getFallbackLang('en-GB');
expect(answer, 'en');
});
test('test gl fallback', () {
String answer = getFallbackLang('gl');
final String answer = getFallbackLang('gl');
expect(answer, 'es');
});
test('test fr fallback', () {
String answer = getFallbackLang('fr');
final String answer = getFallbackLang('fr');
expect(answer, 'en');
});
test('test privacy English md page', () async {
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');
});