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:
parent
1864e5b105
commit
7a4c9fb3bc
61 changed files with 1386 additions and 1202 deletions
181
analysis_options.yaml
Normal file
181
analysis_options.yaml
Normal 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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.'),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export 'appActions.dart';
|
||||
export 'yourLocationActions.dart';
|
||||
export 'fireMapActions.dart';
|
||||
export 'fireNotificationActions.dart';
|
||||
export 'fireNotificationActions.dart';
|
||||
export 'yourLocationActions.dart';
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'actions.dart';
|
||||
import '../models/appState.dart';
|
||||
import 'actions.dart';
|
||||
|
||||
AppState appReducer(AppState state, action) {
|
||||
if (action is FetchYourLocationsSucceededAction) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
])
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
]))
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue