|
- import 'dart:async';
- import 'dart:convert';
-
- import 'package:chat/generated/i18n.dart';
- import 'package:chat/map/auto_comp_iete_item.dart';
- import 'package:chat/map/location_provider.dart';
- import 'package:chat/map/location_result.dart';
- import 'package:chat/map/map.dart';
- import 'package:chat/map/nearby_place.dart';
- import 'package:chat/map/rich_suggestion.dart';
- import 'package:chat/map/search_input.dart';
- import 'package:chat/utils/CustomUI.dart';
- import 'package:chat/utils/uuid.dart';
- import 'package:flutter/material.dart';
- import 'package:google_maps_flutter/google_maps_flutter.dart';
- import 'package:http/http.dart' as http;
- import 'package:provider/provider.dart';
-
- class LocationPicker extends StatefulWidget {
- LocationPicker(
- this.apiKey, {
- Key key,
- this.initialCenter,
- this.requiredGPS = true,
- });
-
- final String apiKey;
-
- final LatLng initialCenter;
-
- final bool requiredGPS;
-
- @override
- LocationPickerState createState() => LocationPickerState();
-
- /// Returns a [LatLng] object of the location that was picked.
- ///
- /// The [apiKey] argument API key generated from Google Cloud Console.
- /// You can get an API key [here](https://cloud.google.com/maps-platform/)
- ///
- /// [initialCenter] The geographical location that the camera is pointing at.
- ///
- static Future<LocationResult> pickLocation(
- BuildContext context,
- String apiKey, {
- LatLng initialCenter = const LatLng(45.521563, -122.677433),
- bool requiredGPS = true,
- }) async {
- var results = await Navigator.of(context).push(
- MaterialPageRoute<dynamic>(
- builder: (BuildContext context) {
- return LocationPicker(
- apiKey,
- initialCenter: initialCenter,
- requiredGPS: requiredGPS,
- );
- },
- ),
- );
-
- if (results != null && results.containsKey('location')) {
- return results['location'];
- } else {
- return null;
- }
- }
- }
-
- class LocationPickerState extends State<LocationPicker> {
- /// Result returned after user completes selection
- LocationResult locationResult;
-
- /// Overlay to display autocomplete suggestions
- OverlayEntry overlayEntry;
-
- List<NearbyPlace> nearbyPlaces = List();
-
- /// Session token required for autocomplete API call
- String sessionToken = Uuid().generateV4();
-
- var mapKey = GlobalKey<MapPickerState>();
-
- var appBarKey = GlobalKey();
-
- var searchInputKey = GlobalKey<SearchInputState>();
-
- bool hasSearchTerm = false;
-
- /// Hides the autocomplete overlay
- void clearOverlay() {
- if (overlayEntry != null) {
- overlayEntry.remove();
- overlayEntry = null;
- }
- }
-
- /// Begins the search process by displaying a "wait" overlay then
- /// proceeds to fetch the autocomplete list. The bottom "dialog"
- /// is hidden so as to give more room and better experience for the
- /// autocomplete list overlay.
- void searchPlace(String place) {
- if (context == null) return;
-
- clearOverlay();
-
- setState(() => hasSearchTerm = place.length > 0);
-
- if (place.length < 1) return;
-
- final RenderBox renderBox = context.findRenderObject();
- Size size = renderBox.size;
-
- final RenderBox appBarBox = appBarKey.currentContext.findRenderObject();
-
- overlayEntry = OverlayEntry(
- builder: (context) => Positioned(
- top: appBarBox.size.height,
- width: size.width,
- child: Material(
- elevation: 1,
- child: Container(
- padding: EdgeInsets.symmetric(
- vertical: 16,
- horizontal: 24,
- ),
- color: Colors.white,
- child: Row(
- children: <Widget>[
- SizedBox(
- height: 24,
- width: 24,
- child: CircularProgressIndicator(
- strokeWidth: 3,
- ),
- ),
- SizedBox(
- width: 24,
- ),
- Expanded(
- child: Text(
- "${I18n.of(context).finding_place}...",
- style: TextStyle(
- fontSize: 16,
- ),
- ),
- )
- ],
- ),
- ),
- ),
- ),
- );
-
- Overlay.of(context).insert(overlayEntry);
-
- autoCompleteSearch(place);
- }
-
- /// Fetches the place autocomplete list with the query [place].
- void autoCompleteSearch(String place) {
- place = place.replaceAll(" ", "+");
- var endpoint =
- "https://maps.googleapis.com/maps/api/place/autocomplete/json?" +
- "key=${widget.apiKey}&" +
- "input={$place}&sessiontoken=$sessionToken";
-
- if (locationResult != null) {
- endpoint += "&location=${locationResult.latLng.latitude}," +
- "${locationResult.latLng.longitude}";
- }
- http.get(endpoint).then((response) {
- if (response.statusCode == 200) {
- Map<String, dynamic> data = jsonDecode(response.body);
- List<dynamic> predictions = data['predictions'];
-
- List<RichSuggestion> suggestions = [];
-
- if (predictions.isEmpty) {
- AutoCompleteItem aci = AutoCompleteItem();
- aci.text = "No result found";
- aci.offset = 0;
- aci.length = 0;
-
- suggestions.add(RichSuggestion(aci, () {}));
- } else {
- for (dynamic t in predictions) {
- AutoCompleteItem aci = AutoCompleteItem();
-
- aci.id = t['place_id'];
- aci.text = t['description'];
- aci.offset = t['matched_substrings'][0]['offset'];
- aci.length = t['matched_substrings'][0]['length'];
-
- suggestions.add(RichSuggestion(aci, () {
- decodeAndSelectPlace(aci.id);
- }));
- }
- }
-
- displayAutoCompleteSuggestions(suggestions);
- }
- }).catchError((error) {
- print(error);
- });
- }
-
- /// To navigate to the selected place from the autocomplete list to the map,
- /// the lat,lng is required. This method fetches the lat,lng of the place and
- /// proceeds to moving the map to that location.
- void decodeAndSelectPlace(String placeId) {
- clearOverlay();
-
- String endpoint =
- "https://maps.googleapis.com/maps/api/place/details/json?key=${widget.apiKey}" +
- "&placeid=$placeId";
-
- http.get(endpoint).then((response) {
- if (response.statusCode == 200) {
- Map<String, dynamic> location =
- jsonDecode(response.body)['result']['geometry']['location'];
-
- LatLng latLng = LatLng(location['lat'], location['lng']);
-
- moveToLocation(latLng);
- }
- }).catchError((error) {
- print(error);
- });
- }
-
- /// Display autocomplete suggestions with the overlay.
- void displayAutoCompleteSuggestions(List<RichSuggestion> suggestions) {
- final RenderBox renderBox = context.findRenderObject();
- Size size = renderBox.size;
-
- final RenderBox appBarBox = appBarKey.currentContext.findRenderObject();
-
- clearOverlay();
-
- overlayEntry = OverlayEntry(
- builder: (context) => Positioned(
- width: size.width,
- top: appBarBox.size.height,
- child: Material(
- elevation: 1,
- color: Colors.white,
- child: Column(
- children: suggestions,
- ),
- ),
- ),
- );
-
- Overlay.of(context).insert(overlayEntry);
- }
-
- /// Utility function to get clean readable name of a location. First checks
- /// for a human-readable name from the nearby list. This helps in the cases
- /// that the user selects from the nearby list (and expects to see that as a
- /// result, instead of road name). If no name is found from the nearby list,
- /// then the road name returned is used instead.
- // String getLocationName() {
- // if (locationResult == null) {
- // return "Unnamed location";
- // }
- //
- // for (NearbyPlace np in nearbyPlaces) {
- // if (np.latLng == locationResult.latLng) {
- // locationResult.name = np.name;
- // return np.name;
- // }
- // }
- //
- // return "${locationResult.name}, ${locationResult.locality}";
- // }
-
- /// Fetches and updates the nearby places to the provided lat,lng
- void getNearbyPlaces(LatLng latLng) {
- http
- .get("https://maps.googleapis.com/maps/api/place/nearbysearch/json?" +
- "key=${widget.apiKey}&" +
- "location=${latLng.latitude},${latLng.longitude}&radius=150")
- .then((response) {
- if (response.statusCode == 200) {
- nearbyPlaces.clear();
- for (Map<String, dynamic> item
- in jsonDecode(response.body)['results']) {
- NearbyPlace nearbyPlace = NearbyPlace();
-
- nearbyPlace.name = item['name'];
- nearbyPlace.icon = item['icon'];
- double latitude = item['geometry']['location']['lat'];
- double longitude = item['geometry']['location']['lng'];
-
- LatLng _latLng = LatLng(latitude, longitude);
-
- nearbyPlace.latLng = _latLng;
-
- nearbyPlaces.add(nearbyPlace);
- }
- }
-
- // to update the nearby places
- setState(() {
- // this is to require the result to show
- hasSearchTerm = false;
- });
- }).catchError((error) {});
- }
-
- /// This method gets the human readable name of the location. Mostly appears
- /// to be the road name and the locality.
- Future reverseGeocodeLatLng(LatLng latLng) async {
- /*
- var placeMarks = await Geolocator()
- .placemarkFromCoordinates(latLng.latitude, latLng.longitude);
-
- if (placeMarks == null) {
- return;
- }
- Placemark place = placeMarks.first;
-
- print('~~~~~~~~~~~~~~~~~~~~~~~~');
- print(place.toString());
- setState(() {
- locationResult = LocationResult();
- locationResult.address = place.name;
- locationResult.latLng =
- LatLng(place.position.latitude, place.position.longitude);
- });
- */
-
- var response = await http.get(
- "https://maps.googleapis.com/maps/api/geocode/json?latlng=${latLng.latitude},${latLng.longitude}"
- "&key=${widget.apiKey}");
-
- if (response.statusCode == 200) {
- Map<String, dynamic> responseJson = jsonDecode(response.body);
-
- String road =
- responseJson['results'][0]['address_components'][0]['short_name'];
-
- setState(() {
- locationResult = LocationResult();
- locationResult.address = road;
- locationResult.latLng = latLng;
- });
- }
- }
-
- /// Moves the camera to the provided location and updates other UI features to
- /// match the location.
- void moveToLocation(LatLng latLng) {
- mapKey.currentState.mapController.future.then((controller) {
- controller.animateCamera(
- CameraUpdate.newCameraPosition(
- CameraPosition(
- target: latLng,
- zoom: 18.0,
- ),
- ),
- );
- });
-
- reverseGeocodeLatLng(latLng);
-
- getNearbyPlaces(latLng);
- }
-
- @override
- void dispose() {
- mapKey = null;
- appBarKey = null;
-
- clearOverlay();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return MultiProvider(
- providers: [
- ChangeNotifierProvider(create: (_) => LocationProvider()),
- ],
- child: Builder(builder: (context) {
- return Scaffold(
- appBar: AppBar(
- backgroundColor: Colors.white,
- iconTheme: IconThemeData(color: Colors.black),
- key: appBarKey,
- title: SearchInput(
- (input) => searchPlace(input),
- key: searchInputKey,
- ),
- leading: CustomUI.buildCustomLeading(context),
- ),
- body: MapPicker(
- initialCenter: widget.initialCenter,
- key: mapKey,
- apiKey: widget.apiKey,
- requiredGPS: widget.requiredGPS,
- ),
- );
- }),
- );
- }
- }
|