Last changes not published

This commit is contained in:
vjrj 2026-03-05 01:18:27 +01:00
parent ac65ccf990
commit 21ec08303a
43 changed files with 607 additions and 613 deletions

View file

@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) {
def flutterRoot = localProperties.getProperty('flutter.sdk') def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) { if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
} }
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
@ -19,7 +19,7 @@ def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android { android {
compileSdkVersion 27 compileSdkVersion 30
lintOptions { lintOptions {
disable 'InvalidPackage' disable 'InvalidPackage'
@ -29,7 +29,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "org.comunes.fires" applicationId "org.comunes.fires"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 27 targetSdkVersion 30
versionCode 9 versionCode 9
versionName "1.9" versionName "1.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@ -78,7 +78,7 @@ dependencies {
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation 'com.google.firebase:firebase-core:15.0.2' // implementation 'com.google.firebase:firebase-core:15.0.2'
// google recommends 16 but fails because location flutter package deps in 15 // google recommends 16 but fails because location flutter package deps in 15
} }

View file

@ -1,14 +1,6 @@
package org.comunes.fires; package org.comunes.fires;
import android.os.Bundle; import io.flutter.embedding.android.FlutterActivity;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity { public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
} }

View file

@ -5,4 +5,8 @@
Flutter draws its first frame --> Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/normal_background</item>
</style>
</resources> </resources>

View file

@ -5,8 +5,8 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.google.gms:google-services:4.0.1' classpath 'com.google.gms:google-services:4.3.4'
} }
} }

View file

@ -1 +1,3 @@
org.gradle.jvmargs=-Xmx1536M org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View file

@ -9,11 +9,9 @@ targets:
# defined in `package:source_gen/source_gen.dart`. # defined in `package:source_gen/source_gen.dart`.
# #
# Note: use `|` to define a multi-line block. # Note: use `|` to define a multi-line block.
header: | # header: |
// Copyright (c) 2018, Comunes Association. # // Copyright (c) 2018, Comunes Association.
# // GENERATED CODE - DO NOT MODIFY BY HAND
// GENERATED CODE - DO NOT MODIFY BY HAND
# Options configure how source code is generated for every # Options configure how source code is generated for every
# `@JsonSerializable`-annotated class in the package. # `@JsonSerializable`-annotated class in the package.
# #
@ -21,8 +19,9 @@ targets:
# #
# For usage information, reference the corresponding field in # For usage information, reference the corresponding field in
# `JsonSerializableGenerator`. # `JsonSerializableGenerator`.
use_wrappers: true #use_wrappers: true
any_map: true any_map: true
checked: true checked: true
explicit_to_json: true explicit_to_json: true
generate_to_json_function: true #generate_to_json_function: true
create_to_json: true

View file

@ -5,7 +5,7 @@ import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_fab_dialer/flutter_fab_dialer.dart'; import 'package:flutter_fab_dialer/flutter_fab_dialer.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/src/store.dart'; import 'package:redux/redux.dart';
import 'colors.dart'; import 'colors.dart';
import 'firesSpinner.dart'; import 'firesSpinner.dart';
@ -91,7 +91,7 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
icon: new Icon(loc.subscribed icon: new Icon(loc.subscribed
? Icons.notifications_active ? Icons.notifications_active
: Icons.notifications_off), : Icons.notifications_off),
color: loc.subscribed ? fires600: null, color: loc.subscribed ? fires600 : null,
onPressed: () { onPressed: () {
loc.subscribed = !loc.subscribed; loc.subscribed = !loc.subscribed;
onToggle(loc); onToggle(loc);
@ -125,7 +125,8 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
} }
void showSnackMsg(String msg) { void showSnackMsg(String msg) {
_scaffoldKey.currentState.showSnackBar(new SnackBar( // _scaffoldKey.currentState.showSnackBar(new SnackBar(
ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(msg), content: new Text(msg),
)); ));
} }
@ -183,7 +184,7 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
}, },
onDelete: (loc) { onDelete: (loc) {
store.dispatch(new DeleteYourLocationAction(loc)); store.dispatch(new DeleteYourLocationAction(loc));
_scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).youDeletedThisPlace), content: new Text(S.of(context).youDeletedThisPlace),
action: new SnackBarAction( action: new SnackBarAction(
label: S.of(context).UNDO, label: S.of(context).UNDO,
@ -242,6 +243,7 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
view.onRefresh(completer); view.onRefresh(completer);
return completer.future; return completer.future;
}), }),
// TODO: Evaluate: https://github.com/tiagojencmartins/unicornspeeddial
new FabDialer(_fabMiniMenuItemList(context, view.onAdd), new FabDialer(_fabMiniMenuItemList(context, view.onAdd),
fires600, new Icon(Icons.add)) fires600, new Icon(Icons.add))
]) ])

View file

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_map/plugin_api.dart'; import 'package:flutter_map/plugin_api.dart';
class AttributionPluginOptions extends LayerOptions { class AttributionPluginOptions extends LayerOptions {
@ -10,7 +9,8 @@ class AttributionPluginOptions extends LayerOptions {
class AttributionPlugin implements MapPlugin { class AttributionPlugin implements MapPlugin {
@override @override
Widget createLayer(LayerOptions options, MapState mapState) { Widget createLayer(
LayerOptions options, MapState mapState, Stream<Null> stream) {
if (options is AttributionPluginOptions) { if (options is AttributionPluginOptions) {
var style = new TextStyle( var style = new TextStyle(
// fontWeight: FontWeight.bold, // fontWeight: FontWeight.bold,

View file

@ -34,12 +34,17 @@ class CustomBottomAppBar extends StatelessWidget {
rowContents.addAll(this.actions); rowContents.addAll(this.actions);
return new BottomAppBar( return showNotch
color: color, ? new BottomAppBar(
hasNotch: showNotch, color: color,
child: new Row( shape: CircularNotchedRectangle(),
mainAxisAlignment: mainAxisAlignment, child: new Row(
children: rowContents), mainAxisAlignment: mainAxisAlignment, children: rowContents),
); )
: new BottomAppBar(
color: color,
child: new Row(
mainAxisAlignment: mainAxisAlignment, children: rowContents),
);
} }
} }

View file

@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// TODO(dragostis): Missing functionality: // TODO(dragostis): Missing functionality:
// * mobile horizontal mode with adding/removing steps // * mobile horizontal mode with adding/removing steps
// * alternative labeling // * alternative labeling
@ -53,7 +52,8 @@ const Color _kCircleActiveDark = Colors.black87;
const Color _kDisabledLight = Colors.black38; const Color _kDisabledLight = Colors.black38;
const Color _kDisabledDark = Colors.white30; const Color _kDisabledDark = Colors.white30;
const double _kCustomStepSize = 24.0; const double _kCustomStepSize = 24.0;
const double _kTriangleHeight = _kCustomStepSize * 0.866025; // Triangle height. sqrt(3.0) / 2.0 const double _kTriangleHeight =
_kCustomStepSize * 0.866025; // Triangle height. sqrt(3.0) / 2.0
/// A material step used in [CustomStepper]. The step can have a title and subtitle, /// A material step used in [CustomStepper]. The step can have a title and subtitle,
/// an icon within its circle, some content and a state that governs its /// an icon within its circle, some content and a state that governs its
@ -75,9 +75,9 @@ class CustomStep {
@required this.content, @required this.content,
this.state: CustomStepState.indexed, this.state: CustomStepState.indexed,
this.isActive: false, this.isActive: false,
}) : assert(title != null), }) : assert(title != null),
assert(content != null), assert(content != null),
assert(state != null); assert(state != null);
/// The title of the step that typically describes it. /// The title of the step that typically describes it.
final Widget title; final Widget title;
@ -130,11 +130,11 @@ class CustomStepper extends StatefulWidget {
this.onCustomStepTapped, this.onCustomStepTapped,
this.onCustomStepContinue, this.onCustomStepContinue,
this.onCustomStepCancel, this.onCustomStepCancel,
}) : assert(steps != null), }) : assert(steps != null),
assert(type != null), assert(type != null),
assert(currentCustomStep != null), assert(currentCustomStep != null),
assert(0 <= currentCustomStep && currentCustomStep < steps.length), assert(0 <= currentCustomStep && currentCustomStep < steps.length),
super(key: key); super(key: key);
/// The steps of the stepper whose titles, subtitles, icons always get shown. /// The steps of the stepper whose titles, subtitles, icons always get shown.
/// ///
@ -177,7 +177,7 @@ class _CustomStepperState extends State<CustomStepper> {
super.initState(); super.initState();
_keys = new List<GlobalKey>.generate( _keys = new List<GlobalKey>.generate(
widget.steps.length, widget.steps.length,
(int i) => new GlobalKey(), (int i) => new GlobalKey(),
); );
for (int i = 0; i < widget.steps.length; i += 1) for (int i = 0; i < widget.steps.length; i += 1)
@ -218,7 +218,8 @@ class _CustomStepperState extends State<CustomStepper> {
} }
Widget _buildCircleChild(int index, bool oldState) { Widget _buildCircleChild(int index, bool oldState) {
final CustomStepState state = oldState ? _oldStates[index] : widget.steps[index].state; final CustomStepState state =
oldState ? _oldStates[index] : widget.steps[index].state;
final bool isDarkActive = _isDark() && widget.steps[index].isActive; final bool isDarkActive = _isDark() && widget.steps[index].isActive;
assert(state != null); assert(state != null);
switch (state) { switch (state) {
@ -226,7 +227,9 @@ class _CustomStepperState extends State<CustomStepper> {
case CustomStepState.disabled: case CustomStepState.disabled:
return new Text( return new Text(
'${index + 1}', '${index + 1}',
style: isDarkActive ? _kCustomStepStyle.copyWith(color: Colors.black87) : _kCustomStepStyle, style: isDarkActive
? _kCustomStepStyle.copyWith(color: Colors.black87)
: _kCustomStepStyle,
); );
case CustomStepState.editing: case CustomStepState.editing:
return new Icon( return new Icon(
@ -247,9 +250,13 @@ class _CustomStepperState extends State<CustomStepper> {
Color _circleColor(int index) { Color _circleColor(int index) {
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
if (!_isDark()) { if (!_isDark()) {
return widget.steps[index].isActive ? themeData.primaryColor : Colors.black38; return widget.steps[index].isActive
? themeData.primaryColor
: Colors.black38;
} else { } else {
return widget.steps[index].isActive ? themeData.accentColor : themeData.backgroundColor; return widget.steps[index].isActive
? themeData.accentColor
: themeData.backgroundColor;
} }
} }
@ -266,7 +273,8 @@ class _CustomStepperState extends State<CustomStepper> {
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: new Center( child: new Center(
child: _buildCircleChild(index, oldState && widget.steps[index].state == CustomStepState.error), child: _buildCircleChild(index,
oldState && widget.steps[index].state == CustomStepState.error),
), ),
), ),
); );
@ -280,14 +288,19 @@ class _CustomStepperState extends State<CustomStepper> {
child: new Center( child: new Center(
child: new SizedBox( child: new SizedBox(
width: _kCustomStepSize, width: _kCustomStepSize,
height: _kTriangleHeight, // Height of 24dp-long-sided equilateral triangle. height:
_kTriangleHeight, // Height of 24dp-long-sided equilateral triangle.
child: new CustomPaint( child: new CustomPaint(
painter: new _TrianglePainter( painter: new _TrianglePainter(
color: _isDark() ? _kErrorDark : _kErrorLight, color: _isDark() ? _kErrorDark : _kErrorLight,
), ),
child: new Align( child: new Align(
alignment: const Alignment(0.0, 0.8), // 0.8 looks better than the geometrical 0.33. alignment: const Alignment(
child: _buildCircleChild(index, oldState && widget.steps[index].state != CustomStepState.error), 0.0, 0.8), // 0.8 looks better than the geometrical 0.33.
child: _buildCircleChild(
index,
oldState &&
widget.steps[index].state != CustomStepState.error),
), ),
), ),
), ),
@ -303,7 +316,9 @@ class _CustomStepperState extends State<CustomStepper> {
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn), firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn), secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn, sizeCurve: Curves.fastOutSlowIn,
crossFadeState: widget.steps[index].state == CustomStepState.error ? CrossFadeState.showSecond : CrossFadeState.showFirst, crossFadeState: widget.steps[index].state == CustomStepState.error
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration, duration: kThemeAnimationDuration,
); );
} else { } else {
@ -328,8 +343,9 @@ class _CustomStepperState extends State<CustomStepper> {
assert(cancelColor != null); assert(cancelColor != null);
final ThemeData themeData = Theme.of(context); // final ThemeData themeData = Theme.of(context);
final MaterialLocalizations localizations = MaterialLocalizations.of(context); // final MaterialLocalizations localizations =
// MaterialLocalizations.of(context);
return new Container( return new Container(
margin: const EdgeInsets.only(top: 16.0), margin: const EdgeInsets.only(top: 16.0),
@ -368,15 +384,13 @@ class _CustomStepperState extends State<CustomStepper> {
case CustomStepState.indexed: case CustomStepState.indexed:
case CustomStepState.editing: case CustomStepState.editing:
case CustomStepState.complete: case CustomStepState.complete:
return textTheme.body2; return textTheme.bodyText1;
case CustomStepState.disabled: case CustomStepState.disabled:
return textTheme.body2.copyWith( return textTheme.bodyText1
color: _isDark() ? _kDisabledDark : _kDisabledLight .copyWith(color: _isDark() ? _kDisabledDark : _kDisabledLight);
);
case CustomStepState.error: case CustomStepState.error:
return textTheme.body2.copyWith( return textTheme.bodyText1
color: _isDark() ? _kErrorDark : _kErrorLight .copyWith(color: _isDark() ? _kErrorDark : _kErrorLight);
);
} }
return null; return null;
} }
@ -392,13 +406,11 @@ class _CustomStepperState extends State<CustomStepper> {
case CustomStepState.complete: case CustomStepState.complete:
return textTheme.caption; return textTheme.caption;
case CustomStepState.disabled: case CustomStepState.disabled:
return textTheme.caption.copyWith( return textTheme.caption
color: _isDark() ? _kDisabledDark : _kDisabledLight .copyWith(color: _isDark() ? _kDisabledDark : _kDisabledLight);
);
case CustomStepState.error: case CustomStepState.error:
return textTheme.caption.copyWith( return textTheme.caption
color: _isDark() ? _kErrorDark : _kErrorLight .copyWith(color: _isDark() ? _kErrorDark : _kErrorLight);
);
} }
return null; return null;
} }
@ -427,33 +439,26 @@ class _CustomStepperState extends State<CustomStepper> {
); );
return new Column( return new Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: children children: children);
);
} }
Widget _buildVerticalHeader(int index) { Widget _buildVerticalHeader(int index) {
return new Container( return new Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0), margin: const EdgeInsets.symmetric(horizontal: 24.0),
child: new Row( child: new Row(children: <Widget>[
children: <Widget>[ new Column(children: <Widget>[
new Column( // Line parts are always added in order for the ink splash to
children: <Widget>[ // flood the tips of the connector lines.
// Line parts are always added in order for the ink splash to _buildLine(!_isFirst(index)),
// flood the tips of the connector lines. _buildIcon(index),
_buildLine(!_isFirst(index)), _buildLine(!_isLast(index)),
_buildIcon(index), ]),
_buildLine(!_isLast(index)),
]
),
new Container( new Container(
margin: const EdgeInsetsDirectional.only(start: 12.0), margin: const EdgeInsetsDirectional.only(start: 12.0),
child: _buildHeaderText(index) child: _buildHeaderText(index))
) ]));
]
)
);
} }
Widget _buildVerticalBody(int index) { Widget _buildVerticalBody(int index) {
@ -493,7 +498,9 @@ class _CustomStepperState extends State<CustomStepper> {
firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn), firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn), secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
sizeCurve: Curves.fastOutSlowIn, sizeCurve: Curves.fastOutSlowIn,
crossFadeState: _isCurrent(index) ? CrossFadeState.showSecond : CrossFadeState.showFirst, crossFadeState: _isCurrent(index)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: kThemeAnimationDuration, duration: kThemeAnimationDuration,
), ),
], ],
@ -504,29 +511,25 @@ class _CustomStepperState extends State<CustomStepper> {
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
for (int i = 0; i < widget.steps.length; i += 1) { for (int i = 0; i < widget.steps.length; i += 1) {
children.add( children.add(new Column(key: _keys[i], children: <Widget>[
new Column( new InkWell(
key: _keys[i], onTap: widget.steps[i].state != CustomStepState.disabled
children: <Widget>[ ? () {
new InkWell( // In the vertical case we need to scroll to the newly tapped
onTap: widget.steps[i].state != CustomStepState.disabled ? () { // step.
// In the vertical case we need to scroll to the newly tapped Scrollable.ensureVisible(
// step. _keys[i].currentContext,
Scrollable.ensureVisible( curve: Curves.fastOutSlowIn,
_keys[i].currentContext, duration: kThemeAnimationDuration,
curve: Curves.fastOutSlowIn, );
duration: kThemeAnimationDuration,
);
if (widget.onCustomStepTapped != null) if (widget.onCustomStepTapped != null)
widget.onCustomStepTapped(i); widget.onCustomStepTapped(i);
} : null, }
child: _buildVerticalHeader(i) : null,
), child: _buildVerticalHeader(i)),
_buildVerticalBody(i) _buildVerticalBody(i)
] ]));
)
);
} }
return new ListView( return new ListView(
@ -541,10 +544,12 @@ class _CustomStepperState extends State<CustomStepper> {
for (int i = 0; i < widget.steps.length; i += 1) { for (int i = 0; i < widget.steps.length; i += 1) {
children.add( children.add(
new InkResponse( new InkResponse(
onTap: widget.steps[i].state != CustomStepState.disabled ? () { onTap: widget.steps[i].state != CustomStepState.disabled
if (widget.onCustomStepTapped != null) ? () {
widget.onCustomStepTapped(i); if (widget.onCustomStepTapped != null)
} : null, widget.onCustomStepTapped(i);
}
: null,
child: new Row( child: new Row(
children: <Widget>[ children: <Widget>[
new Container( new Container(
@ -593,7 +598,7 @@ class _CustomStepperState extends State<CustomStepper> {
new AnimatedSize( new AnimatedSize(
curve: Curves.fastOutSlowIn, curve: Curves.fastOutSlowIn,
duration: kThemeAnimationDuration, duration: kThemeAnimationDuration,
//vsync: this, vsync: null,
child: widget.steps[widget.currentCustomStep].content, child: widget.steps[widget.currentCustomStep].content,
), ),
_buildVerticalControls(), _buildVerticalControls(),
@ -608,12 +613,11 @@ class _CustomStepperState extends State<CustomStepper> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context)); assert(debugCheckHasMaterial(context));
assert(() { assert(() {
if (context.ancestorWidgetOfExactType(CustomStepper) != null) if (context.findAncestorWidgetOfExactType<CustomStepper>() != null)
throw new FlutterError( throw new FlutterError(
'CustomSteppers must not be nested. The material specification advises ' 'CustomSteppers must not be nested. The material specification advises '
'that one should avoid embedding steppers within steppers. ' 'that one should avoid embedding steppers within steppers. '
'https://material.google.com/components/steppers.html#steppers-usage\n' 'https://material.google.com/components/steppers.html#steppers-usage\n');
);
return true; return true;
}()); }());
assert(widget.type != null); assert(widget.type != null);
@ -630,9 +634,7 @@ class _CustomStepperState extends State<CustomStepper> {
// Paints a triangle whose base is the bottom of the bounding rectangle and its // Paints a triangle whose base is the bottom of the bounding rectangle and its
// top vertex the middle of its top. // top vertex the middle of its top.
class _TrianglePainter extends CustomPainter { class _TrianglePainter extends CustomPainter {
_TrianglePainter({ _TrianglePainter({this.color});
this.color
});
final Color color; final Color color;

View file

@ -1,6 +1,5 @@
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/plugin_api.dart';
class DummyMapPluginOptions extends LayerOptions { class DummyMapPluginOptions extends LayerOptions {
final String text; final String text;
@ -10,9 +9,9 @@ class DummyMapPluginOptions extends LayerOptions {
// https://github.com/apptreesoftware/flutter_map/blob/master/flutter_map_example/lib/pages/plugin_api.dart // https://github.com/apptreesoftware/flutter_map/blob/master/flutter_map_example/lib/pages/plugin_api.dart
class DummyMapPlugin extends MapPlugin { class DummyMapPlugin extends MapPlugin {
@override @override
Widget createLayer(LayerOptions options, MapState mapState) { Widget createLayer(
LayerOptions options, MapState mapState, Stream<Null> stream) {
if (options is DummyMapPluginOptions) { if (options is DummyMapPluginOptions) {
return const SizedBox(); return const SizedBox();
} }

View file

@ -1,25 +1,34 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/services.dart' show rootBundle;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
final esRegExp = new RegExp("^es-"); final esRegExp = new RegExp("^es-");
String getFallbackLang(String lang) { String getFallbackLang(String lang) {
if (lang == 'ast' || lang == 'gl' || lang == 'eu' || if (lang == 'ast' ||
lang == 'es' || lang == 'ca' || (esRegExp).allMatches(lang).length > 0) { lang == 'gl' ||
lang == 'eu' ||
lang == 'es' ||
lang == 'ca' ||
(esRegExp).allMatches(lang).length > 0) {
return 'es'; return 'es';
} }
return 'en'; return 'en';
} }
Future<String> getFileNameOfLang({@required String dir,@required String fileName,@required String ext,@required String lang}) async { Future<String> getFileNameOfLang(
{@required String dir,
@required String fileName,
@required String ext,
@required String lang}) async {
String base = '$dir/$fileName'; String base = '$dir/$fileName';
String fallback = getFallbackLang(lang); String fallback = getFallbackLang(lang);
String file = '${base}-${lang}.${ext}'; String file = '$base-$lang.$ext';
if (await assetNotExists(file)) { if (await assetNotExists(file)) {
file = '${base}-${fallback}.${ext}'; file = '$base-$fallback.$ext';
if (await assetNotExists(file)) { if (await assetNotExists(file)) {
file = '${base}.${ext}'; file = '$base.$ext';
} }
} }
return file; return file;
@ -28,5 +37,9 @@ Future<String> getFileNameOfLang({@required String dir,@required String fileNam
// https://github.com/flutter/flutter/issues/15325 // https://github.com/flutter/flutter/issues/15325
Future<bool> assetNotExists(String asset) { Future<bool> assetNotExists(String asset) {
print('Does not exists $asset ?'); print('Does not exists $asset ?');
return rootBundle.load(asset).then((_) => false).catchError((err, stack) { print (err); print(stack); return true;}); return rootBundle.load(asset).then((_) => false).catchError((err, stack) {
print(err);
print(stack);
return true;
});
} }

View file

@ -3,10 +3,10 @@ import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:share/share.dart'; import 'package:share/share.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'customStepper.dart';
import 'mainDrawer.dart';
import 'customStepper.dart';
import 'generated/i18n.dart'; import 'generated/i18n.dart';
import 'mainDrawer.dart';
import 'placesAutocompleteUtils.dart'; import 'placesAutocompleteUtils.dart';
class FireAlert extends StatefulWidget { class FireAlert extends StatefulWidget {
@ -40,7 +40,7 @@ class _FireAlertState extends State<FireAlert> {
heroTag: 'neighAction', heroTag: 'neighAction',
child: const Icon(CommunityMaterialIcons.bullhorn), child: const Icon(CommunityMaterialIcons.bullhorn),
onPressed: () { onPressed: () {
_scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).inDevelopment), content: new Text(S.of(context).inDevelopment),
)); ));
}, },
@ -48,7 +48,6 @@ class _FireAlertState extends State<FireAlert> {
); );
} }
Widget buildTweetButton() { Widget buildTweetButton() {
return new Align( return new Align(
alignment: const Alignment(0.0, -0.2), alignment: const Alignment(0.0, -0.2),
@ -62,9 +61,11 @@ class _FireAlertState extends State<FireAlert> {
String where = String where =
yourLocation.description.replaceAll(' ', '').split(',')[0]; yourLocation.description.replaceAll(' ', '').split(',')[0];
print(where); print(where);
Share.share(S.of(context).tweetAboutSelf(yourLocation.description, '#IF${where}')); Share.share(S
.of(context)
.tweetAboutSelf(yourLocation.description, '#IF$where'));
}).catchError((onError) { }).catchError((onError) {
_scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text( content: new Text(
S.of(_scaffoldKey.currentContext).errorFirePlaceDialog))); S.of(_scaffoldKey.currentContext).errorFirePlaceDialog)));
}); });
@ -87,13 +88,13 @@ class _FireAlertState extends State<FireAlert> {
buildCallButton() buildCallButton()
])), ])),
new CustomStep( new CustomStep(
title: new Text(S.of(context).notifyNeighbours), title: new Text(S.of(context).notifyNeighbours),
// state: CustomStepState.disabled, // state: CustomStepState.disabled,
content: new Column(children: <Widget>[ content: new Column(children: <Widget>[
new Text(S.of(context).notifyNeighboursDescription), new Text(S.of(context).notifyNeighboursDescription),
new SizedBox(height: 20.0), new SizedBox(height: 20.0),
buildNotifyNeighboursButton() buildNotifyNeighboursButton()
])), ])),
// TODO conditional: this only in Spain // TODO conditional: this only in Spain
new CustomStep( new CustomStep(
title: new Text(S.of(context).tweetAboutAFireTitle), title: new Text(S.of(context).tweetAboutAFireTitle),

View file

@ -2,30 +2,33 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/plugin_api.dart'; import 'package:flutter_map/plugin_api.dart';
import 'package:latlong/latlong.dart'; import 'package:latlong/latlong.dart';
import 'fireMarkerIcon.dart';
import 'fireMarkType.dart'; import 'fireMarkType.dart';
import 'fireMarkerIcon.dart';
class FireMarker extends Marker { class FireMarker extends Marker {
FireMarker(LatLng pos, type, FireMarker(LatLng pos, type,
[onTap = null, AnchorPos anchor = AnchorPos.center, Anchor anchorOverride]) [onTap,
// AnchorPos anchor = AnchorPos.center,
Anchor anchorOverride])
: super( : super(
width: 80.0, width: 80.0,
height: 80.0, height: 80.0,
point: pos, point: pos,
builder: (ctx) => new Container( builder: (ctx) => new Container(
child: new GestureDetector( child: new GestureDetector(
child: new FireMarkerIcon(type), onTap: onTap) child: new FireMarkerIcon(type), onTap: onTap)),
), // anchor: anchor,
anchor: anchor, anchorPos: anchorOverride ?? type == FireMarkType.position
anchorOverride: anchorOverride ?? type == FireMarkType.position ? AnchorPos.exactly(new Anchor(40.0, 20.0))
? new Anchor(40.0, 20.0)
: type == FireMarkType.fire : type == FireMarkType.fire
? new Anchor(40.0, 24.0) ? AnchorPos.exactly(new Anchor(40.0, 24.0))
: type == FireMarkType.pixel : type == FireMarkType.pixel
? null // auto calculate with height/width in Marker ? null // auto calculate with height/width in Marker
// industries, etc (below) // industries, etc (below)
// FIXME check if this is accurate // FIXME check if this is accurate
: new Anchor(40.0, 35.0), : AnchorPos.exactly(new Anchor(40.0, 35.0)),
); ) {
// anchor = anchorOverride;
}
} }

View file

@ -5,15 +5,15 @@ import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/src/store.dart'; import 'package:redux/redux.dart';
import 'customMoment.dart'; import 'customMoment.dart';
import 'firesSpinner.dart';
import 'generated/i18n.dart'; import 'generated/i18n.dart';
import 'genericMap.dart'; import 'genericMap.dart';
import 'mainDrawer.dart'; import 'mainDrawer.dart';
import 'models/appState.dart'; import 'models/appState.dart';
import 'redux/actions.dart'; import 'redux/actions.dart';
import 'firesSpinner.dart';
@immutable @immutable
class _ViewModel { class _ViewModel {
@ -76,7 +76,7 @@ class _FireNotificationListState extends State<FireNotificationList> {
return new ListTile( return new ListTile(
dense: true, dense: true,
leading: const Icon(Icons.whatshot), leading: const Icon(Icons.whatshot),
title: new Text('${prefix}${notif.description}', title: new Text('$prefix${notif.description}',
style: new TextStyle( style: new TextStyle(
fontWeight: notif.read ? FontWeight.normal : FontWeight.bold)), fontWeight: notif.read ? FontWeight.normal : FontWeight.bold)),
subtitle: new Text(Moment.now().from(context, notif.when)), subtitle: new Text(Moment.now().from(context, notif.when)),
@ -89,7 +89,7 @@ class _FireNotificationListState extends State<FireNotificationList> {
} }
void showSnackMsg(String msg) { void showSnackMsg(String msg) {
_scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(msg), content: new Text(msg),
)); ));
} }
@ -149,8 +149,8 @@ class _FireNotificationListState extends State<FireNotificationList> {
return new StoreConnector<AppState, _ViewModel>( return new StoreConnector<AppState, _ViewModel>(
distinct: true, distinct: true,
converter: (store) { converter: (store) {
print('New ViewModel of Fires Notifications (unread: ${store.state print(
.fireNotificationsUnread})'); 'New ViewModel of Fires Notifications (unread: ${store.state.fireNotificationsUnread})');
return new _ViewModel( return new _ViewModel(
isLoaded: store.state.isLoaded, isLoaded: store.state.isLoaded,
onDeleteAll: () { onDeleteAll: () {
@ -158,7 +158,7 @@ class _FireNotificationListState extends State<FireNotificationList> {
}, },
onDelete: (notif) { onDelete: (notif) {
store.dispatch(new DeleteFireNotificationAction(notif)); store.dispatch(new DeleteFireNotificationAction(notif));
_scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(context).showSnackBar(new SnackBar(
content: new Text(S.of(context).youDeletedThisNotification), content: new Text(S.of(context).youDeletedThisNotification),
action: new SnackBarAction( action: new SnackBarAction(
label: S.of(context).UNDO, label: S.of(context).UNDO,
@ -202,25 +202,33 @@ class _FireNotificationListState extends State<FireNotificationList> {
onPressed: () => _showConfirmDialog(view)) onPressed: () => _showConfirmDialog(view))
: null : null
])), ])),
body: !view.isLoaded ? new FiresSpinner() : !hasFireNotifications body: !view.isLoaded
? Padding( ? new FiresSpinner()
padding: const EdgeInsets.all(20.0), : !hasFireNotifications
child: new Card( ? Padding(
child: new Padding( padding: const EdgeInsets.all(20.0),
padding: const EdgeInsets.all(20.0), child: new Card(
child: new CenteredColumn(children: <Widget>[ child: new Padding(
new Icon(Icons.notifications_none, padding: const EdgeInsets.all(20.0),
size: 150.0, color: Colors.black26), child: new CenteredColumn(children: <Widget>[
new SizedBox(height: 20.0), new Icon(Icons.notifications_none,
new Text( size: 150.0, color: Colors.black26),
S.of(context).fireNotificationsDescription, new SizedBox(height: 20.0),
textAlign: TextAlign.center, new Text(
textScaleFactor: 1.1, S
style: new TextStyle( .of(context)
height: 1.3, color: Colors.black45)) .fireNotificationsDescription,
])))) textAlign: TextAlign.center,
: _buildSavedFireNotifications(context, view.yourLocations, textScaleFactor: 1.1,
view.fireNotifications, view.onDelete, view.onTap)); style: new TextStyle(
height: 1.3, color: Colors.black45))
]))))
: _buildSavedFireNotifications(
context,
view.yourLocations,
view.fireNotifications,
view.onDelete,
view.onTap));
}); });
} }

View file

@ -1,20 +1,18 @@
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:redux/src/store.dart';
import 'globals.dart';
import 'monitoredAreas.dart';
import 'activeFires.dart'; import 'activeFires.dart';
import 'fireAlert.dart'; import 'fireAlert.dart';
import 'fireNotificationList.dart'; import 'fireNotificationList.dart';
import 'generated/i18n.dart'; import 'generated/i18n.dart';
import 'globals.dart';
import 'homePage.dart'; import 'homePage.dart';
import 'introPage.dart'; import 'introPage.dart';
import 'models/appState.dart'; import 'models/appState.dart';
import 'monitoredAreas.dart';
import 'privacyPage.dart'; import 'privacyPage.dart';
import 'redux/actions.dart'; import 'redux/actions.dart';
import 'sandbox.dart'; import 'sandbox.dart';
@ -47,16 +45,15 @@ class _FiresAppState extends State<FiresApp> {
SupportPage.routeName: (BuildContext context) => new SupportPage(), SupportPage.routeName: (BuildContext context) => new SupportPage(),
FireNotificationList.routeName: (BuildContext context) => FireNotificationList.routeName: (BuildContext context) =>
new FireNotificationList(), new FireNotificationList(),
MonitoredAreasPage.routeName: (BuildContext context) => new MonitoredAreasPage() MonitoredAreasPage.routeName: (BuildContext context) =>
new MonitoredAreasPage()
}; };
final Store<AppState> store; final Store<AppState> store;
// globals.getIt.registerSingleton // globals.getIt.registerSingleton
_FiresAppState(this.store); _FiresAppState(this.store);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
StatefulWidget home = new MaterialAppWithIntroHome( StatefulWidget home = new MaterialAppWithIntroHome(
@ -82,7 +79,7 @@ class _FiresAppState extends State<FiresApp> {
} }
return S.of(context).appName; return S.of(context).appName;
}, },
theme: isDevelopment? devFiresTheme: firesTheme, theme: isDevelopment ? devFiresTheme : firesTheme,
routes: routes)); routes: routes));
} }
} }

View file

@ -1,5 +1,5 @@
import 'dart:core'; import 'dart:core';
import 'locationUtils.dart';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -11,6 +11,7 @@ import 'package:share/share.dart';
import 'attributionMapPlugin.dart'; import 'attributionMapPlugin.dart';
import 'colors.dart'; import 'colors.dart';
import 'customMoment.dart';
import 'dummyMapPlugin.dart'; import 'dummyMapPlugin.dart';
import 'fireMarkType.dart'; import 'fireMarkType.dart';
import 'fireMarker.dart'; import 'fireMarker.dart';
@ -18,13 +19,13 @@ import 'generated/i18n.dart';
import 'genericMapBottom.dart'; import 'genericMapBottom.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'layerSelectorMapPlugin.dart'; import 'layerSelectorMapPlugin.dart';
import 'locationUtils.dart';
import 'models/appState.dart'; import 'models/appState.dart';
import 'models/fireMapState.dart'; import 'models/fireMapState.dart';
import 'redux/actions.dart'; import 'redux/actions.dart';
import 'sentryReport.dart'; import 'sentryReport.dart';
import 'slider.dart'; import 'slider.dart';
import 'zoomMapPlugin.dart'; import 'zoomMapPlugin.dart';
import 'customMoment.dart';
@immutable @immutable
class _ViewModel { class _ViewModel {
@ -142,33 +143,34 @@ class _genericMapState extends State<genericMap> {
double maxZoom = 18.0; // works? double maxZoom = 18.0; // works?
MapOptions mapOptions = new MapOptions( MapOptions mapOptions = new MapOptions(
center: new LatLng(_location.lat, _location.lon), center: new LatLng(_location.lat, _location.lon),
plugins: globals.isDevelopment plugins: globals.isDevelopment
? [ ? [
new ZoomMapPlugin(), new ZoomMapPlugin(),
new AttributionPlugin(), new AttributionPlugin(),
new LayerSelectorMapPlugin() new LayerSelectorMapPlugin()
] ]
: [ : [
new DummyMapPlugin(), new DummyMapPlugin(),
new AttributionPlugin(), new AttributionPlugin(),
new LayerSelectorMapPlugin() new LayerSelectorMapPlugin()
], ],
// this works ? // this works ?
interactive: false, interactive: true,
zoom: 13.0, zoom: 13.0,
// THIS does not works as expected // THIS does not works as expected
maxZoom: maxZoom, maxZoom: maxZoom,
onTap: (callback) { onTap: (callback) {
if (status == FireMapStatus.edit) { if (status == FireMapStatus.edit) {
_location = _location.copyWith( _location = _location.copyWith(
lat: callback.latitude, lon: callback.longitude); lat: callback.latitude, lon: callback.longitude);
view.onEditing(_location); view.onEditing(_location);
} }
}, },
onPositionChanged: (positionCallback) { // onPositionChanged: (positionCallback) {
// print('${positionCallback.center}, ${positionCallback.zoom}'); // print('${positionCallback.center}, ${positionCallback.zoom}');
}); //}
);
var mapController = new MapController(); var mapController = new MapController();
// mapController.fitBounds(bounds); // mapController.fitBounds(bounds);
@ -312,7 +314,7 @@ class _genericMapState extends State<genericMap> {
} }
}, },
// https://github.com/flutter/flutter/issues/17583 // https://github.com/flutter/flutter/issues/17583
heroTag: "firesmap" + _location.id.toHexString(), heroTag: "firesmap" + _location.id.hexString,
icon: new Icon(btnIcon, color: fires600), icon: new Icon(btnIcon, color: fires600),
label: new Text( label: new Text(
btnText, btnText,
@ -388,8 +390,7 @@ class _genericMapState extends State<genericMap> {
icon: new Icon(Icons.share), icon: new Icon(Icons.share),
onPressed: () { onPressed: () {
Share.share( Share.share(
'${view.mapState.fireNotification.description}. ${view '${view.mapState.fireNotification.description}. ${view.serverUrl}fire/${view.mapState.fireNotification.sealed}');
.serverUrl}fire/${view.mapState.fireNotification.sealed}');
}) })
]; ];
default: default:
@ -405,14 +406,14 @@ class _genericMapState extends State<genericMap> {
bool isNotif, bool isNotif,
OnFirePressedInMap onFirePressed) { OnFirePressedInMap onFirePressed) {
List<Marker> markers = []; List<Marker> markers = [];
print('building markers: fires: ${fires.length} falsePos: ${falsePosList print(
.length} industries: ${industries.length}, isNotif: ${isNotif} '); 'building markers: fires: ${fires.length} falsePos: ${falsePosList.length} industries: ${industries.length}, isNotif: $isNotif ');
const calibrate = false; // useful when we change the fire icons size const calibrate = false; // useful when we change the fire icons size
falsePosList.forEach((falsePos) { falsePosList.forEach((falsePos) {
try { try {
var coords = falsePos['geo']['coordinates']; var coords = falsePos['geo']['coordinates'];
// print('false pos: ${coords}'); // print('false pos: ${coords}');
var loc = LatLng(coords[1], coords[0]); var loc = LatLng(coords[1].toDouble(), coords[0].toDouble());
markers.add(FireMarker(loc, FireMarkType.falsePos)); markers.add(FireMarker(loc, FireMarkType.falsePos));
if (calibrate) markers.add(FireMarker(loc, FireMarkType.pixel)); if (calibrate) markers.add(FireMarker(loc, FireMarkType.pixel));
} catch (e) { } catch (e) {

View file

@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:fires_flutter/models/fireNotification.dart'; import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'colors.dart'; import 'colors.dart';
@ -27,7 +27,7 @@ class GenericMapBottom extends StatelessWidget {
GenericMapBottom( GenericMapBottom(
{@required this.onSave, {@required this.onSave,
@required this.onCancel, @required this.onCancel,
@required this.onFalsePositive, @required this.onFalsePositive,
@required this.state, @required this.state,
@required this.scaffoldKey}); @required this.scaffoldKey});
@ -45,7 +45,7 @@ class GenericMapBottom extends StatelessWidget {
List<Widget> buildActionList(YourLocation loc, BuildContext context, List<Widget> buildActionList(YourLocation loc, BuildContext context,
int kmAround, GlobalKey<ScaffoldState> scaffoldState) { int kmAround, GlobalKey<ScaffoldState> scaffoldState) {
List<Widget> actionList = new List<Widget>(); List<Widget> actionList = [];
switch (state.status) { switch (state.status) {
case FireMapStatus.edit: case FireMapStatus.edit:
actionList.add(new FlatButton( actionList.add(new FlatButton(
@ -74,8 +74,7 @@ class GenericMapBottom extends StatelessWidget {
children: <Widget>[ children: <Widget>[
new Icon(Icons.access_time), new Icon(Icons.access_time),
new SizedBox(width: 5.0), new SizedBox(width: 5.0),
new Text(Moment new Text(Moment.now()
.now()
.from(context, state.fireNotification.when)), .from(context, state.fireNotification.when)),
], ],
), ),
@ -113,7 +112,8 @@ class GenericMapBottom extends StatelessWidget {
onFalsePositive(state.fireNotification, value); onFalsePositive(state.fireNotification, value);
await new Future.delayed( await new Future.delayed(
new Duration(milliseconds: 500)); new Duration(milliseconds: 500));
scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(context)
.showSnackBar(new SnackBar(
content: new Text( content: new Text(
S.of(context).thanksForParticipating), S.of(context).thanksForParticipating),
)); ));

View file

@ -1,21 +1,21 @@
import 'dart:async'; import 'dart:async';
import 'package:bson_objectid/bson_objectid.dart';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:connectivity/connectivity.dart'; import 'package:connectivity/connectivity.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:fires_flutter/models/fireNotification.dart'; import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/objectIdUtils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:flutter_simple_dependency_injection/injector.dart'; import 'package:flutter_simple_dependency_injection/injector.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'firesSpinner.dart';
import 'activeFires.dart'; import 'activeFires.dart';
import 'colors.dart'; import 'colors.dart';
import 'fireAlert.dart'; import 'fireAlert.dart';
import 'fireNotificationList.dart'; import 'fireNotificationList.dart';
import 'firesSpinner.dart';
import 'generated/i18n.dart'; import 'generated/i18n.dart';
import 'mainDrawer.dart'; import 'mainDrawer.dart';
import 'models/appState.dart'; import 'models/appState.dart';
@ -28,14 +28,13 @@ class _ViewModel {
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
other is _ViewModel && other is _ViewModel &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
isLoaded == other.isLoaded; isLoaded == other.isLoaded;
@override @override
int get hashCode => isLoaded.hashCode; int get hashCode => isLoaded.hashCode;
} }
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@ -83,16 +82,20 @@ class _HomePageState extends State<HomePage> {
void initState() { void initState() {
super.initState(); super.initState();
_firebaseMessaging.configure(onMessage: (Map<String, dynamic> message) { _firebaseMessaging.configure(onMessage: (Map<String, dynamic> message) {
debugPrint("onMessage in fireApp (isLoaded: ${store.state.isLoaded}): $message"); debugPrint(
"onMessage in fireApp (isLoaded: ${store.state.isLoaded}): $message");
_showItemDialog(message, _notifForMessage(message, store.state.isLoaded)); _showItemDialog(message, _notifForMessage(message, store.state.isLoaded));
return;
}, onLaunch: (Map<String, dynamic> message) { }, onLaunch: (Map<String, dynamic> message) {
debugPrint("onLaunch (isLoaded: ${store.state.isLoaded}): $message"); debugPrint("onLaunch (isLoaded: ${store.state.isLoaded}): $message");
_notifForMessage(message, store.state.isLoaded); _notifForMessage(message, store.state.isLoaded);
_navigateToItemDetail(message); _navigateToItemDetail(message);
return;
}, onResume: (Map<String, dynamic> message) { }, onResume: (Map<String, dynamic> message) {
debugPrint("onResume (isLoaded: ${store.state.isLoaded}): $message"); debugPrint("onResume (isLoaded: ${store.state.isLoaded}): $message");
_notifForMessage(message, store.state.isLoaded); _notifForMessage(message, store.state.isLoaded);
_navigateToItemDetail(message); _navigateToItemDetail(message);
return;
}); });
_firebaseMessaging.requestNotificationPermissions( _firebaseMessaging.requestNotificationPermissions(
const IosNotificationSettings(sound: true, badge: true, alert: true)); const IosNotificationSettings(sound: true, badge: true, alert: true));
@ -140,9 +143,7 @@ class _HomePageState extends State<HomePage> {
}); });
newNotifications.clear(); newNotifications.clear();
} }
return new _ViewModel( return new _ViewModel(isLoaded: store.state.isLoaded);
isLoaded: store.state.isLoaded
);
}, },
builder: (context, view) { builder: (context, view) {
return new Scaffold( return new Scaffold(
@ -179,45 +180,47 @@ class _HomePageState extends State<HomePage> {
), ),
), ),
]), ]),
body: !view.isLoaded? new FiresSpinner(): new SafeArea( body: !view.isLoaded
child: Center( ? new FiresSpinner()
child: new CenteredColumn(children: <Widget>[ : new SafeArea(
new Row( child: Center(
mainAxisAlignment: MainAxisAlignment.start, child: new CenteredColumn(children: <Widget>[
children: <Widget>[ new Row(
new IconButton( mainAxisAlignment: MainAxisAlignment.start,
onPressed: () {
_scaffoldKey.currentState.openDrawer();
},
icon: new Icon(Icons.menu,
size: 30.0, color: Colors.black38)),
]),
new Expanded(
child: new FractionallySizedBox(
alignment: FractionalOffset.center,
heightFactor: 0.7,
child: new Image.asset('images/logo-200.png',
fit: BoxFit.fitHeight))),
new Expanded(
child: new FractionallySizedBox(
alignment: FractionalOffset.topCenter,
heightFactor: 1.0,
child: new Column(
children: <Widget>[ children: <Widget>[
new Padding( new IconButton(
padding: const EdgeInsets.symmetric( onPressed: () {
vertical: 10.0, horizontal: 20.0), _scaffoldKey.currentState.openDrawer();
child: FittedBox( },
child: new Text(S.of(context).appName, icon: new Icon(Icons.menu,
maxLines: 2, size: 30.0, color: Colors.black38)),
textAlign: TextAlign.center, ]),
style: _homeFont), new Expanded(
fit: BoxFit.scaleDown, child: new FractionallySizedBox(
)), alignment: FractionalOffset.center,
], heightFactor: 0.7,
))) child: new Image.asset('images/logo-200.png',
])), fit: BoxFit.fitHeight))),
)); new Expanded(
child: new FractionallySizedBox(
alignment: FractionalOffset.topCenter,
heightFactor: 1.0,
child: new Column(
children: <Widget>[
new Padding(
padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 20.0),
child: FittedBox(
child: new Text(S.of(context).appName,
maxLines: 2,
textAlign: TextAlign.center,
style: _homeFont),
fit: BoxFit.scaleDown,
)),
],
)))
])),
));
}); });
} }
@ -282,12 +285,13 @@ class _HomePageState extends State<HomePage> {
// https://pub.dartlang.org/packages/firebase_messaging#-example-tab- // https://pub.dartlang.org/packages/firebase_messaging#-example-tab-
final FirebaseMessaging _firebaseMessaging = new FirebaseMessaging(); final FirebaseMessaging _firebaseMessaging = new FirebaseMessaging();
FireNotification _notifForMessage(Map<String, dynamic> message, bool isLoaded) { FireNotification _notifForMessage(
Map<String, dynamic> message, bool isLoaded) {
FireNotification notif; FireNotification notif;
try { try {
notif = new FireNotification( notif = new FireNotification(
id: new ObjectId.fromHexString(message['id']), id: objectIdFromJson(message['id']),
subsId: new ObjectId.fromHexString(message['subsId']), subsId: objectIdFromJson(message['subsId']),
lat: double.parse(message['lat']), lat: double.parse(message['lat']),
lon: double.parse(message['lon']), lon: double.parse(message['lon']),
description: message['description'], description: message['description'],
@ -299,12 +303,12 @@ class _HomePageState extends State<HomePage> {
debugPrint(e.toString()); debugPrint(e.toString());
} }
if (notif != null) if (notif != null)
// if our store is loaded, we just dispatch the notification, if not, we wait til is loaded // if our store is loaded, we just dispatch the notification, if not, we wait til is loaded
if (isLoaded) { if (isLoaded) {
store.dispatch(new AddFireNotificationAction(notif)); store.dispatch(new AddFireNotificationAction(notif));
} else { } else {
newNotifications.add(notif); newNotifications.add(notif);
} }
return notif; return notif;
} }
} }

View file

@ -1,6 +1,5 @@
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_map/plugin_api.dart'; import 'package:flutter_map/plugin_api.dart';
import 'package:flutter_simple_dependency_injection/injector.dart'; import 'package:flutter_simple_dependency_injection/injector.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
@ -18,16 +17,19 @@ class LayerSelectorMapPluginOptions extends LayerOptions {
class LayerSelectorMapPlugin extends MapPlugin { class LayerSelectorMapPlugin extends MapPlugin {
Widget LayerSelectorButton(Store<AppState> store) { Widget LayerSelectorButton(Store<AppState> store) {
return new FloatingActionButton( return new FloatingActionButton(
backgroundColor: Colors.black26, backgroundColor: Colors.black26,
mini: true, mini: true,
child: Icon(Icons.layers, /* size: 40.0, color: Colors.black45, */ ), child: Icon(
Icons.layers, /* size: 40.0, color: Colors.black45, */
),
onPressed: () { onPressed: () {
store.dispatch(new ToggleMapLayerAction()); store.dispatch(new ToggleMapLayerAction());
}); });
} }
@override @override
Widget createLayer(LayerOptions options, MapState mapState) { Widget createLayer(
LayerOptions options, MapState mapState, Stream<Null> stream) {
Store<AppState> store = Injector.getInjector().get<Store<AppState>>(); Store<AppState> store = Injector.getInjector().get<Store<AppState>>();
if (options is LayerSelectorMapPluginOptions) { if (options is LayerSelectorMapPluginOptions) {
return LayoutBuilder( return LayoutBuilder(

View file

@ -1,29 +1,32 @@
import 'package:location/location.dart';
import 'package:flutter/services.dart';
import 'dart:async'; import 'dart:async';
import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:geocoder/geocoder.dart'; import 'package:flutter/services.dart';
import 'generated/i18n.dart';
import 'package:flutter_simple_dependency_injection/injector.dart'; import 'package:flutter_simple_dependency_injection/injector.dart';
import 'package:geocoder/geocoder.dart';
import 'package:location/location.dart';
import 'generated/i18n.dart';
Future<YourLocation> getUserLocation( Future<YourLocation> getUserLocation(
GlobalKey<ScaffoldState> scaffoldKey) async { GlobalKey<ScaffoldState> scaffoldKey) async {
// Platform messages may fail, so we use a try/catch PlatformException. // Platform messages may fail, so we use a try/catch PlatformException.
try { try {
Location _location = new Location(); Location _location = new Location();
Map<String, double> location = await _location.getLocation;
LocationData location = await _location.getLocation();
// It seems that the lib fails with lat/lon values // It seems that the lib fails with lat/lon values
var yl = new YourLocation( var yl = new YourLocation(lat: location.latitude, lon: location.longitude);
lat: location['latitude'], lon: location['longitude']);
var address; var address;
try { try {
address = await getReverseLocation(lat: yl.lat, lon: yl.lon); address = await getReverseLocation(lat: yl.lat, lon: yl.lon);
yl.description = address; yl.description = address;
} catch (e) { } catch (e) {
try { try {
address = await getReverseLocation(lat: yl.lat, lon: yl.lon, external: true); address =
await getReverseLocation(lat: yl.lat, lon: yl.lon, external: true);
yl.description = address; yl.description = address;
} catch (e) { } catch (e) {
print('We cannot reverse geolocate'); print('We cannot reverse geolocate');
@ -32,22 +35,25 @@ Future<YourLocation> getUserLocation(
return yl; return yl;
} on PlatformException catch (e) { } on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') { if (e.code == 'PERMISSION_DENIED') {
scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(scaffoldKey.currentContext)
.showSnackBar(new SnackBar(
content: new Text(S.of(scaffoldKey.currentContext).notPermsUbication), content: new Text(S.of(scaffoldKey.currentContext).notPermsUbication),
)); ));
} else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {} } else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {}
scaffoldKey.currentState.showSnackBar(new SnackBar( ScaffoldMessenger.of(scaffoldKey.currentContext).showSnackBar(new SnackBar(
content: new Text(S.of(scaffoldKey.currentContext).isYourUbicationEnabled content:
), new Text(S.of(scaffoldKey.currentContext).isYourUbicationEnabled),
)); ));
return YourLocation.noLocation; return YourLocation.noLocation;
} }
} }
Future<String> getReverseLocation({@required lat, @required lon, Future<String> getReverseLocation(
bool external = false}) async { {@required lat, @required lon, bool external = false}) async {
final coordinates = new Coordinates(lat, lon); final coordinates = new Coordinates(lat, lon);
var geoCoder = external ? Geocoder.google(Injector.getInjector().get<String>(key: "gmapKey")) : Geocoder.local; var geoCoder = external
? Geocoder.google(Injector.getInjector().get<String>(key: "gmapKey"))
: Geocoder.local;
var addresses = await geoCoder.findAddressesFromCoordinates(coordinates); var addresses = await geoCoder.findAddressesFromCoordinates(coordinates);
var first = addresses.first; var first = addresses.first;
print("${first.featureName} : ${first.addressLine}"); print("${first.featureName} : ${first.addressLine}");

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_simple_dependency_injection/injector.dart'; import 'package:flutter_simple_dependency_injection/injector.dart';
import 'package:package_info/package_info.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:sentry/sentry.dart'; import 'package:sentry/sentry.dart';
@ -12,7 +13,6 @@ import 'models/appState.dart';
import 'models/firesApi.dart'; import 'models/firesApi.dart';
import 'redux/fetchDataMiddleware.dart'; import 'redux/fetchDataMiddleware.dart';
import 'redux/reducers.dart'; import 'redux/reducers.dart';
import 'package:package_info/package_info.dart';
import 'sentryReport.dart'; import 'sentryReport.dart';
Future<PackageInfo> loadPackageInfo() async { Future<PackageInfo> loadPackageInfo() async {
@ -22,13 +22,14 @@ Future<PackageInfo> loadPackageInfo() async {
Future<Map<String, dynamic>> loadSecrets() async { Future<Map<String, dynamic>> loadSecrets() async {
return await SecretLoader( return await SecretLoader(
secretPath: globals.isDevelopment? secretPath: globals.isDevelopment
'assets/private-settings-dev.json' : ? 'assets/private-settings-dev.json'
'assets/private-settings.json' : 'assets/private-settings.json')
).load(); .load();
} }
void mainCommon(List<Middleware<AppState>> otherMiddleware) { void mainCommon(List<Middleware<AppState>> otherMiddleware) {
WidgetsFlutterBinding.ensureInitialized();
final injector = Injector.getInjector(); final injector = Injector.getInjector();
injector.map<FiresApi>((i) => new FiresApi(), isSingleton: true); injector.map<FiresApi>((i) => new FiresApi(), isSingleton: true);
loadPackageInfo().then((packageInfo) { loadPackageInfo().then((packageInfo) {
@ -36,13 +37,12 @@ void mainCommon(List<Middleware<AppState>> otherMiddleware) {
print('Running version ${packageInfo.version}'); print('Running version ${packageInfo.version}');
loadSecrets().then((secrets) { loadSecrets().then((secrets) {
final store = new Store<AppState>(appStateReducer, final store = new Store<AppState>(appStateReducer,
initialState: new AppState( initialState: new AppState(
gmapKey: secrets['gmapKey'], gmapKey: secrets['gmapKey'],
firesApiKey: secrets['firesApiKey'], firesApiKey: secrets['firesApiKey'],
serverUrl: secrets['firesApiUrl'], serverUrl: secrets['firesApiUrl'],
firesApiUrl: secrets['firesApiUrl'] + "api/v1/"), firesApiUrl: secrets['firesApiUrl'] + "api/v1/"),
middleware: List.from(otherMiddleware) middleware: List.from(otherMiddleware)..add(fetchDataMiddleware));
..add(fetchDataMiddleware));
injector.map<Store<AppState>>((i) => store); injector.map<Store<AppState>>((i) => store);
injector.map<String>((i) => store.state.firesApiUrl, key: "firesApiUrl"); injector.map<String>((i) => store.state.firesApiUrl, key: "firesApiUrl");
@ -56,9 +56,9 @@ void mainCommon(List<Middleware<AppState>> otherMiddleware) {
} }
// https://flutter.io/cookbook/maintenance/error-reporting/ // https://flutter.io/cookbook/maintenance/error-reporting/
runZoned<Future<Null>>(() async { runZonedGuarded<Future<void>>(() async {
runApp(new FiresApp(store)); runApp(new FiresApp(store));
}, onError: (error, stackTrace) { }, (Object error, StackTrace stackTrace) {
// Whenever an error occurs, call the `_reportError` function. This will send // Whenever an error occurs, call the `_reportError` function. This will send
// Dart errors to our dev console or Sentry depending on the environment. // Dart errors to our dev console or Sentry depending on the environment.
reportError(error, stackTrace); reportError(error, stackTrace);

View file

@ -30,7 +30,7 @@ LoggingMiddleware customLogPrinter<State>({
void main() { void main() {
globals.isDevelopment = true; globals.isDevelopment = true;
print("Is development!");
String onlyLogActionFormatter<State>( String onlyLogActionFormatter<State>(
State state, State state,
dynamic action, dynamic action,
@ -39,9 +39,9 @@ void main() {
return ">>>>> ${action.toString().replaceAll('Instance of ', '')}"; return ">>>>> ${action.toString().replaceAll('Instance of ', '')}";
} }
LogLevel logRedux = LogLevel.none; LogLevel logRedux = LogLevel.actions;
List<Middleware> devMiddlewares = logRedux == LogLevel.none List<Middleware> devMiddlewares = logRedux == LogLevel.actions
? [] ? []
: [ : [
customLogPrinter( customLogPrinter(

View file

@ -1,4 +1,4 @@
import 'package:badge/badge.dart'; import 'package:badges/badges.dart';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
@ -97,13 +97,28 @@ Widget mainDrawer(BuildContext context, String currentRoute) {
new ListTile( new ListTile(
leading: const Icon(Icons.notifications), leading: const Icon(Icons.notifications),
selected: currentRoute == FireNotificationList.routeName, selected: currentRoute == FireNotificationList.routeName,
title: view.unreadCount > 0 title: Text(S.of(context).fireNotificationsTitleShort),
? Badge.after( trailing: SizedBox(
spacing: 5.0, width: 100,
borderColor: Colors.red, child: Row(
child: Text(S.of(context).fireNotificationsTitleShort), mainAxisAlignment: MainAxisAlignment.end,
value: ' ${view.unreadCount.toString()} ') children: <Widget>[
: Text(S.of(context).fireNotificationsTitleShort), Badge(
position: BadgePosition(top: 0, end: 0),
padding: EdgeInsets.all(7),
badgeColor: view.unreadCount > 0
? Colors.red
: Colors.grey,
// spacing: 5.0,
// borderColor: Colors.red,
//child: Text(S.of(context).fireNotificationsTitleShort),
badgeContent: Text(
'${view.unreadCount.toString()}',
style: TextStyle(color: Colors.white),
))
])),
// Text(S.of(context).fireNotificationsTitleShort),
onTap: () { onTap: () {
Navigator.popAndPushNamed( Navigator.popAndPushNamed(
context, FireNotificationList.routeName); context, FireNotificationList.routeName);

View file

@ -5,21 +5,21 @@ import 'package:connectivity/connectivity.dart';
import 'package:fires_flutter/models/fireNotification.dart'; import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:latlong/latlong.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'fireMapState.dart'; import 'fireMapState.dart';
import 'user.dart'; import 'user.dart';
export 'fireMapState.dart'; export 'fireMapState.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong/latlong.dart';
part 'appState.g.dart'; part 'appState.g.dart';
@immutable @immutable
@JsonSerializable(nullable: false) @JsonSerializable(nullable: false)
class AppState extends Object with _$AppStateSerializerMixin { class AppState {
@JsonKey(ignore: true) @JsonKey(ignore: true)
final bool isLoading; final bool isLoading;
@JsonKey(ignore: true) @JsonKey(ignore: true)
@ -58,7 +58,7 @@ class AppState extends Object with _$AppStateSerializerMixin {
this.user: const User.initial(), this.user: const User.initial(),
this.isLoading: false, this.isLoading: false,
this.isLoaded: false, this.isLoaded: false,
this.error: null, this.error,
this.gmapKey, this.gmapKey,
this.firesApiKey, this.firesApiKey,
this.firesApiUrl, this.firesApiUrl,
@ -102,11 +102,7 @@ class AppState extends Object with _$AppStateSerializerMixin {
@override @override
String toString() { String toString() {
return 'AppState{\nuser: ${user}\nconnectivity: ${connectivity return 'AppState{\nuser: $user\nconnectivity: ${connectivity.toString()}\nisLoading: $isLoading\nisLoaded: $isLoaded\napiKey: ${ellipse(firesApiKey, 8)}\napiUrl: $firesApiUrl\nserverUrl: $serverUrl\nfireMapState: $fireMapState\nyourLocations count: ${yourLocations.length}\nunread notif: $fireNotificationsUnread\nfireNotifications: $fireNotifications\nyourLocations: $yourLocations}';
.toString()}\nisLoading: $isLoading\nisLoaded: $isLoaded\napiKey: ${ellipse(
firesApiKey,
8)}\napiUrl: ${firesApiUrl}\nserverUrl: ${serverUrl}\nfireMapState: $fireMapState\nyourLocations count: ${yourLocations
.length}\nunread notif: ${fireNotificationsUnread}\nfireNotifications: ${fireNotifications}\nyourLocations: ${yourLocations}}';
} }
} }

View file

@ -1,5 +1,3 @@
// Copyright (c) 2018, Comunes Association.
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'appState.dart'; part of 'appState.dart';
@ -8,37 +6,30 @@ part of 'appState.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
AppState _$AppStateFromJson(Map<String, dynamic> json) => new AppState( AppState _$AppStateFromJson(Map json) {
yourLocations: (json['yourLocations'] as List) return $checkedNew('AppState', json, () {
.map((e) => new YourLocation.fromJson(e as Map<String, dynamic>)) final val = AppState(
.toList(), yourLocations: $checkedConvert(
fireNotifications: (json['fireNotifications'] as List) json,
.map((e) => new FireNotification.fromJson(e as Map<String, dynamic>)) 'yourLocations',
.toList()); (v) => (v as List)
.map((e) =>
abstract class _$AppStateSerializerMixin { YourLocation.fromJson(Map<String, dynamic>.from(e as Map)))
List<YourLocation> get yourLocations; .toList()),
List<FireNotification> get fireNotifications; fireNotifications: $checkedConvert(
Map<String, dynamic> toJson() => new _$AppStateJsonMapWrapper(this); json,
'fireNotifications',
(v) => (v as List)
.map((e) => FireNotification.fromJson(
Map<String, dynamic>.from(e as Map)))
.toList()),
);
return val;
});
} }
class _$AppStateJsonMapWrapper extends $JsonMapWrapper { Map<String, dynamic> _$AppStateToJson(AppState instance) => <String, dynamic>{
final _$AppStateSerializerMixin _v; 'yourLocations': instance.yourLocations.map((e) => e.toJson()).toList(),
_$AppStateJsonMapWrapper(this._v); 'fireNotifications':
instance.fireNotifications.map((e) => e.toJson()).toList(),
@override };
Iterable<String> get keys => const ['yourLocations', 'fireNotifications'];
@override
dynamic operator [](Object key) {
if (key is String) {
switch (key) {
case 'yourLocations':
return _v.yourLocations;
case 'fireNotifications':
return _v.fireNotifications;
}
}
return null;
}
}

View file

@ -1,13 +1,14 @@
import 'package:bson_objectid/bson_objectid.dart';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:objectid/objectid.dart';
import '../objectIdUtils.dart'; import '../objectIdUtils.dart';
part 'fireNotification.g.dart'; part 'fireNotification.g.dart';
@JsonSerializable(nullable: false) @JsonSerializable(nullable: false)
class FireNotification extends Object with _$FireNotificationSerializerMixin { class FireNotification {
@JsonKey(toJson: objectIdToJson, fromJson: objectIdFromJson) @JsonKey(toJson: objectIdToJson, fromJson: objectIdFromJson)
ObjectId id; ObjectId id;
final double lat; final double lat;
@ -74,11 +75,11 @@ class FireNotification extends Object with _$FireNotificationSerializerMixin {
@override @override
String toString() { String toString() {
return 'FireNotification {id: $id, lat: $lat, lon: $lon, when: $when, read: $read, subsId: $subsId, sealed ${ellipse( return 'FireNotification {id: $id, lat: $lat, lon: $lon, when: $when, read: $read, subsId: $subsId, sealed ${ellipse(sealed)}';
sealed)}';
} }
static final Map<String, Route<Null>> routes = <String, Route<Null>>{}; static final Map<String, Route<Null>> routes = <String, Route<Null>>{};
Map<String, dynamic> toJson() => _$FireNotificationToJson(this);
/* /*
Route<Null> getRoute(Store store) { Route<Null> getRoute(Store store) {

View file

@ -1,5 +1,3 @@
// Copyright (c) 2018, Comunes Association.
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'fireNotification.dart'; part of 'fireNotification.dart';
@ -8,67 +6,31 @@ part of 'fireNotification.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
FireNotification _$FireNotificationFromJson(Map<String, dynamic> json) => FireNotification _$FireNotificationFromJson(Map json) {
new FireNotification( return $checkedNew('FireNotification', json, () {
id: objectIdFromJson(json['id'] as String), final val = FireNotification(
lat: (json['lat'] as num).toDouble(), id: $checkedConvert(json, 'id', (v) => objectIdFromJson(v as String)),
lon: (json['lon'] as num).toDouble(), lat: $checkedConvert(json, 'lat', (v) => (v as num).toDouble()),
description: json['description'] as String, lon: $checkedConvert(json, 'lon', (v) => (v as num).toDouble()),
when: DateTime.parse(json['when'] as String), description: $checkedConvert(json, 'description', (v) => v as String),
read: json['read'] as bool, when: $checkedConvert(json, 'when', (v) => DateTime.parse(v as String)),
sealed: json['sealed'] as String, read: $checkedConvert(json, 'read', (v) => v as bool),
subsId: objectIdFromJson(json['subsId'] as String)); sealed: $checkedConvert(json, 'sealed', (v) => v as String),
subsId:
abstract class _$FireNotificationSerializerMixin { $checkedConvert(json, 'subsId', (v) => objectIdFromJson(v as String)),
ObjectId get id; );
double get lat; return val;
double get lon; });
String get description;
DateTime get when;
String get sealed;
ObjectId get subsId;
bool get read;
Map<String, dynamic> toJson() => new _$FireNotificationJsonMapWrapper(this);
} }
class _$FireNotificationJsonMapWrapper extends $JsonMapWrapper { Map<String, dynamic> _$FireNotificationToJson(FireNotification instance) =>
final _$FireNotificationSerializerMixin _v; <String, dynamic>{
_$FireNotificationJsonMapWrapper(this._v); 'id': objectIdToJson(instance.id),
'lat': instance.lat,
@override 'lon': instance.lon,
Iterable<String> get keys => const [ 'description': instance.description,
'id', 'when': instance.when.toIso8601String(),
'lat', 'sealed': instance.sealed,
'lon', 'subsId': objectIdToJson(instance.subsId),
'description', 'read': instance.read,
'when', };
'sealed',
'subsId',
'read'
];
@override
dynamic operator [](Object key) {
if (key is String) {
switch (key) {
case 'id':
return objectIdToJson(_v.id);
case 'lat':
return _v.lat;
case 'lon':
return _v.lon;
case 'description':
return _v.description;
case 'when':
return _v.when.toIso8601String();
case 'sealed':
return _v.sealed;
case 'subsId':
return objectIdToJson(_v.subsId);
case 'read':
return _v.read;
}
}
return null;
}
}

View file

@ -1,8 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:fires_flutter/models/fireNotification.dart';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/fireNotification.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
final String fireNotificationKey = 'fireNotifications'; final String fireNotificationKey = 'fireNotifications';
@ -10,7 +11,7 @@ final String fireNotificationKey = 'fireNotifications';
Future<List<FireNotification>> loadFireNotifications() async { Future<List<FireNotification>> loadFireNotifications() async {
return await globals.prefs.then((prefs) { return await globals.prefs.then((prefs) {
List<String> FireNotifications = prefs.getStringList(fireNotificationKey); List<String> FireNotifications = prefs.getStringList(fireNotificationKey);
List<FireNotification> persistedList = List<FireNotification>(); List<FireNotification> persistedList = [];
if (FireNotifications == null) { if (FireNotifications == null) {
FireNotifications = []; FireNotifications = [];
// first run, init with empty list // first run, init with empty list

View file

@ -1,26 +1,26 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter/material.dart';
import 'falsePositiveTypes.dart';
import 'package:bson_objectid/bson_objectid.dart';
import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:http/http.dart' as ht; import 'package:http/http.dart' as ht;
import 'package:jaguar_resty/jaguar_resty.dart' as resty; import 'package:jaguar_resty/jaguar_resty.dart' as resty;
import 'package:latlong/latlong.dart'; import 'package:latlong/latlong.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../objectIdUtils.dart';
import '../redux/actions.dart'; import '../redux/actions.dart';
import 'appState.dart'; import 'appState.dart';
import 'falsePositiveTypes.dart';
class FiresApi { class FiresApi {
FiresApi() { FiresApi() {
resty.globalClient = new ht.IOClient(); resty.globalClient = new ht.IOClient();
} }
Future<String> createUser(AppState state, String mobileToken, Future<String> createUser(
String lang) async { AppState state, String mobileToken, String lang) async {
assert(state.firesApiUrl != null); assert(state.firesApiUrl != null);
assert(state.firesApiKey != null); assert(state.firesApiKey != null);
assert(mobileToken != null); assert(mobileToken != null);
@ -38,6 +38,8 @@ class FiresApi {
if (response.statusCode == 200) { if (response.statusCode == 200) {
// print(response.body); // print(response.body);
return json.decode(response.body)['data']['userId']; return json.decode(response.body)['data']['userId'];
} else {
return throw "Unexpected error on create user";
} }
}); });
} }
@ -45,28 +47,29 @@ class FiresApi {
Future<List<YourLocation>> fetchYourLocations(AppState state) async { Future<List<YourLocation>> fetchYourLocations(AppState state) async {
final apiKey = state.firesApiKey; final apiKey = state.firesApiKey;
final mobileToken = state.user.token; final mobileToken = state.user.token;
final String url = '${state final String url =
.firesApiUrl}mobile/subscriptions/all/$apiKey/$mobileToken'; '${state.firesApiUrl}mobile/subscriptions/all/$apiKey/$mobileToken';
// if (globals.isDevelopment) print('$url'); // if (globals.isDevelopment) print('$url');
return await resty.get(url).go().then((response) { return await resty.get(url).go().then((response) {
if (response.statusCode == 200) { if (response.statusCode == 200) {
// if (globals.isDevelopment) print(response.body); // if (globals.isDevelopment) print(response.body);
final dataSubscriptions = final dataSubscriptions =
json.decode(response.body)['data']['subscriptions']; json.decode(response.body)['data']['subscriptions'];
List<YourLocation> subscribed = []; List<YourLocation> subscribed = [];
for (int i = 0; i < dataSubscriptions.length; i++) { for (int i = 0; i < dataSubscriptions.length; i++) {
var el = dataSubscriptions[i]; var el = dataSubscriptions[i];
var lat = el['location']['lat']; var lat = el['location']['lat'];
var lon = el['location']['lon']; var lon = el['location']['lon'];
subscribed.add(new YourLocation( subscribed.add(new YourLocation(
id: ObjectId.fromHexString(el['_id']['_str']), id: objectIdFromJson(el['_id']['_str']),
lat: lat, lat: lat,
lon: lon, lon: lon,
subscribed: true, subscribed: true,
distance: el['distance'])); distance: el['distance']));
} }
return subscribed; return subscribed;
} else {
return throw "Unexpected error fetching your locations";
} }
}); });
} }
@ -83,13 +86,12 @@ class FiresApi {
final params = { final params = {
"token": state.firesApiKey, "token": state.firesApiKey,
"mobileToken": state.user.token, "mobileToken": state.user.token,
"id": loc.id.toHexString(), "id": loc.id.hexString,
"lat": loc.lat, "lat": loc.lat,
"lon": loc.lon, "lon": loc.lon,
"distance": loc.distance "distance": loc.distance
}; };
final String url = '${state final String url = '${state.firesApiUrl}mobile/subscriptions';
.firesApiUrl}mobile/subscriptions';
return await resty.post(url).json(params).go().then((response) { return await resty.post(url).json(params).go().then((response) {
if (response.statusCode == 200) { if (response.statusCode == 200) {
// print(response.body); // print(response.body);
@ -97,6 +99,7 @@ class FiresApi {
} else { } else {
// take care of? "Unexpected error in REST call: Error: The user is already subscribed to this area [on-already-subscribed]" // take care of? "Unexpected error in REST call: Error: The user is already subscribed to this area [on-already-subscribed]"
print(response.body); print(response.body);
return throw "Unexpected error on subscribe";
} }
}); });
} }
@ -108,22 +111,24 @@ class FiresApi {
final apiKey = state.firesApiKey; final apiKey = state.firesApiKey;
final mobileToken = state.user.token; final mobileToken = state.user.token;
assert(subsId != null); assert(subsId != null);
final String url = '${state final String url =
.firesApiUrl}mobile/subscriptions/$apiKey/$mobileToken/$subsId'; '${state.firesApiUrl}mobile/subscriptions/$apiKey/$mobileToken/$subsId';
return await resty.delete(url).go().then((response) { return await resty.delete(url).go().then((response) {
if (response.statusCode == 200) { if (response.statusCode == 200) {
// print(response.body); // print(response.body);
return true; return true;
} else {
return throw "Unexpected error on unsubscribe";
} }
}); });
} }
Future<UpdateFireMapStatsAction> getFiresInLocation( Future<UpdateFireMapStatsAction> getFiresInLocation(
{AppState state, double lat, double lon, int distance}) async { {AppState state, double lat, double lon, int distance}) async {
assert(state.firesApiUrl != null); assert(state.firesApiUrl != null);
assert(state.firesApiKey != null); assert(state.firesApiKey != null);
var url = '${state.firesApiUrl}fires-in-full/${state var url =
.firesApiKey}/${lat}/${lon}/${distance}'; '${state.firesApiUrl}fires-in-full/${state.firesApiKey}/$lat/$lon/$distance';
if (globals.isDevelopment) print(url); if (globals.isDevelopment) print(url);
return await resty.get(url).go().then((response) { return await resty.get(url).go().then((response) {
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -138,13 +143,13 @@ class FiresApi {
var industriesCount = industries.length; var industriesCount = industries.length;
var falsePosCount = falsePos.length; var falsePosCount = falsePos.length;
print( print(
'(Pos: $lat, $lon) real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount'); '(Pos: $lat, $lon) real: $numFires, fire: $firesCount falsePos: $falsePosCount industries: $industriesCount');
} }
return new UpdateFireMapStatsAction( return new UpdateFireMapStatsAction(
numFires: numFires, numFires: numFires,
fires: fires, fires: fires,
falsePos: falsePos, falsePos: falsePos,
industries: industries); industries: industries);
} else } else
throw Exception('Wrong response trying to get fire data'); throw Exception('Wrong response trying to get fire data');
}); });
@ -153,8 +158,8 @@ class FiresApi {
Future<List<Polyline>> getMonitoredAreas({AppState state}) async { Future<List<Polyline>> getMonitoredAreas({AppState state}) async {
assert(state.firesApiUrl != null); assert(state.firesApiUrl != null);
assert(state.firesApiKey != null); assert(state.firesApiKey != null);
var url = '${state.firesApiUrl}status/subs-public-union/${state var url =
.firesApiKey}'; '${state.firesApiUrl}status/subs-public-union/${state.firesApiKey}';
var color = const Color(0xFF145A32); var color = const Color(0xFF145A32);
return await resty.get(url).go().then((response) { return await resty.get(url).go().then((response) {
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -162,8 +167,8 @@ class FiresApi {
// print(resultDecoded['data']['union']); // print(resultDecoded['data']['union']);
List<Polyline> union = []; List<Polyline> union = [];
final multipolygon = final multipolygon =
json.decode(resultDecoded['data']['union']['value'])['geometry'] json.decode(resultDecoded['data']['union']['value'])['geometry']
['coordinates']; ['coordinates'];
for (List<dynamic> polygon in multipolygon) { for (List<dynamic> polygon in multipolygon) {
for (List<dynamic> hole in polygon) { for (List<dynamic> hole in polygon) {
List<LatLng> points = []; List<LatLng> points = [];
@ -171,7 +176,7 @@ class FiresApi {
points.add(new LatLng(point[1].toDouble(), point[0].toDouble())); points.add(new LatLng(point[1].toDouble(), point[0].toDouble()));
} }
union.add( union.add(
new Polyline(points: points, color: color, strokeWidth: 3.0)); new Polyline(points: points, color: color, strokeWidth: 3.0));
} }
} }
return union; return union;
@ -181,7 +186,7 @@ class FiresApi {
} }
Future<bool> markFalsePositive(AppState state, String mobileToken, Future<bool> markFalsePositive(AppState state, String mobileToken,
String sealed, FalsePositiveType type) async { String sealed, FalsePositiveType type) async {
assert(state.firesApiUrl != null); assert(state.firesApiUrl != null);
assert(state.firesApiKey != null); assert(state.firesApiKey != null);
assert(mobileToken != null); assert(mobileToken != null);
@ -198,8 +203,8 @@ class FiresApi {
return await resty.post(url).json(params).go().then((response) { return await resty.post(url).json(params).go().then((response) {
if (response.statusCode == 200) { if (response.statusCode == 200) {
// print(response.body); // print(response.body);
if (globals.isDevelopment) print( if (globals.isDevelopment)
json.decode(response.body)['data']['upsert']); print(json.decode(response.body)['data']['upsert']);
return true; return true;
} else { } else {
debugPrint(json.decode(response.body)); debugPrint(json.decode(response.body));

View file

@ -1,14 +1,12 @@
import 'package:bson_objectid/bson_objectid.dart'; import 'package:fires_flutter/objectIdUtils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:fires_flutter/objectIdUtils.dart'; import 'package:objectid/objectid.dart';
part 'yourLocation.g.dart'; part 'yourLocation.g.dart';
@JsonSerializable(nullable: false) @JsonSerializable(nullable: false)
class YourLocation extends Object with _$YourLocationSerializerMixin { class YourLocation {
@JsonKey(toJson: objectIdToJson, fromJson: objectIdFromJson) @JsonKey(toJson: objectIdToJson, fromJson: objectIdFromJson)
ObjectId id; ObjectId id;
final double lat; final double lat;
@ -22,40 +20,33 @@ class YourLocation extends Object with _$YourLocationSerializerMixin {
_$YourLocationFromJson(json); _$YourLocationFromJson(json);
static YourLocation noLocation = new YourLocation(lat: 0.0, lon: 0.0); static YourLocation noLocation = new YourLocation(lat: 0.0, lon: 0.0);
static const int withoutStats = null; static const int withoutStats = null;
YourLocation( YourLocation(
{this.id, {this.id,
@required this.lat, @required this.lat,
@required this.lon, @required this.lon,
this.description, this.description,
this.distance: 10, this.distance: 10,
int currentNumFires: withoutStats, int currentNumFires: withoutStats,
this.subscribed: false}) { this.subscribed: false}) {
if (this.description == null) if (this.description == null)
this.description = 'Position: ${this.lat}, ${this.lon}'; this.description = 'Position: ${this.lat}, ${this.lon}';
if (this.id == null) this.id = new ObjectId(); if (this.id == null) this.id = new ObjectId();
} }
Map<String, dynamic> toJson() => _$YourLocationToJson(this);
YourLocation copyWith( YourLocation copyWith(
{id, {id, lat, lon, description, distance, currentNumFires, subscribed}) {
lat,
lon,
description,
distance,
currentNumFires,
subscribed}) {
return new YourLocation( return new YourLocation(
id: id?? this.id, id: id ?? this.id,
lat: lat?? this.lat, lat: lat ?? this.lat,
lon: lon?? this.lon, lon: lon ?? this.lon,
description: description?? this.description, description: description ?? this.description,
distance: distance?? this.distance, distance: distance ?? this.distance,
currentNumFires: currentNumFires ?? this.currentNumFires, currentNumFires: currentNumFires ?? this.currentNumFires,
subscribed: subscribed?? this.subscribed subscribed: subscribed ?? this.subscribed);
);
} }
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>
identical(this, other) || identical(this, other) ||
@ -66,7 +57,7 @@ static const int withoutStats = null;
lon == other.lon && lon == other.lon &&
description == other.description && description == other.description &&
subscribed == other.subscribed && subscribed == other.subscribed &&
currentNumFires == other.currentNumFires && currentNumFires == other.currentNumFires &&
distance == other.distance; distance == other.distance;
@override @override

View file

@ -1,5 +1,3 @@
// Copyright (c) 2018, Comunes Association.
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'yourLocation.dart'; part of 'yourLocation.dart';
@ -8,51 +6,29 @@ part of 'yourLocation.dart';
// JsonSerializableGenerator // JsonSerializableGenerator
// ************************************************************************** // **************************************************************************
YourLocation _$YourLocationFromJson(Map<String, dynamic> json) => YourLocation _$YourLocationFromJson(Map json) {
new YourLocation( return $checkedNew('YourLocation', json, () {
id: objectIdFromJson(json['id'] as String), final val = YourLocation(
lat: (json['lat'] as num).toDouble(), id: $checkedConvert(json, 'id', (v) => objectIdFromJson(v as String)),
lon: (json['lon'] as num).toDouble(), lat: $checkedConvert(json, 'lat', (v) => (v as num).toDouble()),
description: json['description'] as String, lon: $checkedConvert(json, 'lon', (v) => (v as num).toDouble()),
distance: json['distance'] as int, description: $checkedConvert(json, 'description', (v) => v as String),
subscribed: json['subscribed'] as bool); distance: $checkedConvert(json, 'distance', (v) => v as int),
currentNumFires:
abstract class _$YourLocationSerializerMixin { $checkedConvert(json, 'currentNumFires', (v) => v as int),
ObjectId get id; subscribed: $checkedConvert(json, 'subscribed', (v) => v as bool),
double get lat; );
double get lon; return val;
String get description; });
bool get subscribed;
int get distance;
Map<String, dynamic> toJson() => new _$YourLocationJsonMapWrapper(this);
} }
class _$YourLocationJsonMapWrapper extends $JsonMapWrapper { Map<String, dynamic> _$YourLocationToJson(YourLocation instance) =>
final _$YourLocationSerializerMixin _v; <String, dynamic>{
_$YourLocationJsonMapWrapper(this._v); 'id': objectIdToJson(instance.id),
'lat': instance.lat,
@override 'lon': instance.lon,
Iterable<String> get keys => 'description': instance.description,
const ['id', 'lat', 'lon', 'description', 'subscribed', 'distance']; 'subscribed': instance.subscribed,
'distance': instance.distance,
@override 'currentNumFires': instance.currentNumFires,
dynamic operator [](Object key) { };
if (key is String) {
switch (key) {
case 'id':
return objectIdToJson(_v.id);
case 'lat':
return _v.lat;
case 'lon':
return _v.lon;
case 'description':
return _v.description;
case 'subscribed':
return _v.subscribed;
case 'distance':
return _v.distance;
}
}
return null;
}
}

View file

@ -1,8 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
final String locationKey = 'yourlocations'; final String locationKey = 'yourlocations';
@ -10,7 +11,7 @@ final String locationKey = 'yourlocations';
Future<List<YourLocation>> loadYourLocations() async { Future<List<YourLocation>> loadYourLocations() async {
return await globals.prefs.then((prefs) { return await globals.prefs.then((prefs) {
List<String> yourLocations = prefs.getStringList(locationKey); List<String> yourLocations = prefs.getStringList(locationKey);
List<YourLocation> persistedList = List<YourLocation>(); List<YourLocation> persistedList = [];
if (yourLocations == null) { if (yourLocations == null) {
yourLocations = []; yourLocations = [];
// first run, init with empty list // first run, init with empty list

View file

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

View file

@ -1,12 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_google_places_autocomplete/flutter_google_places_autocomplete.dart'; import 'package:flutter_google_places_autocomplete/flutter_google_places_autocomplete.dart';
import 'package:fires_flutter/models/yourLocation.dart';
import 'generated/i18n.dart';
import 'package:flutter_simple_dependency_injection/injector.dart'; import 'package:flutter_simple_dependency_injection/injector.dart';
import 'generated/i18n.dart';
Future<YourLocation> openPlacesDialog(GlobalKey<ScaffoldState> sc) async { Future<YourLocation> openPlacesDialog(GlobalKey<ScaffoldState> sc) async {
Mode _mode = Mode.overlay; Mode _mode = Mode.overlay;
String gmapKey = Injector.getInjector().get<String>(key: "gmapKey"); String gmapKey = Injector.getInjector().get<String>(key: "gmapKey");
@ -16,7 +16,7 @@ Future<YourLocation> openPlacesDialog(GlobalKey<ScaffoldState> sc) async {
hint: S.of(sc.currentContext).typeTheNameOfAPlace, hint: S.of(sc.currentContext).typeTheNameOfAPlace,
apiKey: gmapKey, apiKey: gmapKey,
onError: (res) { onError: (res) {
sc.currentState ScaffoldMessenger.of(sc.currentContext)
.showSnackBar(new SnackBar(content: new Text(res.errorMessage))); .showSnackBar(new SnackBar(content: new Text(res.errorMessage)));
print('Error $res'); print('Error $res');
}, },

View file

@ -1,16 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'package:bson_objectid/bson_objectid.dart';
import 'package:fires_flutter/models/fireNotification.dart'; import 'package:fires_flutter/models/fireNotification.dart';
import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart';
import 'package:flutter_simple_dependency_injection/injector.dart'; import 'package:flutter_simple_dependency_injection/injector.dart';
import 'package:just_debounce_it/just_debounce_it.dart'; import 'package:just_debounce_it/just_debounce_it.dart';
import 'package:objectid/objectid.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import '../models/appState.dart'; import '../models/appState.dart';
import '../models/fireNotificationsPersist.dart'; import '../models/fireNotificationsPersist.dart';
import '../models/firesApi.dart'; import '../models/firesApi.dart';
import '../models/yourLocationPersist.dart'; import '../models/yourLocationPersist.dart';
import '../objectIdUtils.dart';
import 'actions.dart'; import 'actions.dart';
// A middleware takes in 3 parameters: your Store, which you can use to // A middleware takes in 3 parameters: your Store, which you can use to
@ -147,16 +148,18 @@ void fetchDataMiddleware(Store<AppState> store, action, NextDispatcher next) {
.then((List<YourLocation> subscribedLocations) { .then((List<YourLocation> subscribedLocations) {
// If it succeeds, dispatch a success action with the YourLocations. // If it succeeds, dispatch a success action with the YourLocations.
// Our reducer will then update the State using these YourLocations. // Our reducer will then update the State using these YourLocations.
// print('Subscribed to: ${subscribedLocations.length}'); print('Subscribed to: ${subscribedLocations.length}');
if (subscribedLocations is List) { if (subscribedLocations is List) {
// unsubscribe all locally to sync the subs state // unsubscribe all locally to sync the subs state
localLocations.forEach((location) => location.subscribed = false); localLocations.forEach((location) => location.subscribed = false);
// print('Local persisted: ${localLocations.length}'); print('Local persisted: ${localLocations.length}');
subscribedLocations.forEach((subsLoc) { subscribedLocations.forEach((subsLoc) {
localLocations.firstWhere( var locSubs = localLocations.firstWhere(
(localLocation) => localLocation.id == subsLoc.id, orElse: () { (localLocation) => localLocation.id == subsLoc.id, orElse: () {
localLocations.add(subsLoc); localLocations.add(subsLoc);
}).subscribed = true; return subsLoc;
});
locSubs.subscribed = true;
}); });
} }
@ -251,7 +254,7 @@ void getFiresStatsInFire(Store<AppState> store, FireNotification notif) {
} }
void unsubsViaApi(Store<AppState> store, ObjectId id, onUnsubs) { void unsubsViaApi(Store<AppState> store, ObjectId id, onUnsubs) {
api.unsubscribe(store.state, id.toHexString()).then((res) { api.unsubscribe(store.state, id.hexString).then((res) {
onUnsubs(); onUnsubs();
persistYourLocations(store.state.yourLocations); persistYourLocations(store.state.yourLocations);
}); });
@ -260,9 +263,9 @@ void unsubsViaApi(Store<AppState> store, ObjectId id, onUnsubs) {
void subscribeViaApi(Store<AppState> store, YourLocation loc, onSubs) { void subscribeViaApi(Store<AppState> store, YourLocation loc, onSubs) {
api.subscribe(store.state, loc).then((subsId) { api.subscribe(store.state, loc).then((subsId) {
YourLocation sub = loc; YourLocation sub = loc;
if (loc.id != subsId) { // if (loc.id != subsId) {
sub.id = new ObjectId.fromHexString(subsId); sub.id = objectIdFromJson(subsId);
} // }
onSubs(sub); onSubs(sub);
persistYourLocations(store.state.yourLocations); persistYourLocations(store.state.yourLocations);
}); });

View file

@ -28,7 +28,8 @@ List<FireNotification> _deletedFireNotification(
} }
List<FireNotification> _updatedFireNotification( List<FireNotification> _updatedFireNotification(
List<FireNotification> notifications, UpdatedFireNotificationAction action) { List<FireNotification> notifications,
UpdatedFireNotificationAction action) {
return notifications return notifications
.map((notif) => notif.id == action.notif.id ? action.notif : notif) .map((notif) => notif.id == action.notif.id ? action.notif : notif)
.toList(); .toList();
@ -45,5 +46,5 @@ List<FireNotification> _readedFireNotification(
List<FireNotification> _deletedAllFireNotifications( List<FireNotification> _deletedAllFireNotifications(
List<FireNotification> notifications, List<FireNotification> notifications,
DeletedAllFireNotificationAction action) { DeletedAllFireNotificationAction action) {
return new List<FireNotification>(); return [];
} }

View file

@ -1,5 +1,5 @@
import 'package:bson_objectid/bson_objectid.dart';
import 'package:fires_flutter/models/yourLocation.dart'; import 'package:fires_flutter/models/yourLocation.dart';
import 'package:objectid/objectid.dart';
abstract class YourLocationActions {} abstract class YourLocationActions {}

View file

@ -12,7 +12,7 @@ ThemeData _buildFiresTheme() {
buttonColor: fires900, buttonColor: fires900,
scaffoldBackgroundColor: firesSurfaceWhite, scaffoldBackgroundColor: firesSurfaceWhite,
cardColor: firesBackgroundWhite, cardColor: firesBackgroundWhite,
textSelectionColor: fires300, textSelectionTheme: TextSelectionThemeData(selectionColor: fires300),
errorColor: firesErrorRed, errorColor: firesErrorRed,
//TODO: Add the text themes (103) //TODO: Add the text themes (103)

View file

@ -12,7 +12,7 @@ ThemeData _buildFiresTheme() {
buttonColor: fires900, buttonColor: fires900,
scaffoldBackgroundColor: firesSurfaceWhite, scaffoldBackgroundColor: firesSurfaceWhite,
cardColor: firesBackgroundWhite, cardColor: firesBackgroundWhite,
textSelectionColor: fires300, textSelectionTheme: TextSelectionThemeData(selectionColor: fires300),
errorColor: firesErrorRed, errorColor: firesErrorRed,
//TODO: Add the text themes (103) //TODO: Add the text themes (103)

View file

@ -1,7 +1,6 @@
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:flutter/material.dart';
import 'package:comunes_flutter/comunes_flutter.dart'; import 'package:comunes_flutter/comunes_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/plugin_api.dart';
class ZoomMapPluginOptions extends LayerOptions { class ZoomMapPluginOptions extends LayerOptions {
final String text; final String text;
@ -22,7 +21,8 @@ class ZoomMapPlugin extends MapPlugin {
} }
@override @override
Widget createLayer(LayerOptions options, MapState mapState) { Widget createLayer(
LayerOptions options, MapState mapState, Stream<Null> stream) {
if (options is ZoomMapPluginOptions) { if (options is ZoomMapPluginOptions) {
/* print('point ${mapState /* print('point ${mapState
.getPixelBounds(mapState.zoom) .getPixelBounds(mapState.zoom)

View file

@ -1,39 +1,42 @@
name: fires_flutter name: fires_flutter
description: All Against Fire description: All Against Fire
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# i18n # i18n
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
intl: "^0.15.6" intl: "^0.16.1"
# https://pub.dartlang.org/packages/flutter # https://pub.dartlang.org/packages/flutter
# utils # utils
simple_moment: "^0.0.3" simple_moment: "^1.0.7"
just_debounce_it: "^1.0.4" just_debounce_it: "^3.0.0+2"
flutter_simple_dependency_injection: "^0.0.2" flutter_simple_dependency_injection: "^0.0.2"
sentry: "^2.0.2" sentry: "^2.0.2"
flutter_markdown: "^0.1.5" flutter_markdown: "^0.5.1"
url_launcher: "^3.0.2" url_launcher: "^5.7.10"
package_info: "^0.3.2" package_info: ^0.4.3+2
launch_review: "^1.0.1" launch_review: "^1.0.1"
# net # net
http: "^0.11.3+16" http: "^0.11.3+16"
jaguar_resty: "^2.5.5" jaguar_resty: "^2.5.5"
connectivity: "^0.3.1" connectivity: ^2.0.2
share: "^0.5.2" share: "^0.5.2"
# data # data
json_annotation: "^0.2.3" json_annotation: "^3.1.1"
shared_preferences: "^0.4.2" shared_preferences: "^0.4.2"
bson_objectid: "^0.1.0" # bson_objectid: "^0.1.0
objectid: 1.1.0
comunes_flutter: comunes_flutter:
git: git@github.com:comunes/comunes-flutter.git #git: git@github.com:comunes/comunes-flutter.git
# path: /home/vjrj/dev/comunes_flutter path: /home/vjrj/dev/comunes_flutter
# version: "^0.0.12" # version: "^0.0.12"
# redux # redux
@ -42,33 +45,36 @@ dependencies:
redux_logging: "^0.3.0" redux_logging: "^0.3.0"
# maps, geo, etc # maps, geo, etc
flutter_map: "^0.0.10" flutter_map: ^0.10.1+1
flutter_google_places_autocomplete: "^0.0.4" flutter_google_places_autocomplete: "^0.1.3"
location: "^1.3.4" location: ^3.2.1
geocoder: "^0.0.1" geocoder: "^0.2.1"
# layout # layout
fluttery: "^0.0.7" # fluttery: "^0.0.7"
# https://pub.dartlang.org/packages/padder # https://pub.dartlang.org/packages/padder
padder: "^1.0.1" padder: "^1.0.1"
flutter_fab_dialer: "^0.0.5" flutter_fab_dialer: "^0.0.5"
badge: "^0.0.2" # badge: "^0.0.2"
badges: ^1.1.6
flutter_spinkit: "^1.0.0" flutter_spinkit: "^1.0.0"
# firebase # firebase
firebase_messaging: "^1.0.4" firebase_core: ^0.5.3
firebase_messaging: ^7.0.3
# icons # icons
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: "^0.1.2" cupertino_icons: "^0.1.2"
community_material_icon: "^0.1.2" community_material_icon: "^5.4.55"
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^0.8.0 build_runner: "^1.0.0"
json_serializable: ^0.5.0 json_serializable: ^3.5.1
test: ^1.15.7
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec # following page: https://www.dartlang.org/tools/pub/pubspec
@ -119,3 +125,7 @@ flutter:
# #
# For details regarding fonts from package dependencies, # For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages # see https://flutter.io/custom-fonts/#from-packages
# https://github.com/flutter/flutter/issues/70433
dependency_overrides:
intl: ^0.17.0-nullsafety.2