mirror of
https://github.com/flutter/samples.git
synced 2025-11-09 22:38:42 +00:00
Initial commit flutter_maps_firestore (#93)
This commit is contained in:
committed by
Andrew Brogdon
parent
f79f7d20b5
commit
599c865629
273
flutter_maps_firestore/lib/main.dart
Normal file
273
flutter_maps_firestore/lib/main.dart
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2019 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:google_maps_webservice/places.dart';
|
||||
import 'api_key.dart';
|
||||
|
||||
// Center of the Google Map
|
||||
const initialPosition = LatLng(37.7786, -122.4375);
|
||||
// Hue used by the Google Map Markers to match the theme
|
||||
const _pinkHue = 350.0;
|
||||
// Places API client used for Place Photos
|
||||
final _placesApiClient = GoogleMapsPlaces(apiKey: googleMapsApiKey);
|
||||
|
||||
void main() => runApp(App());
|
||||
|
||||
class App extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Ice Creams FTW',
|
||||
home: const HomePage(title: 'Ice Cream Stores in SF'),
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.pink,
|
||||
scaffoldBackgroundColor: Colors.pink[50],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({@required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _HomePageState();
|
||||
}
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
Stream<QuerySnapshot> _iceCreamStores;
|
||||
final Completer<GoogleMapController> _mapController = Completer();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_iceCreamStores = Firestore.instance
|
||||
.collection('ice_cream_stores')
|
||||
.orderBy('name')
|
||||
.snapshots();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: StreamBuilder<QuerySnapshot>(
|
||||
stream: _iceCreamStores,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Error: ${snapshot.error}'));
|
||||
}
|
||||
if (!snapshot.hasData) {
|
||||
return Center(child: const Text('Loading...'));
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
StoreMap(
|
||||
documents: snapshot.data.documents,
|
||||
initialPosition: initialPosition,
|
||||
mapController: _mapController,
|
||||
),
|
||||
StoreCarousel(
|
||||
mapController: _mapController,
|
||||
documents: snapshot.data.documents,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StoreCarousel extends StatelessWidget {
|
||||
const StoreCarousel({
|
||||
Key key,
|
||||
@required this.documents,
|
||||
@required this.mapController,
|
||||
}) : super(key: key);
|
||||
|
||||
final List<DocumentSnapshot> documents;
|
||||
final Completer<GoogleMapController> mapController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: SizedBox(
|
||||
height: 90,
|
||||
child: new StoreCarouselList(
|
||||
documents: documents,
|
||||
mapController: mapController,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StoreCarouselList extends StatelessWidget {
|
||||
const StoreCarouselList({
|
||||
Key key,
|
||||
@required this.documents,
|
||||
@required this.mapController,
|
||||
}) : super(key: key);
|
||||
|
||||
final List<DocumentSnapshot> documents;
|
||||
final Completer<GoogleMapController> mapController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: documents.length,
|
||||
itemBuilder: (context, index) {
|
||||
return SizedBox(
|
||||
width: 340,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: Card(
|
||||
child: Center(
|
||||
child: StoreListTile(
|
||||
document: documents[index],
|
||||
mapController: mapController,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StoreListTile extends StatefulWidget {
|
||||
const StoreListTile({
|
||||
Key key,
|
||||
@required this.document,
|
||||
@required this.mapController,
|
||||
}) : super(key: key);
|
||||
|
||||
final DocumentSnapshot document;
|
||||
final Completer<GoogleMapController> mapController;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return _StoreListTileState();
|
||||
}
|
||||
}
|
||||
|
||||
class _StoreListTileState extends State<StoreListTile> {
|
||||
String _placePhotoUrl = '';
|
||||
bool _disposed = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_retrievePlacesDetails();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _retrievePlacesDetails() async {
|
||||
final details =
|
||||
await _placesApiClient.getDetailsByPlaceId(widget.document['placeId']);
|
||||
if (!_disposed) {
|
||||
setState(() {
|
||||
_placePhotoUrl = _placesApiClient.buildPhotoUrl(
|
||||
photoReference: details.result.photos[0].photoReference,
|
||||
maxHeight: 300,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(widget.document['name']),
|
||||
subtitle: Text(widget.document['address']),
|
||||
leading: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: _placePhotoUrl.isNotEmpty
|
||||
? ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(2)),
|
||||
child: Image.network(_placePhotoUrl, fit: BoxFit.cover),
|
||||
)
|
||||
: Container(),
|
||||
),
|
||||
onTap: () async {
|
||||
final controller = await widget.mapController.future;
|
||||
await controller.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(
|
||||
target: LatLng(
|
||||
widget.document['location'].latitude,
|
||||
widget.document['location'].longitude,
|
||||
),
|
||||
zoom: 16,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StoreMap extends StatelessWidget {
|
||||
const StoreMap({
|
||||
Key key,
|
||||
@required this.documents,
|
||||
@required this.initialPosition,
|
||||
@required this.mapController,
|
||||
}) : super(key: key);
|
||||
|
||||
final List<DocumentSnapshot> documents;
|
||||
final LatLng initialPosition;
|
||||
final Completer<GoogleMapController> mapController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GoogleMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: initialPosition,
|
||||
zoom: 12,
|
||||
),
|
||||
markers: documents
|
||||
.map((document) => Marker(
|
||||
markerId: MarkerId(document['placeId']),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(_pinkHue),
|
||||
position: LatLng(
|
||||
document['location'].latitude,
|
||||
document['location'].longitude,
|
||||
),
|
||||
infoWindow: InfoWindow(
|
||||
title: document['name'],
|
||||
snippet: document['address'],
|
||||
),
|
||||
))
|
||||
.toSet(),
|
||||
onMapCreated: (mapController) {
|
||||
this.mapController.complete(mapController);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user