Many work in navegation, map, drawer, about, slider
This commit is contained in:
parent
662deb3086
commit
574c1afc33
15 changed files with 446 additions and 168 deletions
BIN
images/industry-marker-reg.png
Normal file
BIN
images/industry-marker-reg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 360 B |
BIN
images/industry-marker.png
Normal file
BIN
images/industry-marker.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 339 B |
|
|
@ -9,10 +9,8 @@ import 'basicLocation.dart';
|
||||||
import 'locationUtils.dart';
|
import 'locationUtils.dart';
|
||||||
import 'globals.dart' as globals;
|
import 'globals.dart' as globals;
|
||||||
import 'basicLocationPersist.dart';
|
import 'basicLocationPersist.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'dart:convert' show json;
|
|
||||||
import 'globalFiresBottomStats.dart';
|
import 'globalFiresBottomStats.dart';
|
||||||
import 'dart:convert';
|
import 'package:flutter_fab_dialer/flutter_fab_dialer.dart';
|
||||||
|
|
||||||
class ActiveFiresPage extends StatefulWidget {
|
class ActiveFiresPage extends StatefulWidget {
|
||||||
static const String routeName = '/fires';
|
static const String routeName = '/fires';
|
||||||
|
|
@ -26,39 +24,68 @@ class ActiveFiresPage extends StatefulWidget {
|
||||||
class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
List<FabMiniMenuItem> _fabMiniMenuItemList(BuildContext context) {
|
||||||
|
return [
|
||||||
|
new FabMiniMenuItem.withText(
|
||||||
|
new Icon(Icons.location_searching),
|
||||||
|
fires600,
|
||||||
|
8.0,
|
||||||
|
"Add your current position",
|
||||||
|
() {
|
||||||
|
onAddYourLocation();
|
||||||
|
},
|
||||||
|
"Add your current position",
|
||||||
|
fires300,
|
||||||
|
Colors.white,
|
||||||
|
),
|
||||||
|
new FabMiniMenuItem.withText(
|
||||||
|
new Icon(Icons.edit_location),
|
||||||
|
fires600,
|
||||||
|
8.0,
|
||||||
|
"Add some other place",
|
||||||
|
() {
|
||||||
|
onAddOtherLocation(context);
|
||||||
|
},
|
||||||
|
"Add some other place",
|
||||||
|
fires300,
|
||||||
|
Colors.white)
|
||||||
|
];}
|
||||||
|
|
||||||
_ActiveFiresPageState();
|
_ActiveFiresPageState();
|
||||||
|
|
||||||
Widget _buildRow(BasicLocation loc) {
|
Widget _buildRow(BasicLocation loc) {
|
||||||
String desc = loc.description != null
|
|
||||||
? loc.description
|
|
||||||
: 'Position: ${loc.lat}, ${loc.lon}';
|
|
||||||
return new ListTile(
|
return new ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
leading: const Icon(Icons.location_on),
|
leading: const Icon(Icons.location_on),
|
||||||
trailing: const Icon(Icons.notifications_off),
|
trailing: new IconButton(
|
||||||
title: new Text(desc),
|
icon: new Icon(loc.isSubscribed
|
||||||
|
? Icons.notifications_active
|
||||||
|
: Icons.notifications_off),
|
||||||
|
onPressed: () {
|
||||||
|
loc.subscribed = !loc.isSubscribed;
|
||||||
|
persistYourLocations();
|
||||||
|
setState(() {});
|
||||||
|
}),
|
||||||
|
title: new Text(loc.description),
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
showSnackMsg('Slide horizontally to delete this location');
|
showSnackMsg('Slide horizontally to delete this location');
|
||||||
},
|
},
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const String km = '100';
|
showLocationMap(loc);
|
||||||
var url = '${globals.firesApiUrl}fires-in/${globals
|
|
||||||
.firesApiKey}/${loc.lat}/${loc
|
|
||||||
.lon}/$km';
|
|
||||||
http.read(url).then((result) {
|
|
||||||
int numFires = json.decode(result)['real'];
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
new MaterialPageRoute(
|
|
||||||
builder: (context) => new LeafletMap(
|
|
||||||
title: desc,
|
|
||||||
location: loc,
|
|
||||||
numFires: numFires,
|
|
||||||
kmAround: km)));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void showLocationMap(BasicLocation loc) {
|
||||||
|
// , VoidCallback onSubs
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
new MaterialPageRoute(
|
||||||
|
builder: (context) => new LeafletMap(
|
||||||
|
title: loc.description,
|
||||||
|
location: loc,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
void showSnackMsg(String msg) {
|
void showSnackMsg(String msg) {
|
||||||
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||||
content: new Text(msg),
|
content: new Text(msg),
|
||||||
|
|
@ -91,7 +118,7 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
void handleUndo(BasicLocation item) {
|
void handleUndo(BasicLocation item) {
|
||||||
setState(() {
|
setState(() {
|
||||||
globals.yourLocations.add(item);
|
globals.yourLocations.add(item);
|
||||||
persist();
|
persistYourLocations();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +130,7 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
onDismissed: (DismissDirection direction) {
|
onDismissed: (DismissDirection direction) {
|
||||||
setState(() {
|
setState(() {
|
||||||
globals.yourLocations.remove(item);
|
globals.yourLocations.remove(item);
|
||||||
persist();
|
persistYourLocations();
|
||||||
});
|
});
|
||||||
final String action = 'deleted';
|
final String action = 'deleted';
|
||||||
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||||
|
|
@ -132,16 +159,11 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
child: _buildRow(item)));
|
child: _buildRow(item)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void persist() {
|
|
||||||
globals.prefs.then((prefs) {
|
|
||||||
persistYourLocations(prefs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var hasLocations = globals.yourLocations.length > 0;
|
||||||
|
final title = hasLocations ? 'Active fires in your places': 'Active fires in the world';
|
||||||
print('Building Active Fires');
|
print('Building Active Fires');
|
||||||
final title = 'Your locations';
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
// FIXME new?
|
// FIXME new?
|
||||||
|
|
@ -157,29 +179,32 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
bottomNavigationBar: new GlobalFiresBottomStats(),
|
bottomNavigationBar: new GlobalFiresBottomStats(),
|
||||||
body: globals.yourLocations.length > 0
|
body: hasLocations
|
||||||
? _buildSavedLocations()
|
? new Stack(children: <Widget>[
|
||||||
|
_buildSavedLocations(),
|
||||||
|
new FabDialer(_fabMiniMenuItemList(context), fires600, new Icon(Icons.add))
|
||||||
|
])
|
||||||
: new Center(
|
: new Center(
|
||||||
child: new CenteredColumn(children: <Widget>[
|
child: new CenteredColumn(children: <Widget>[
|
||||||
new RoundedBtn(
|
new RoundedBtn(
|
||||||
icon: Icons.location_searching,
|
icon: Icons.location_searching,
|
||||||
text: 'Add your position',
|
text: 'Fires near your',
|
||||||
onPressed: onAddYourLocation,
|
onPressed: onAddYourLocation,
|
||||||
backColor: fires600),
|
backColor: fires600),
|
||||||
const SizedBox(height: 26.0),
|
const SizedBox(height: 26.0),
|
||||||
new RoundedBtn(
|
new RoundedBtn(
|
||||||
icon: Icons.edit_location,
|
icon: Icons.edit_location,
|
||||||
text: 'Add some other place',
|
text: 'Fires near other place',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onAddOtherLocation(context);
|
onAddOtherLocation(context);
|
||||||
},
|
},
|
||||||
backColor: fires600),
|
backColor: fires600),
|
||||||
])),
|
])),
|
||||||
floatingActionButton: globals.yourLocations.length > 0
|
floatingActionButton: hasLocations
|
||||||
? Column(mainAxisAlignment: MainAxisAlignment.end,
|
? Column(mainAxisAlignment: MainAxisAlignment.end,
|
||||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FloatingActionButton.extended(
|
/* FloatingActionButton.extended(
|
||||||
onPressed: onAddYourLocation,
|
onPressed: onAddYourLocation,
|
||||||
heroTag: 'yourposition',
|
heroTag: 'yourposition',
|
||||||
label: const Text('Add your position'),
|
label: const Text('Add your position'),
|
||||||
|
|
@ -195,7 +220,7 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
label: new Text('Add some other place'),
|
label: new Text('Add some other place'),
|
||||||
icon: const Icon(Icons.edit_location),
|
icon: const Icon(Icons.edit_location),
|
||||||
),
|
),
|
||||||
)
|
), */
|
||||||
])
|
])
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
|
@ -219,7 +244,10 @@ class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
} else
|
} else
|
||||||
this.setState(() {
|
this.setState(() {
|
||||||
globals.yourLocations.add(newLocation);
|
globals.yourLocations.add(newLocation);
|
||||||
persist();
|
persistYourLocations();
|
||||||
|
new Timer(new Duration(milliseconds: 1000), () {
|
||||||
|
showLocationMap(newLocation);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,14 @@ class BasicLocation extends Comparable<BasicLocation> {
|
||||||
final double lat;
|
final double lat;
|
||||||
final double lon;
|
final double lon;
|
||||||
String description;
|
String description;
|
||||||
|
bool subscribed;
|
||||||
|
|
||||||
static BasicLocation noLocation = new BasicLocation(lat: 0.0, lon: 0.0);
|
static BasicLocation noLocation = new BasicLocation(lat: 0.0, lon: 0.0);
|
||||||
|
|
||||||
BasicLocation({@required this.lat, @required this.lon, this.description});
|
BasicLocation({@required this.lat, @required this.lon, this.description, this.subscribed: false}) {
|
||||||
|
if (this.description == null)
|
||||||
|
this.description = 'Position: ${this.lat}, ${this.lon}';
|
||||||
|
}
|
||||||
|
|
||||||
int compareTo(BasicLocation other) {
|
int compareTo(BasicLocation other) {
|
||||||
return lat == other.lat && lon == other.lon ? 1 : 0;
|
return lat == other.lat && lon == other.lon ? 1 : 0;
|
||||||
|
|
@ -15,6 +19,17 @@ class BasicLocation extends Comparable<BasicLocation> {
|
||||||
|
|
||||||
bool operator ==(o) => o is BasicLocation && o.lat == lat && o.lon == lon;
|
bool operator ==(o) => o is BasicLocation && o.lat == lat && o.lon == lon;
|
||||||
|
|
||||||
|
int get hashCode {
|
||||||
|
int hash = 1;
|
||||||
|
hash = hash * 17 + lat.hashCode;
|
||||||
|
hash = hash * 31 + lon.hashCode;
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isSubscribed {
|
||||||
|
return subscribed == null ? false : subscribed;
|
||||||
|
}
|
||||||
|
|
||||||
BasicLocation.fromJson(Map<String, dynamic> json)
|
BasicLocation.fromJson(Map<String, dynamic> json)
|
||||||
: lat = json['lat'],
|
: lat = json['lat'],
|
||||||
lon = json['lon'],
|
lon = json['lon'],
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,36 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'basicLocation.dart';
|
import 'basicLocation.dart';
|
||||||
import 'globals.dart' as globals;
|
import 'globals.dart' as globals;
|
||||||
|
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
final String locationKey = 'yourlocations';
|
final String locationKey = 'yourlocations';
|
||||||
|
|
||||||
void loadYourLocations(prefs) {
|
void loadYourLocations() {
|
||||||
List<String> yourLocations = prefs.getStringList(locationKey);
|
globals.prefs.then((prefs) {
|
||||||
if (yourLocations == null) {
|
loadYourLocationsWithPrefs(prefs);
|
||||||
yourLocations = [];
|
|
||||||
persistYourLocations(prefs);
|
|
||||||
}
|
|
||||||
yourLocations.forEach((locationString) {
|
|
||||||
Map locationMap = json.decode(locationString);
|
|
||||||
globals.yourLocations.add(BasicLocation.fromJson(locationMap));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
persistYourLocations(prefs) {
|
persistYourLocations() {
|
||||||
List<String> yourLocationsAsString = [];
|
globals.prefs.then((prefs) {
|
||||||
globals.yourLocations.forEach((location) {
|
List<String> yourLocationsAsString = [];
|
||||||
yourLocationsAsString.add(json.encode(location.toJson()));
|
globals.yourLocations.forEach((location) {
|
||||||
|
yourLocationsAsString.add(json.encode(location.toJson()));
|
||||||
|
});
|
||||||
|
prefs.setStringList(locationKey, yourLocationsAsString);
|
||||||
});
|
});
|
||||||
prefs.setStringList(locationKey, yourLocationsAsString);
|
}
|
||||||
}
|
|
||||||
|
void loadYourLocationsWithPrefs(SharedPreferences prefs) {
|
||||||
|
globals.prefs.then((prefs) {
|
||||||
|
List<String> yourLocations = prefs.getStringList(locationKey);
|
||||||
|
if (yourLocations == null) {
|
||||||
|
yourLocations = [];
|
||||||
|
persistYourLocations();
|
||||||
|
}
|
||||||
|
yourLocations.forEach((locationString) {
|
||||||
|
Map locationMap = json.decode(locationString);
|
||||||
|
globals.yourLocations.add(BasicLocation.fromJson(locationMap));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ class CustomBottomAppBar extends StatelessWidget {
|
||||||
const CustomBottomAppBar(
|
const CustomBottomAppBar(
|
||||||
{this.fabLocation,
|
{this.fabLocation,
|
||||||
this.showNotch,
|
this.showNotch,
|
||||||
|
this.height = 56.0,
|
||||||
this.color = Colors.black45,
|
this.color = Colors.black45,
|
||||||
this.mainAxisAlignment = MainAxisAlignment.center,
|
this.mainAxisAlignment = MainAxisAlignment.center,
|
||||||
this.actions});
|
this.actions});
|
||||||
|
|
||||||
final Color color;
|
final Color color;
|
||||||
|
final double height;
|
||||||
final MainAxisAlignment mainAxisAlignment;
|
final MainAxisAlignment mainAxisAlignment;
|
||||||
final FloatingActionButtonLocation fabLocation;
|
final FloatingActionButtonLocation fabLocation;
|
||||||
final bool showNotch;
|
final bool showNotch;
|
||||||
|
|
@ -22,7 +24,7 @@ class CustomBottomAppBar extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<Widget> rowContents = <Widget>[new SizedBox(height: 56.0)];
|
final List<Widget> rowContents = <Widget>[new SizedBox(height: height)];
|
||||||
|
|
||||||
if (kCenterLocations.contains(fabLocation)) {
|
if (kCenterLocations.contains(fabLocation)) {
|
||||||
/* rowContents.add(
|
/* rowContents.add(
|
||||||
|
|
|
||||||
58
lib/customMapPlugin.dart
Normal file
58
lib/customMapPlugin.dart
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
class ZoomMapPluginOptions extends LayerOptions {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
ZoomMapPluginOptions({this.text = ""});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/apptreesoftware/flutter_map/blob/master/flutter_map_example/lib/pages/plugin_api.dart
|
||||||
|
class ZoomMapPlugin extends MapPlugin {
|
||||||
|
IconButton zoomButton(MapState mapState, IconData zoomIcon, int inc) {
|
||||||
|
return new IconButton(
|
||||||
|
icon: Icon(zoomIcon),
|
||||||
|
onPressed: () {
|
||||||
|
var currentZoom = mapState.zoom;
|
||||||
|
var currentCenter = mapState.center;
|
||||||
|
mapState.move(currentCenter, currentZoom + inc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget createLayer(LayerOptions options, MapState mapState) {
|
||||||
|
if (options is ZoomMapPluginOptions) {
|
||||||
|
/* print('point ${mapState
|
||||||
|
.getPixelBounds(mapState.zoom)
|
||||||
|
.bottomLeft}'); */
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) =>
|
||||||
|
Stack(fit: StackFit.expand, children: <Widget>[
|
||||||
|
Positioned(
|
||||||
|
top: constraints.maxHeight - 100,
|
||||||
|
right: 10.0,
|
||||||
|
// left: 10.0,
|
||||||
|
child: new CenteredRow(
|
||||||
|
children: <Widget>[
|
||||||
|
new Column(
|
||||||
|
children: <Widget>[
|
||||||
|
zoomButton(mapState, Icons.zoom_in, 1),
|
||||||
|
zoomButton(mapState, Icons.zoom_out, -1)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
throw ("Unknown options type for ZoomMapPlugin"
|
||||||
|
"plugin: $options");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool supportsLayer(LayerOptions options) {
|
||||||
|
return options is ZoomMapPluginOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,16 @@ import 'package:get_it/get_it.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'basicLocation.dart';
|
import 'basicLocation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
String gmapKey;
|
String gmapKey;
|
||||||
String firesApiKey;
|
String firesApiKey;
|
||||||
String firesApiUrl;
|
String firesApiUrl;
|
||||||
String appName = 'All Against The Fire!';
|
String appName = 'All Against The Fire!';
|
||||||
|
String appVersion = '0.0.1';
|
||||||
|
String appLicense = "(c) 2017-2018 Comunes Association under the GNU Affero GPL v3";
|
||||||
|
Widget appMediumIcon = Image.asset('images/logo-200.png', width: 60.0, height: 60.0);
|
||||||
|
Widget appIcon = Image.asset('images/logo-200.png', width: 24.0, height: 24.0);
|
||||||
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
||||||
final List<BasicLocation> yourLocations = [];
|
final List<BasicLocation> yourLocations = [];
|
||||||
final GetIt getIt = new GetIt();
|
final GetIt getIt = new GetIt();
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ class HomePage extends StatelessWidget {
|
||||||
]),
|
]),
|
||||||
new Expanded(
|
new Expanded(
|
||||||
child: new FractionallySizedBox(
|
child: new FractionallySizedBox(
|
||||||
alignment: FractionalOffset.bottomCenter,
|
alignment: FractionalOffset.center,
|
||||||
heightFactor: 0.9,
|
heightFactor: 0.8,
|
||||||
child: new Image.asset('images/logo-200.png',
|
child: new Image.asset('images/logo-200.png',
|
||||||
fit: BoxFit.fitHeight))),
|
fit: BoxFit.fitHeight))),
|
||||||
new Expanded(
|
new Expanded(
|
||||||
|
|
|
||||||
|
|
@ -7,21 +7,24 @@ import 'package:comunes_flutter/comunes_flutter.dart';
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
import 'customBottomAppBar.dart';
|
import 'customBottomAppBar.dart';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
|
import 'globals.dart' as globals;
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'dart:convert' show json;
|
||||||
|
import 'slider.dart';
|
||||||
|
import 'customMapPlugin.dart';
|
||||||
|
import 'package:just_debounce_it/just_debounce_it.dart';
|
||||||
|
|
||||||
|
enum MarkType { position, fire, industry, falsePos }
|
||||||
|
|
||||||
class LeafletMap extends StatefulWidget {
|
class LeafletMap extends StatefulWidget {
|
||||||
final BasicLocation location;
|
final BasicLocation location;
|
||||||
final String title;
|
final String title;
|
||||||
final int numFires;
|
|
||||||
final String kmAround;
|
|
||||||
|
|
||||||
LeafletMap({@required this.title, @required this.location,
|
LeafletMap({@required this.title, @required this.location});
|
||||||
@required this.numFires, @required this.kmAround
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LeafletMapState createState() => _LeafletMapState(title: title, location: location,
|
_LeafletMapState createState() =>
|
||||||
numFires: numFires, kmAround: kmAround
|
_LeafletMapState(title: title, location: location);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LeafletMapState extends State<LeafletMap> {
|
class _LeafletMapState extends State<LeafletMap> {
|
||||||
|
|
@ -29,26 +32,94 @@ class _LeafletMapState extends State<LeafletMap> {
|
||||||
|
|
||||||
final BasicLocation location;
|
final BasicLocation location;
|
||||||
final String title;
|
final String title;
|
||||||
final int numFires;
|
int numFires;
|
||||||
final String kmAround;
|
int kmAround = 100;
|
||||||
|
List<dynamic> fires = [];
|
||||||
|
List<dynamic> falsePos = [];
|
||||||
|
List<dynamic> industries = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
updateFireStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateFireStats() {
|
||||||
|
var url = '${globals.firesApiUrl}fires-in-full/${globals
|
||||||
|
.firesApiKey}/${location.lat}/${location.lon}/$kmAround';
|
||||||
|
http.read(url).then((result) {
|
||||||
|
setState(() {
|
||||||
|
var resultDecoded = json.decode(result);
|
||||||
|
print(resultDecoded);
|
||||||
|
numFires = resultDecoded['real'];
|
||||||
|
fires = resultDecoded['fires'];
|
||||||
|
falsePos = resultDecoded['falsePos'];
|
||||||
|
industries = resultDecoded['industries'];
|
||||||
|
|
||||||
|
var firesCount = fires.length;
|
||||||
|
var industriesCount = industries.length;
|
||||||
|
var falsePosCount = falsePos.length;
|
||||||
|
|
||||||
|
print(
|
||||||
|
'fire: $firesCount falsePos: $falsePosCount industries: $industriesCount');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_LeafletMapState({@required this.title, @required this.location});
|
||||||
|
|
||||||
_LeafletMapState({@required this.title, @required this.location,
|
|
||||||
@required this.numFires, @required this.kmAround
|
|
||||||
});
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
MapOptions mapOptions = new MapOptions(
|
||||||
|
center: new LatLng(this.location.lat, this.location.lon),
|
||||||
|
plugins: [new ZoomMapPlugin()],
|
||||||
|
// this works ?
|
||||||
|
// interactive: false,
|
||||||
|
zoom: 13.0,
|
||||||
|
// THIS does not works as expected
|
||||||
|
// maxZoom: 6.0,
|
||||||
|
onPositionChanged: (positionCallback) {
|
||||||
|
// decouple
|
||||||
|
// print('${positionCallback.center}, ${positionCallback.zoom}');
|
||||||
|
});
|
||||||
|
var mapController = new MapController();
|
||||||
|
// mapController.fitBounds(bounds);
|
||||||
|
// mapController.center
|
||||||
|
var fmap = new FlutterMap(
|
||||||
|
options: mapOptions,
|
||||||
|
mapController: mapController,
|
||||||
|
layers: [
|
||||||
|
new TileLayerOptions(
|
||||||
|
maxZoom: 6.0,
|
||||||
|
subdomains: ['a', 'b', 'c'],
|
||||||
|
urlTemplate: 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
|
||||||
|
additionalOptions: {
|
||||||
|
// 'opacity': '0.1',
|
||||||
|
'attribution':
|
||||||
|
'© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new ZoomMapPluginOptions(),
|
||||||
|
new MarkerLayerOptions(
|
||||||
|
markers: buildMarkers(
|
||||||
|
this.location, this.fires, this.industries, this.falsePos),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
// drawer: new MainDrawer(context),
|
// drawer: new MainDrawer(context),
|
||||||
appBar: new AppBar(
|
appBar: new AppBar(
|
||||||
title: new Text(title),
|
title: new Text(title),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton.extended(
|
floatingActionButton: FloatingActionButton.extended(
|
||||||
onPressed: () {
|
onPressed: () {},
|
||||||
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.notifications_none, color: fires600),
|
icon: const Icon(Icons.notifications_none, color: fires600),
|
||||||
label: new Text('Subscribe', style: const TextStyle(color: fires600),),
|
label: new Text(
|
||||||
|
'Subscribe',
|
||||||
|
style: const TextStyle(color: fires600),
|
||||||
|
),
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
|
|
@ -56,54 +127,90 @@ class _LeafletMapState extends State<LeafletMap> {
|
||||||
fabLocation: FloatingActionButtonLocation.centerFloat,
|
fabLocation: FloatingActionButtonLocation.centerFloat,
|
||||||
showNotch: false,
|
showNotch: false,
|
||||||
color: fires100,
|
color: fires100,
|
||||||
|
// height: 170.0,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
actions: listWithoutNulls(<Widget>[
|
actions: listWithoutNulls(<Widget>[
|
||||||
numFires > 0 ?
|
numFires == null
|
||||||
new Text('${numFires.toString()} fires at $kmAround км around this area'):
|
? null
|
||||||
new Text('There is no fires at $kmAround км around this area'),
|
: numFires > 0
|
||||||
|
? new Text('${numFires.toString()} fires at ${kmAround
|
||||||
|
.toString()} км around this area')
|
||||||
|
: new Text(
|
||||||
|
'There is no fires at ${kmAround.toString()} км around this area'),
|
||||||
SizedBox(width: 10.0)
|
SizedBox(width: 10.0)
|
||||||
])),
|
])),
|
||||||
body: new FlutterMap(
|
body: LayoutBuilder(
|
||||||
options: new MapOptions(
|
builder: (context, constraints) =>
|
||||||
center: new LatLng(this.location.lat, this.location.lon),
|
Stack(fit: StackFit.expand, children: <Widget>[
|
||||||
zoom: 13.0,
|
// Material(color: Colors.yellowAccent),
|
||||||
// THIS does not works as expected
|
fmap,
|
||||||
// maxZoom: 6.0,
|
Positioned(
|
||||||
onPositionChanged: (positionCallback) {
|
top: constraints.maxHeight - 160,
|
||||||
print('$positionCallback');
|
right: 10.0,
|
||||||
}
|
left: 10.0,
|
||||||
),
|
child: new CenteredRow(
|
||||||
layers: [
|
// Fit sample:
|
||||||
new TileLayerOptions(
|
// https://github.com/apptreesoftware/flutter_map/blob/master/flutter_map_example/lib/pages/map_controller.dart
|
||||||
maxZoom: 6.0,
|
children: <Widget>[
|
||||||
urlTemplate: 'https://tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
|
new FireDistanceSlider(
|
||||||
additionalOptions: {
|
initialValue: kmAround,
|
||||||
// 'opacity': '0.1',
|
onSlide: (distance) {
|
||||||
'attribution': '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
setState(() {
|
||||||
},
|
kmAround = distance;
|
||||||
), /*
|
Debounce.seconds(1, updateFireStats);
|
||||||
new TileLayerOptions(
|
});
|
||||||
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
})
|
||||||
additionalOptions: {
|
],
|
||||||
// 'opacity': '0.1',
|
),
|
||||||
'attribution': '&copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
|
)
|
||||||
},
|
]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
), */
|
List<Marker> buildMarkers(BasicLocation yourLocation, List<dynamic> fires,
|
||||||
new MarkerLayerOptions(
|
List<dynamic> falsePos, List<dynamic> industries) {
|
||||||
markers: [
|
List<Marker> markers = [];
|
||||||
new Marker(
|
markers.add(buildMarker(yourLocation, MarkType.position));
|
||||||
width: 80.0,
|
falsePos.forEach((fire) {
|
||||||
height: 80.0,
|
var coords = fire['geo']['coordinates'];
|
||||||
point: new LatLng(this.location.lat, this.location.lon),
|
var loc = BasicLocation(lon: coords[0], lat: coords[1]);
|
||||||
builder: (ctx) =>
|
markers.add(buildMarker(loc, MarkType.falsePos));
|
||||||
new Container(
|
});
|
||||||
child: new Image.asset('images/fire-marker-l.png'),
|
industries.forEach((fire) {
|
||||||
),
|
// print(fire['geo']['coordinates']);
|
||||||
),
|
var coords = fire['geo']['coordinates'];
|
||||||
],
|
var loc = BasicLocation(lon: coords[0], lat: coords[1]);
|
||||||
),
|
markers.add(buildMarker(loc, MarkType.industry));
|
||||||
],
|
});
|
||||||
));
|
fires.forEach((fire) {
|
||||||
|
var loc = new BasicLocation(lat: fire['lat'], lon: fire['lon']);
|
||||||
|
markers.add(buildMarker(loc, MarkType.fire));
|
||||||
|
});
|
||||||
|
return markers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Marker buildMarker(location, MarkType type) {
|
||||||
|
return new Marker(
|
||||||
|
width: 80.0,
|
||||||
|
height: 80.0,
|
||||||
|
point: new LatLng(location.lat, location.lon),
|
||||||
|
builder: (ctx) => new Container(
|
||||||
|
child: buildImageMark(type),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildImageMark(MarkType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MarkType.position:
|
||||||
|
return new Icon(Icons.location_on, color: fires600, size: 50.0);
|
||||||
|
case MarkType.fire:
|
||||||
|
return new Image.asset('images/fire-marker-l.png');
|
||||||
|
case MarkType.industry:
|
||||||
|
return new Image.asset('images/industry-marker-reg.png');
|
||||||
|
case MarkType.falsePos:
|
||||||
|
default:
|
||||||
|
return new Image.asset('images/industry-marker.png');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ void main() {
|
||||||
globals.firesApiKey = secrets['firesApiKey'];
|
globals.firesApiKey = secrets['firesApiKey'];
|
||||||
globals.firesApiUrl = secrets['firesApiUrl'];
|
globals.firesApiUrl = secrets['firesApiUrl'];
|
||||||
globals.prefs.then((prefs) {
|
globals.prefs.then((prefs) {
|
||||||
loadYourLocations(prefs);
|
loadYourLocationsWithPrefs(prefs);
|
||||||
|
|
||||||
// Run baby run!
|
// Run baby run!
|
||||||
runApp(new FiresApp());
|
runApp(new FiresApp());
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ Widget mainDrawer(BuildContext context) {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
new ListTile(
|
/* new ListTile(
|
||||||
// https://docs.flutter.io/flutter/material/CircleAvatar-class.html
|
// https://docs.flutter.io/flutter/material/CircleAvatar-class.html
|
||||||
leading: new CircleAvatar(
|
leading: new CircleAvatar(
|
||||||
backgroundColor: Colors.brown.shade800,
|
backgroundColor: Colors.brown.shade800,
|
||||||
|
|
@ -49,14 +49,10 @@ Widget mainDrawer(BuildContext context) {
|
||||||
),
|
),
|
||||||
title: new Text('Your profile'),
|
title: new Text('Your profile'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Update the state of the app
|
|
||||||
// ...
|
|
||||||
// Then close the drawer
|
|
||||||
// Navigator.pop(context);
|
|
||||||
Navigator.pushNamed(context, IntroPage.routeName);
|
Navigator.pushNamed(context, IntroPage.routeName);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new Divider(),
|
new Divider(), */
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: const Icon(Icons.whatshot),
|
leading: const Icon(Icons.whatshot),
|
||||||
title: new Text('Active fires'),
|
title: new Text('Active fires'),
|
||||||
|
|
@ -65,14 +61,22 @@ Widget mainDrawer(BuildContext context) {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: const Icon(Icons.location_on),
|
leading: const Icon(Icons.notifications_active),
|
||||||
title: new Text('My subscribed areas'),
|
title: new Text('Notify a fire'),
|
||||||
|
onTap: () {
|
||||||
|
// Then close the drawer
|
||||||
|
Navigator.pushNamed(context, Sandbox.routeName);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
new Divider(),
|
||||||
|
new ListTile(
|
||||||
|
leading: const Icon(Icons.favorite),
|
||||||
|
title: new Text('Support this initiative'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// Then close the drawer
|
// Then close the drawer
|
||||||
Navigator.pushNamed(context, '/subscriptions');
|
Navigator.pushNamed(context, '/subscriptions');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new Divider(),
|
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: const Icon(Icons.bug_report),
|
leading: const Icon(Icons.bug_report),
|
||||||
title: new Text('Sandbox'),
|
title: new Text('Sandbox'),
|
||||||
|
|
@ -92,8 +96,16 @@ Widget mainDrawer(BuildContext context) {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new AboutListTile(
|
new AboutListTile(
|
||||||
// FIXME
|
icon: globals.appIcon,
|
||||||
)
|
applicationName: globals.appName,
|
||||||
|
applicationVersion: globals.appVersion,
|
||||||
|
applicationIcon: globals.appMediumIcon,
|
||||||
|
applicationLegalese: globals.appLicense,
|
||||||
|
aboutBoxChildren: <Widget> [
|
||||||
|
// new Text('What?')
|
||||||
|
]
|
||||||
|
// FIXME
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,25 @@ class Sandbox extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
//showDialog(context: context, child: builder(context));
|
//showDialog(context: context, child: builder(context));
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: new CenteredColumn(
|
body: LayoutBuilder(
|
||||||
children: <Widget>[
|
builder: (context, constraints) =>
|
||||||
new FireDistanceSlider()
|
Stack(fit: StackFit.expand, children: <Widget>[
|
||||||
|
// Material(color: Colors.yellowAccent),
|
||||||
],
|
Positioned(
|
||||||
|
top: 0.0,
|
||||||
|
child: Icon(Icons.star, size: 40.0),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: constraints.maxHeight - 80,
|
||||||
|
right: 10.0,
|
||||||
|
left: 10.0,
|
||||||
|
child: new CenteredRow(
|
||||||
|
children: <Widget>[new FireDistanceSlider()],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,68 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'colors.dart';
|
import 'colors.dart';
|
||||||
import 'package:comunes_flutter/comunes_flutter.dart';
|
import 'package:comunes_flutter/comunes_flutter.dart';
|
||||||
|
import 'package:padder/padding.dart';
|
||||||
|
|
||||||
|
typedef void SlideCallback(int distance);
|
||||||
|
|
||||||
class FireDistanceSlider extends StatefulWidget {
|
class FireDistanceSlider extends StatefulWidget {
|
||||||
|
final int initialValue;
|
||||||
|
final SlideCallback onSlide;
|
||||||
|
|
||||||
|
FireDistanceSlider({this.initialValue, this.onSlide});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_FireDistanceSliderState createState() => new _FireDistanceSliderState();
|
_FireDistanceSliderState createState() => new _FireDistanceSliderState(
|
||||||
|
initialValue: initialValue, onSlide: onSlide);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FireDistanceSliderState extends State<FireDistanceSlider> {
|
class _FireDistanceSliderState extends State<FireDistanceSlider> {
|
||||||
int _discreteValue = 10;
|
int _sliderValue;
|
||||||
|
final SlideCallback onSlide;
|
||||||
|
|
||||||
|
_FireDistanceSliderState({int initialValue = 10, this.onSlide}) {
|
||||||
|
this._sliderValue = initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeText(_sliderValue) =>
|
||||||
|
new Text('Subscribe to $_sliderValue км around this area');
|
||||||
|
|
||||||
|
warningText(_sliderValue) => _sliderValue >= 50
|
||||||
|
? new Text('Warning: this is a very large area',
|
||||||
|
style: new TextStyle(color: fires900))
|
||||||
|
: new Text('');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
var slider = new Slider(
|
||||||
|
value: _sliderValue + 0.0,
|
||||||
|
activeColor: fires900,
|
||||||
|
inactiveColor: Colors.black45,
|
||||||
|
|
||||||
|
min: 1.0,
|
||||||
|
max: 100.0,
|
||||||
|
divisions: 99,
|
||||||
|
label: '${_sliderValue.round()}',
|
||||||
|
onChanged: (double value) {
|
||||||
|
_sliderValue = value.round();
|
||||||
|
if (onSlide != null) {
|
||||||
|
onSlide(_sliderValue);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
return new Column(
|
return new Column(
|
||||||
children: <Widget>[
|
mainAxisSize: MainAxisSize.min,
|
||||||
new Column(
|
children: listWithoutNulls(<Widget>[
|
||||||
mainAxisSize: MainAxisSize.min,
|
slider,
|
||||||
children:
|
new Card(
|
||||||
listWithoutNulls(<Widget>[
|
color: const Color(0x60FFFFFF),
|
||||||
new Slider(
|
child: PaddingAll(10.0,
|
||||||
value: _discreteValue + 0.0,
|
child: new Column(children: <Widget>[
|
||||||
activeColor: fires900,
|
sizeText(_sliderValue),
|
||||||
min: 1.0,
|
warningText(_sliderValue)
|
||||||
max: 100.0,
|
])))
|
||||||
divisions: 99,
|
]),
|
||||||
label: '${_discreteValue.round()}',
|
|
||||||
onChanged: (double value) {
|
|
||||||
setState(() {
|
|
||||||
_discreteValue = value.round();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
new Text('Subscribe to $_discreteValue км around this area'),
|
|
||||||
_discreteValue >= 50 ?
|
|
||||||
new Text(
|
|
||||||
'Warning: this is a very large area',
|
|
||||||
style: new TextStyle(color: Colors.orange)
|
|
||||||
): new Text('')
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ dependencies:
|
||||||
http: "^0.11.3+16"
|
http: "^0.11.3+16"
|
||||||
simple_moment: "^0.0.3"
|
simple_moment: "^0.0.3"
|
||||||
geocoder: "^0.0.1"
|
geocoder: "^0.0.1"
|
||||||
|
just_debounce_it: "^1.0.4"
|
||||||
|
flutter_fab_dialer: "^0.0.5"
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
@ -52,6 +54,8 @@ flutter:
|
||||||
- images/fire-marker-m.png
|
- images/fire-marker-m.png
|
||||||
- images/fire-marker-s.png
|
- images/fire-marker-s.png
|
||||||
- images/fire-marker.png
|
- images/fire-marker.png
|
||||||
|
- images/industry-marker-reg.png
|
||||||
|
- images/industry-marker.png
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.io/assets-and-images/#resolution-aware.
|
# https://flutter.io/assets-and-images/#resolution-aware.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue