Added fire locations
This commit is contained in:
parent
c4438365ab
commit
8e4e469eb8
15 changed files with 607 additions and 49 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -19,3 +19,6 @@ pubspec.lock
|
||||||
# Directory created by dartdoc
|
# Directory created by dartdoc
|
||||||
# If you don't generate documentation locally you can remove this line.
|
# If you don't generate documentation locally you can remove this line.
|
||||||
doc/api/
|
doc/api/
|
||||||
|
|
||||||
|
assets/private-settings.json
|
||||||
|
android/app/src/main/AndroidManifest.xml
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.comunes.firesflutter">
|
package="org.comunes.firesflutter">
|
||||||
|
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
flutter needs it to communicate with the running application
|
flutter needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
|
|
||||||
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
|
||||||
calls FlutterMain.startInitialization(this); in its onCreate method.
|
calls FlutterMain.startInitialization(this); in its onCreate method.
|
||||||
|
|
@ -13,27 +15,31 @@
|
||||||
additional functionality it is fine to subclass or reimplement
|
additional functionality it is fine to subclass or reimplement
|
||||||
FlutterApplication and put your custom class here. -->
|
FlutterApplication and put your custom class here. -->
|
||||||
<application
|
<application
|
||||||
android:name="io.flutter.app.FlutterApplication"
|
android:name="io.flutter.app.FlutterApplication"
|
||||||
android:label="All Against The Fire!"
|
android:label="All Against The Fire!"
|
||||||
android:icon="@mipmap/launch_image">
|
android:icon="@mipmap/launch_image">
|
||||||
|
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="PUT_YOUR_API_HERE"/>
|
||||||
|
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<!-- This keeps the window background of the activity showing
|
<!-- This keeps the window background of the activity showing
|
||||||
until Flutter renders its first frame. It can be removed if
|
until Flutter renders its first frame. It can be removed if
|
||||||
there is no splash screen (such as the default splash screen
|
there is no splash screen (such as the default splash screen
|
||||||
defined in @style/LaunchTheme). -->
|
defined in @style/LaunchTheme). -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
|
||||||
android:value="true" />
|
android:value="true"/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name="com.apptreesoftware.mapview.MapActivity"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -6,6 +6,7 @@ buildscript {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||||
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.2-4'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,5 +41,9 @@
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>Your location will be used to center our map and display fires near you</string>
|
||||||
|
<key>NSLocationAlwaysUsageDescription</key>
|
||||||
|
<string>Your location will be used notify fires near you</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,244 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:location/location.dart';
|
||||||
import 'mainDrawer.dart';
|
import 'mainDrawer.dart';
|
||||||
|
import 'genericMap.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:comunes_flutter/comunes_flutter.dart';
|
||||||
|
import 'colors.dart';
|
||||||
|
import 'placesAutocompleteUtils.dart';
|
||||||
|
import 'basicLocation.dart';
|
||||||
|
import 'package:collection/collection.dart' show lowerBound;
|
||||||
|
|
||||||
class ActiveFiresMap extends StatelessWidget {
|
class _ActiveFiresPageState extends State<ActiveFiresPage> {
|
||||||
static const String routeName = '/fires';
|
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
// https://docs.flutter.io/flutter/dart-core/List-class.html
|
||||||
|
final List<BasicLocation> _saved = [];
|
||||||
|
int length;
|
||||||
|
|
||||||
|
_ActiveFiresPageState();
|
||||||
|
|
||||||
|
Widget _buildRow(BasicLocation loc) {
|
||||||
|
String desc = loc.description != null ? loc.description:
|
||||||
|
'Position: ${loc.lat}, ${loc.lon}';
|
||||||
|
return new ListTile(
|
||||||
|
dense: false,
|
||||||
|
leading: const Icon(Icons.location_on),
|
||||||
|
// trailing: const Icon(Icons.delete),
|
||||||
|
title: new Text(desc),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(context,
|
||||||
|
new MaterialPageRoute(builder: (context) => new GenericMap(title: desc, latitude: loc.lat, longitude: loc.lon)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSavedLocations() {
|
||||||
|
return new ListView.builder(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
itemCount: _saved.length,
|
||||||
|
itemBuilder: (BuildContext _context, int i) {
|
||||||
|
// Add a one-pixel-high divider widget before each row
|
||||||
|
// in the ListView.
|
||||||
|
/* if (i.isOdd) {
|
||||||
|
return new Divider();
|
||||||
|
} */
|
||||||
|
|
||||||
|
// The syntax "i ~/ 2" divides i by 2 and returns an
|
||||||
|
// integer result.
|
||||||
|
// For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
|
||||||
|
// This calculates the actual number of saved items
|
||||||
|
// in the ListView, minus the divider widgets.
|
||||||
|
// final int index = i ~/ 2;
|
||||||
|
// print('$i $index');
|
||||||
|
// if (index >= _saved.length) return null;
|
||||||
|
return _buildItem(_saved.elementAt(i));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleUndo(BasicLocation item) {
|
||||||
|
print('Undo $item');
|
||||||
|
final int insertionIndex = lowerBound(_saved, item);
|
||||||
|
setState(() {
|
||||||
|
_saved.insert(insertionIndex, item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildItem(BasicLocation item) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
return new Dismissible(
|
||||||
|
key: new ObjectKey(item),
|
||||||
|
direction: DismissDirection.horizontal,
|
||||||
|
onDismissed: (DismissDirection direction) {
|
||||||
|
setState(() {
|
||||||
|
_saved.remove(item);
|
||||||
|
});
|
||||||
|
final String action = 'deleted';
|
||||||
|
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||||
|
content: new Text('You $action item'), // ${item.index}'),
|
||||||
|
action: new SnackBarAction(
|
||||||
|
label: 'UNDO',
|
||||||
|
onPressed: () {
|
||||||
|
handleUndo(item);
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
background: new Container(
|
||||||
|
color: theme.primaryColor,
|
||||||
|
child: const ListTile(
|
||||||
|
leading:
|
||||||
|
const Icon(Icons.delete, color: Colors.white, size: 36.0))),
|
||||||
|
secondaryBackground: new Container(
|
||||||
|
color: theme.primaryColor,
|
||||||
|
child: const ListTile(
|
||||||
|
trailing:
|
||||||
|
const Icon(Icons.delete, color: Colors.white, size: 36.0))),
|
||||||
|
child: new Container(
|
||||||
|
decoration: new BoxDecoration(
|
||||||
|
color: theme.canvasColor,
|
||||||
|
border: new Border(
|
||||||
|
bottom: new BorderSide(color: theme.dividerColor))),
|
||||||
|
child: _buildRow(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<BasicLocation> getUserLocation() async {
|
||||||
|
// FIXME do something with this
|
||||||
|
|
||||||
|
String error;
|
||||||
|
// Platform messages may fail, so we use a try/catch PlatformException.
|
||||||
|
try {
|
||||||
|
Location _location = new Location();
|
||||||
|
Map<String, double> location = await _location.getLocation;
|
||||||
|
error = null;
|
||||||
|
print('location $location');
|
||||||
|
|
||||||
|
// It seems that the lib fails with lat/lon values
|
||||||
|
return new BasicLocation(
|
||||||
|
lon: location['latitude'], lat: location['longitude']);
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
if (e.code == 'PERMISSION_DENIED') {
|
||||||
|
error = 'Permission denied';
|
||||||
|
} else if (e.code == 'PERMISSION_DENIED_NEVER_ASK') {
|
||||||
|
error =
|
||||||
|
'Permission denied - please ask the user to enable it from the app settings';
|
||||||
|
}
|
||||||
|
// FIXME
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
length = _saved.length;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
print('Building Active Fires, saved $length');
|
||||||
|
final title = 'Active fires locations';
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _scaffoldKey,
|
key: _scaffoldKey,
|
||||||
drawer: new MainDrawer(context),
|
drawer: new MainDrawer(context),
|
||||||
appBar: new AppBar(
|
appBar: new AppBar(
|
||||||
title: Text('Active fires'),
|
title: Text(title),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(Icons.menu),
|
icon: Icon(Icons.menu),
|
||||||
|
onPressed: () {
|
||||||
|
_scaffoldKey.currentState.openDrawer();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
/* actions: <Widget>[
|
||||||
|
new IconButton(
|
||||||
|
icon: Icon(Icons.location_searching), onPressed: () {}),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.more_vert),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_scaffoldKey.currentState.openDrawer();
|
print('More button');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
], */
|
||||||
new IconButton(
|
),
|
||||||
icon: Icon(Icons.location_searching), onPressed: () => {}),
|
body: length > 0
|
||||||
IconButton(
|
? _buildSavedLocations()
|
||||||
icon: Icon(Icons.more_vert),
|
: new Center(
|
||||||
onPressed: () {
|
child: new CenteredColumn(children: <Widget>[
|
||||||
print('More button');
|
new RoundedBtn(
|
||||||
},
|
icon: Icons.location_searching,
|
||||||
),
|
text: 'Add your position',
|
||||||
],
|
onPressed: onAddYourLocation,
|
||||||
),
|
backColor: fires600),
|
||||||
body: new Text('No active fires'));
|
const SizedBox(height: 26.0),
|
||||||
|
new RoundedBtn(
|
||||||
|
icon: Icons.edit_location,
|
||||||
|
text: 'Add some other place',
|
||||||
|
onPressed: () {
|
||||||
|
onAddOtherLocation(context);
|
||||||
|
},
|
||||||
|
backColor: fires600),
|
||||||
|
])),
|
||||||
|
floatingActionButton: length > 0
|
||||||
|
? Column(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
|
||||||
|
FloatingActionButton.extended(
|
||||||
|
onPressed: onAddYourLocation,
|
||||||
|
heroTag: 'yourposition',
|
||||||
|
label: const Text('Add your position'),
|
||||||
|
icon: const Icon(Icons.location_searching),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
|
child: FloatingActionButton.extended(
|
||||||
|
onPressed: () {
|
||||||
|
onAddOtherLocation(context);
|
||||||
|
},
|
||||||
|
heroTag: 'otherplace',
|
||||||
|
label: new Text('Add some other place'),
|
||||||
|
icon: const Icon(Icons.edit_location),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAddYourLocation() {
|
||||||
|
Future<BasicLocation> location = getUserLocation();
|
||||||
|
_saveLocation(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAddOtherLocation(BuildContext context) {
|
||||||
|
Future<BasicLocation> location = openPlacesDialog(context);
|
||||||
|
_saveLocation(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _saveLocation(Future<BasicLocation> location) {
|
||||||
|
location.then((newLocation) {
|
||||||
|
this.setState(() {
|
||||||
|
_saved.add(newLocation);
|
||||||
|
length = _saved.length;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyDrawer extends StatelessWidget {
|
||||||
|
static final MyDrawer _drawer = new MyDrawer._internal();
|
||||||
|
|
||||||
|
factory MyDrawer() {
|
||||||
|
return _drawer;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyDrawer._internal();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Text("Drawer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActiveFiresPage extends StatefulWidget {
|
||||||
|
static const String routeName = '/fires';
|
||||||
|
|
||||||
|
ActiveFiresPage();
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ActiveFiresPageState createState() => _ActiveFiresPageState();
|
||||||
|
}
|
||||||
|
|
|
||||||
9
lib/basicLocation.dart
Normal file
9
lib/basicLocation.dart
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BasicLocation {
|
||||||
|
final double lat;
|
||||||
|
final double lon;
|
||||||
|
final String description;
|
||||||
|
|
||||||
|
BasicLocation({@required this.lat, @required this.lon, this.description});
|
||||||
|
}
|
||||||
|
|
@ -5,16 +5,26 @@ import 'theme.dart';
|
||||||
import 'introPage.dart';
|
import 'introPage.dart';
|
||||||
import 'sandbox.dart';
|
import 'sandbox.dart';
|
||||||
import 'activeFires.dart';
|
import 'activeFires.dart';
|
||||||
|
import 'placesAutocompleteWidget.dart';
|
||||||
|
import 'globals.dart' as globals;
|
||||||
|
|
||||||
class FiresApp extends StatelessWidget {
|
class FiresApp extends StatelessWidget {
|
||||||
final Map routes = <String, WidgetBuilder>{
|
|
||||||
Sandbox.routeName: (BuildContext context) => new Sandbox(),
|
|
||||||
ActiveFiresMap.routeName: (BuildContext context) => new ActiveFiresMap(),
|
Map routes = <String, WidgetBuilder>{
|
||||||
IntroPage.routeName: (BuildContext context) => new IntroPage(),
|
PlacesAutocompleteWidget.routeName: (BuildContext context) =>
|
||||||
};
|
new PlacesAutocompleteWidget(),
|
||||||
|
Sandbox.routeName: (BuildContext context) => new Sandbox(),
|
||||||
|
ActiveFiresPage.routeName: (BuildContext context) =>
|
||||||
|
new ActiveFiresPage(),
|
||||||
|
IntroPage.routeName: (BuildContext context) => new IntroPage(),
|
||||||
|
};
|
||||||
final WidgetBuilder introWidget = (context) => new IntroPage();
|
final WidgetBuilder introWidget = (context) => new IntroPage();
|
||||||
final WidgetBuilder continueWidget = (context) => new HomePage();
|
final WidgetBuilder continueWidget = (context) => new HomePage();
|
||||||
|
|
||||||
|
// globals.getIt.registerSingleton
|
||||||
|
FiresApp();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new MaterialApp(
|
return new MaterialApp(
|
||||||
|
|
|
||||||
173
lib/genericMap.dart
Normal file
173
lib/genericMap.dart
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:map_view/map_view.dart';
|
||||||
|
import 'globals.dart' as globals;
|
||||||
|
|
||||||
|
class GenericMap extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final double latitude;
|
||||||
|
final double longitude;
|
||||||
|
|
||||||
|
GenericMap(
|
||||||
|
{@required this.title,
|
||||||
|
@required this.latitude,
|
||||||
|
@required this.longitude});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_GenericMapState createState() => new _GenericMapState(
|
||||||
|
title: this.title, latitude: this.latitude, longitude: this.longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GenericMapState extends State<GenericMap> {
|
||||||
|
MapView mapView = new MapView();
|
||||||
|
CameraPosition cameraPosition;
|
||||||
|
var compositeSubscription = new CompositeSubscription();
|
||||||
|
var staticMapProvider;
|
||||||
|
Uri staticMapUri;
|
||||||
|
final String title;
|
||||||
|
Location location;
|
||||||
|
|
||||||
|
_GenericMapState(
|
||||||
|
{@required this.title, @required latitude, @required longitude}) {
|
||||||
|
MapView.setApiKey(globals.gmapKey);
|
||||||
|
this.staticMapProvider = new StaticMapProvider(globals.gmapKey);
|
||||||
|
this.location = new Location(latitude, longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Marker> _markers = <Marker>[
|
||||||
|
new Marker("1", "Work", 45.523970, -122.663081, color: Colors.blue),
|
||||||
|
new Marker("2", "Nossa Familia Coffee", 45.528788, -122.684633),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
cameraPosition = new CameraPosition(location, 2.0);
|
||||||
|
staticMapUri = staticMapProvider.getStaticUri(location, 12,
|
||||||
|
width: 900, height: 400, mapType: StaticMapViewType.roadmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Scaffold(
|
||||||
|
appBar: new AppBar(
|
||||||
|
title: new Text(title),
|
||||||
|
),
|
||||||
|
body: new Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
new Container(
|
||||||
|
height: 250.0,
|
||||||
|
child: new Stack(
|
||||||
|
children: <Widget>[
|
||||||
|
new InkWell(
|
||||||
|
child: new Center(
|
||||||
|
child: new Image.network(staticMapUri.toString()),
|
||||||
|
),
|
||||||
|
onTap: showMap,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new Container(
|
||||||
|
padding: new EdgeInsets.only(top: 10.0),
|
||||||
|
child: new Text(
|
||||||
|
"Tap the map to interact",
|
||||||
|
style: new TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
showMap() {
|
||||||
|
mapView.show(
|
||||||
|
new MapOptions(
|
||||||
|
mapViewType: MapViewType.normal,
|
||||||
|
showUserLocation: true,
|
||||||
|
initialCameraPosition: new CameraPosition(new Location(45.5235258, -122.6732493), 14.0),
|
||||||
|
title: this.title),
|
||||||
|
toolbarActions: [new ToolbarAction("Close", 1)]);
|
||||||
|
|
||||||
|
var sub = mapView.onMapReady.listen((_) {
|
||||||
|
mapView.setMarkers(_markers);
|
||||||
|
mapView.addMarker(new Marker("3", "10 Barrel", 45.5259467, -122.687747,
|
||||||
|
color: Colors.purple));
|
||||||
|
mapView.zoomToFit(padding: 100);
|
||||||
|
});
|
||||||
|
compositeSubscription.add(sub);
|
||||||
|
|
||||||
|
sub = mapView.onLocationUpdated
|
||||||
|
.listen((location) => print("Location updated $location"));
|
||||||
|
compositeSubscription.add(sub);
|
||||||
|
|
||||||
|
sub = mapView.onTouchAnnotation
|
||||||
|
.listen((annotation) => print("annotation tapped"));
|
||||||
|
compositeSubscription.add(sub);
|
||||||
|
|
||||||
|
sub = mapView.onMapTapped
|
||||||
|
.listen((location) => print("Touched location $location"));
|
||||||
|
compositeSubscription.add(sub);
|
||||||
|
|
||||||
|
sub = mapView.onCameraChanged.listen((cameraPosition) =>
|
||||||
|
this.setState(() => this.cameraPosition = cameraPosition));
|
||||||
|
compositeSubscription.add(sub);
|
||||||
|
|
||||||
|
sub = mapView.onToolbarAction.listen((id) {
|
||||||
|
if (id == 1) {
|
||||||
|
_handleDismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
compositeSubscription.add(sub);
|
||||||
|
|
||||||
|
sub = mapView.onInfoWindowTapped.listen((marker) {
|
||||||
|
print("Info Window Tapped for ${marker.title}");
|
||||||
|
});
|
||||||
|
compositeSubscription.add(sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleDismiss() async {
|
||||||
|
double zoomLevel = await mapView.zoomLevel;
|
||||||
|
Location centerLocation = await mapView.centerLocation;
|
||||||
|
List<Marker> visibleAnnotations = await mapView.visibleAnnotations;
|
||||||
|
print("Zoom Level: $zoomLevel");
|
||||||
|
print("Center: $centerLocation");
|
||||||
|
print("Visible Annotation Count: ${visibleAnnotations.length}");
|
||||||
|
var uri = await staticMapProvider.getImageUriFromMap(mapView,
|
||||||
|
width: 900, height: 400);
|
||||||
|
setState(() => staticMapUri = uri);
|
||||||
|
mapView.dismiss();
|
||||||
|
compositeSubscription.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompositeSubscription {
|
||||||
|
Set<StreamSubscription> _subscriptions = new Set();
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
for (var n in this._subscriptions) {
|
||||||
|
n.cancel();
|
||||||
|
}
|
||||||
|
this._subscriptions = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(StreamSubscription subscription) {
|
||||||
|
this._subscriptions.add(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addAll(Iterable<StreamSubscription> subs) {
|
||||||
|
_subscriptions.addAll(subs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool remove(StreamSubscription subscription) {
|
||||||
|
return this._subscriptions.remove(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(StreamSubscription subscription) {
|
||||||
|
return this._subscriptions.contains(subscription);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<StreamSubscription> toList() {
|
||||||
|
return this._subscriptions.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
lib/globals.dart
Normal file
6
lib/globals.dart
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
library fires.globals;
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
|
String gmapKey;
|
||||||
|
|
||||||
|
final GetIt getIt = new GetIt();
|
||||||
|
|
@ -33,12 +33,12 @@ class HomePage extends StatelessWidget {
|
||||||
heightFactor: 0.9,
|
heightFactor: 0.9,
|
||||||
child: new CenteredColumn(
|
child: new CenteredColumn(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
customBtn(
|
new RoundedBtn.nav(
|
||||||
Icons.whatshot,
|
icon: Icons.whatshot,
|
||||||
"Active fires",
|
text: 'Active fires',
|
||||||
context,
|
context: context,
|
||||||
// const EdgeInsets.only(left: 60.0),
|
route: ActiveFiresPage.routeName,
|
||||||
ActiveFiresMap.routeName),
|
backColor: fires600),
|
||||||
],
|
],
|
||||||
)))
|
)))
|
||||||
])),
|
])),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,18 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'firesApp.dart';
|
import 'firesApp.dart';
|
||||||
|
import 'package:comunes_flutter/comunes_flutter.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'globals.dart' as globals;
|
||||||
|
|
||||||
void main() => runApp(new FiresApp());
|
Future<String> getGMapKey() async {
|
||||||
|
Secret secret = await SecretLoader(secretPath: 'assets/private-settings.json')
|
||||||
|
.load('gmap_key');
|
||||||
|
return secret.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
getGMapKey().then((String gmapKey) {
|
||||||
|
globals.gmapKey = gmapKey;
|
||||||
|
runApp(new FiresApp());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'colors.dart';
|
||||||
import 'sandbox.dart';
|
import 'sandbox.dart';
|
||||||
import 'introPage.dart';
|
import 'introPage.dart';
|
||||||
import 'activeFires.dart';
|
import 'activeFires.dart';
|
||||||
|
import 'placesAutocompleteWidget.dart';
|
||||||
|
|
||||||
class MainDrawer extends Drawer {
|
class MainDrawer extends Drawer {
|
||||||
MainDrawer(BuildContext context, {key})
|
MainDrawer(BuildContext context, {key})
|
||||||
|
|
@ -60,7 +61,7 @@ Widget mainDrawer(BuildContext context) {
|
||||||
leading: const Icon(Icons.whatshot),
|
leading: const Icon(Icons.whatshot),
|
||||||
title: new Text('Active fires'),
|
title: new Text('Active fires'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pushNamed(context, ActiveFiresMap.routeName);
|
Navigator.pushNamed(context, ActiveFiresPage.routeName);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new ListTile(
|
new ListTile(
|
||||||
|
|
@ -71,6 +72,14 @@ Widget mainDrawer(BuildContext context) {
|
||||||
Navigator.pushNamed(context, '/subscriptions');
|
Navigator.pushNamed(context, '/subscriptions');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
new ListTile(
|
||||||
|
leading: const Icon(Icons.location_on),
|
||||||
|
title: new Text('Places'),
|
||||||
|
onTap: () {
|
||||||
|
// Then close the drawer
|
||||||
|
Navigator.pushNamed(context, PlacesAutocompleteWidget.routeName);
|
||||||
|
},
|
||||||
|
),
|
||||||
new Divider(),
|
new Divider(),
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: const Icon(Icons.bug_report),
|
leading: const Icon(Icons.bug_report),
|
||||||
|
|
|
||||||
32
lib/placesAutocompleteUtils.dart
Normal file
32
lib/placesAutocompleteUtils.dart
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import 'package:flutter_google_places_autocomplete/flutter_google_places_autocomplete.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'basicLocation.dart';
|
||||||
|
import 'globals.dart' as globals;
|
||||||
|
|
||||||
|
Future<BasicLocation> openPlacesDialog(BuildContext context) async {
|
||||||
|
Mode _mode = Mode.overlay;
|
||||||
|
GoogleMapsPlaces _places = new GoogleMapsPlaces(globals.gmapKey);
|
||||||
|
Prediction p = await showGooglePlacesAutocomplete(
|
||||||
|
context: context,
|
||||||
|
hint: 'Type the name of a place, region, etc',
|
||||||
|
apiKey: globals.gmapKey,
|
||||||
|
onError: (res) {
|
||||||
|
/* homeScaffoldKey.currentState.showSnackBar(
|
||||||
|
new SnackBar(content: new Text(res.errorMessage))); */
|
||||||
|
print('Error $res');
|
||||||
|
},
|
||||||
|
mode: _mode,
|
||||||
|
// FIXME
|
||||||
|
language: "es",
|
||||||
|
components: [new Component(Component.country, "es")]);
|
||||||
|
if (p != null) {
|
||||||
|
// get detail (lat/lng)
|
||||||
|
PlacesDetailsResponse detail = await _places.getDetailsByPlaceId(p.placeId);
|
||||||
|
final lat = detail.result.geometry.location.lat;
|
||||||
|
final lng = detail.result.geometry.location.lng;
|
||||||
|
|
||||||
|
return new BasicLocation(lat: lat, lon: lng, description: p.description);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
75
lib/placesAutocompleteWidget.dart
Normal file
75
lib/placesAutocompleteWidget.dart
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import 'package:flutter_google_places_autocomplete/flutter_google_places_autocomplete.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
import 'globals.dart' as globals;
|
||||||
|
|
||||||
|
class PlacesAutocompleteWidget extends StatefulWidget {
|
||||||
|
static const String routeName = '/place';
|
||||||
|
|
||||||
|
PlacesAutocompleteWidget();
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PlacesAutocompleteWidgetState createState() =>
|
||||||
|
_PlacesAutocompleteWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// to get places detail (lat/lng)
|
||||||
|
|
||||||
|
final homeScaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
final searchScaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
GoogleMapsPlaces _places;
|
||||||
|
|
||||||
|
class _PlacesAutocompleteWidgetState extends State<PlacesAutocompleteWidget> {
|
||||||
|
Mode _mode = Mode.overlay;
|
||||||
|
|
||||||
|
_PlacesAutocompleteWidgetState() {
|
||||||
|
_places = new GoogleMapsPlaces(globals.gmapKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new Scaffold(
|
||||||
|
key: homeScaffoldKey,
|
||||||
|
appBar: new AppBar(
|
||||||
|
title: new Text("My App"),
|
||||||
|
),
|
||||||
|
body:
|
||||||
|
|
||||||
|
new RaisedButton.icon(
|
||||||
|
icon: const Icon(Icons.location_searching),
|
||||||
|
label: new Text('Write a location'),
|
||||||
|
onPressed: () async {
|
||||||
|
// show input autocomplete with selected mode
|
||||||
|
// then get the Prediction selected
|
||||||
|
Prediction p = await showGooglePlacesAutocomplete(
|
||||||
|
context: context,
|
||||||
|
hint: 'Type the name of place, region, etc',
|
||||||
|
apiKey: globals.gmapKey,
|
||||||
|
onError: (res) {
|
||||||
|
homeScaffoldKey.currentState.showSnackBar(
|
||||||
|
new SnackBar(content: new Text(res.errorMessage)));
|
||||||
|
},
|
||||||
|
mode: _mode,
|
||||||
|
// FIXME
|
||||||
|
language: "es",
|
||||||
|
components: [new Component(Component.country, "es")]);
|
||||||
|
displayPrediction(p, homeScaffoldKey.currentState);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Null> displayPrediction(Prediction p, ScaffoldState scaffold) async {
|
||||||
|
if (p != null) {
|
||||||
|
// get detail (lat/lng)
|
||||||
|
PlacesDetailsResponse detail = await _places.getDetailsByPlaceId(p.placeId);
|
||||||
|
final lat = detail.result.geometry.location.lat;
|
||||||
|
final lng = detail.result.geometry.location.lng;
|
||||||
|
|
||||||
|
scaffold.showSnackBar(
|
||||||
|
new SnackBar(content: new Text("${p.description} - $lat/$lng")));
|
||||||
|
}
|
||||||
|
}
|
||||||
12
pubspec.yaml
12
pubspec.yaml
|
|
@ -4,17 +4,22 @@ description: All Against Fire
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: ^0.15.6
|
intl: "^0.15.6"
|
||||||
# https://pub.dartlang.org/packages/padder
|
# https://pub.dartlang.org/packages/padder
|
||||||
padder: ^1.0.1
|
padder: "^1.0.1"
|
||||||
# https://pub.dartlang.org/packages/flutter
|
# https://pub.dartlang.org/packages/flutter
|
||||||
fluttery: "^0.0.7"
|
fluttery: "^0.0.7"
|
||||||
shared_preferences: "^0.4.2"
|
shared_preferences: "^0.4.2"
|
||||||
comunes_flutter:
|
comunes_flutter:
|
||||||
path: /home/vjrj/dev/comunes_flutter
|
path: /home/vjrj/dev/comunes_flutter
|
||||||
|
version: "^0.0.4"
|
||||||
# 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"
|
||||||
|
map_view: "^0.0.12"
|
||||||
|
location: "^1.3.4"
|
||||||
|
flutter_google_places_autocomplete: "^0.0.4"
|
||||||
|
get_it: "^1.0.1+1"
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
@ -39,6 +44,7 @@ flutter:
|
||||||
|
|
||||||
assets:
|
assets:
|
||||||
- images/logo-200.png
|
- images/logo-200.png
|
||||||
|
- assets/private-settings.json
|
||||||
|
|
||||||
# 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