Hibok
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 
 

409 righe
12 KiB

  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:chat/generated/i18n.dart';
  4. import 'package:chat/map/auto_comp_iete_item.dart';
  5. import 'package:chat/map/location_provider.dart';
  6. import 'package:chat/map/location_result.dart';
  7. import 'package:chat/map/map.dart';
  8. import 'package:chat/map/nearby_place.dart';
  9. import 'package:chat/map/rich_suggestion.dart';
  10. import 'package:chat/map/search_input.dart';
  11. import 'package:chat/utils/CustomUI.dart';
  12. import 'package:chat/utils/uuid.dart';
  13. import 'package:flutter/material.dart';
  14. import 'package:google_maps_flutter/google_maps_flutter.dart';
  15. import 'package:http/http.dart' as http;
  16. import 'package:provider/provider.dart';
  17. class LocationPicker extends StatefulWidget {
  18. LocationPicker(
  19. this.apiKey, {
  20. Key key,
  21. this.initialCenter,
  22. this.requiredGPS = true,
  23. });
  24. final String apiKey;
  25. final LatLng initialCenter;
  26. final bool requiredGPS;
  27. @override
  28. LocationPickerState createState() => LocationPickerState();
  29. /// Returns a [LatLng] object of the location that was picked.
  30. ///
  31. /// The [apiKey] argument API key generated from Google Cloud Console.
  32. /// You can get an API key [here](https://cloud.google.com/maps-platform/)
  33. ///
  34. /// [initialCenter] The geographical location that the camera is pointing at.
  35. ///
  36. static Future<LocationResult> pickLocation(
  37. BuildContext context,
  38. String apiKey, {
  39. LatLng initialCenter = const LatLng(45.521563, -122.677433),
  40. bool requiredGPS = true,
  41. }) async {
  42. var results = await Navigator.of(context).push(
  43. MaterialPageRoute<dynamic>(
  44. builder: (BuildContext context) {
  45. return LocationPicker(
  46. apiKey,
  47. initialCenter: initialCenter,
  48. requiredGPS: requiredGPS,
  49. );
  50. },
  51. ),
  52. );
  53. if (results != null && results.containsKey('location')) {
  54. return results['location'];
  55. } else {
  56. return null;
  57. }
  58. }
  59. }
  60. class LocationPickerState extends State<LocationPicker> {
  61. /// Result returned after user completes selection
  62. LocationResult locationResult;
  63. /// Overlay to display autocomplete suggestions
  64. OverlayEntry overlayEntry;
  65. List<NearbyPlace> nearbyPlaces = List();
  66. /// Session token required for autocomplete API call
  67. String sessionToken = Uuid().generateV4();
  68. var mapKey = GlobalKey<MapPickerState>();
  69. var appBarKey = GlobalKey();
  70. var searchInputKey = GlobalKey<SearchInputState>();
  71. bool hasSearchTerm = false;
  72. /// Hides the autocomplete overlay
  73. void clearOverlay() {
  74. if (overlayEntry != null) {
  75. overlayEntry.remove();
  76. overlayEntry = null;
  77. }
  78. }
  79. /// Begins the search process by displaying a "wait" overlay then
  80. /// proceeds to fetch the autocomplete list. The bottom "dialog"
  81. /// is hidden so as to give more room and better experience for the
  82. /// autocomplete list overlay.
  83. void searchPlace(String place) {
  84. if (context == null) return;
  85. clearOverlay();
  86. setState(() => hasSearchTerm = place.length > 0);
  87. if (place.length < 1) return;
  88. final RenderBox renderBox = context.findRenderObject();
  89. Size size = renderBox.size;
  90. final RenderBox appBarBox = appBarKey.currentContext.findRenderObject();
  91. overlayEntry = OverlayEntry(
  92. builder: (context) => Positioned(
  93. top: appBarBox.size.height,
  94. width: size.width,
  95. child: Material(
  96. elevation: 1,
  97. child: Container(
  98. padding: EdgeInsets.symmetric(
  99. vertical: 16,
  100. horizontal: 24,
  101. ),
  102. color: Colors.white,
  103. child: Row(
  104. children: <Widget>[
  105. SizedBox(
  106. height: 24,
  107. width: 24,
  108. child: CircularProgressIndicator(
  109. strokeWidth: 3,
  110. ),
  111. ),
  112. SizedBox(
  113. width: 24,
  114. ),
  115. Expanded(
  116. child: Text(
  117. "${I18n.of(context).finding_place}...",
  118. textScaleFactor: 1.0,
  119. style: TextStyle(
  120. fontSize: 16,
  121. ),
  122. ),
  123. )
  124. ],
  125. ),
  126. ),
  127. ),
  128. ),
  129. );
  130. Overlay.of(context).insert(overlayEntry);
  131. autoCompleteSearch(place);
  132. }
  133. /// Fetches the place autocomplete list with the query [place].
  134. void autoCompleteSearch(String place) {
  135. place = place.replaceAll(" ", "+");
  136. var endpoint =
  137. "https://maps.googleapis.com/maps/api/place/autocomplete/json?" +
  138. "key=${widget.apiKey}&" +
  139. "input={$place}&sessiontoken=$sessionToken";
  140. if (locationResult != null) {
  141. endpoint += "&location=${locationResult.latLng.latitude}," +
  142. "${locationResult.latLng.longitude}";
  143. }
  144. http.get(endpoint).then((response) {
  145. if (response.statusCode == 200) {
  146. Map<String, dynamic> data = jsonDecode(response.body);
  147. List<dynamic> predictions = data['predictions'];
  148. List<RichSuggestion> suggestions = [];
  149. if (predictions.isEmpty) {
  150. AutoCompleteItem aci = AutoCompleteItem();
  151. aci.text = "No result found";
  152. aci.offset = 0;
  153. aci.length = 0;
  154. suggestions.add(RichSuggestion(aci, () {}));
  155. } else {
  156. for (dynamic t in predictions) {
  157. AutoCompleteItem aci = AutoCompleteItem();
  158. aci.id = t['place_id'];
  159. aci.text = t['description'];
  160. aci.offset = t['matched_substrings'][0]['offset'];
  161. aci.length = t['matched_substrings'][0]['length'];
  162. suggestions.add(RichSuggestion(aci, () {
  163. decodeAndSelectPlace(aci.id);
  164. }));
  165. }
  166. }
  167. displayAutoCompleteSuggestions(suggestions);
  168. }
  169. }).catchError((error) {
  170. print(error);
  171. });
  172. }
  173. /// To navigate to the selected place from the autocomplete list to the map,
  174. /// the lat,lng is required. This method fetches the lat,lng of the place and
  175. /// proceeds to moving the map to that location.
  176. void decodeAndSelectPlace(String placeId) {
  177. clearOverlay();
  178. String endpoint =
  179. "https://maps.googleapis.com/maps/api/place/details/json?key=${widget.apiKey}" +
  180. "&placeid=$placeId";
  181. http.get(endpoint).then((response) {
  182. if (response.statusCode == 200) {
  183. Map<String, dynamic> location =
  184. jsonDecode(response.body)['result']['geometry']['location'];
  185. LatLng latLng = LatLng(location['lat'], location['lng']);
  186. moveToLocation(latLng);
  187. }
  188. }).catchError((error) {
  189. print(error);
  190. });
  191. }
  192. /// Display autocomplete suggestions with the overlay.
  193. void displayAutoCompleteSuggestions(List<RichSuggestion> suggestions) {
  194. final RenderBox renderBox = context.findRenderObject();
  195. Size size = renderBox.size;
  196. final RenderBox appBarBox = appBarKey.currentContext.findRenderObject();
  197. clearOverlay();
  198. overlayEntry = OverlayEntry(
  199. builder: (context) => Positioned(
  200. width: size.width,
  201. top: appBarBox.size.height,
  202. child: Material(
  203. elevation: 1,
  204. color: Colors.white,
  205. child: Column(
  206. children: suggestions,
  207. ),
  208. ),
  209. ),
  210. );
  211. Overlay.of(context).insert(overlayEntry);
  212. }
  213. /// Utility function to get clean readable name of a location. First checks
  214. /// for a human-readable name from the nearby list. This helps in the cases
  215. /// that the user selects from the nearby list (and expects to see that as a
  216. /// result, instead of road name). If no name is found from the nearby list,
  217. /// then the road name returned is used instead.
  218. // String getLocationName() {
  219. // if (locationResult == null) {
  220. // return "Unnamed location";
  221. // }
  222. //
  223. // for (NearbyPlace np in nearbyPlaces) {
  224. // if (np.latLng == locationResult.latLng) {
  225. // locationResult.name = np.name;
  226. // return np.name;
  227. // }
  228. // }
  229. //
  230. // return "${locationResult.name}, ${locationResult.locality}";
  231. // }
  232. /// Fetches and updates the nearby places to the provided lat,lng
  233. void getNearbyPlaces(LatLng latLng) {
  234. http
  235. .get("https://maps.googleapis.com/maps/api/place/nearbysearch/json?" +
  236. "key=${widget.apiKey}&" +
  237. "location=${latLng.latitude},${latLng.longitude}&radius=150")
  238. .then((response) {
  239. if (response.statusCode == 200) {
  240. nearbyPlaces.clear();
  241. for (Map<String, dynamic> item
  242. in jsonDecode(response.body)['results']) {
  243. NearbyPlace nearbyPlace = NearbyPlace();
  244. nearbyPlace.name = item['name'];
  245. nearbyPlace.icon = item['icon'];
  246. double latitude = item['geometry']['location']['lat'];
  247. double longitude = item['geometry']['location']['lng'];
  248. LatLng _latLng = LatLng(latitude, longitude);
  249. nearbyPlace.latLng = _latLng;
  250. nearbyPlaces.add(nearbyPlace);
  251. }
  252. }
  253. // to update the nearby places
  254. setState(() {
  255. // this is to require the result to show
  256. hasSearchTerm = false;
  257. });
  258. }).catchError((error) {});
  259. }
  260. /// This method gets the human readable name of the location. Mostly appears
  261. /// to be the road name and the locality.
  262. Future reverseGeocodeLatLng(LatLng latLng) async {
  263. /*
  264. var placeMarks = await Geolocator()
  265. .placemarkFromCoordinates(latLng.latitude, latLng.longitude);
  266. if (placeMarks == null) {
  267. return;
  268. }
  269. Placemark place = placeMarks.first;
  270. print('~~~~~~~~~~~~~~~~~~~~~~~~');
  271. print(place.toString());
  272. setState(() {
  273. locationResult = LocationResult();
  274. locationResult.address = place.name;
  275. locationResult.latLng =
  276. LatLng(place.position.latitude, place.position.longitude);
  277. });
  278. */
  279. var response = await http.get(
  280. "https://maps.googleapis.com/maps/api/geocode/json?latlng=${latLng.latitude},${latLng.longitude}"
  281. "&key=${widget.apiKey}");
  282. if (response.statusCode == 200) {
  283. Map<String, dynamic> responseJson = jsonDecode(response.body);
  284. String road =
  285. responseJson['results'][0]['address_components'][0]['short_name'];
  286. setState(() {
  287. locationResult = LocationResult();
  288. locationResult.address = road;
  289. locationResult.latLng = latLng;
  290. });
  291. }
  292. }
  293. /// Moves the camera to the provided location and updates other UI features to
  294. /// match the location.
  295. void moveToLocation(LatLng latLng) {
  296. mapKey.currentState.mapController.future.then((controller) {
  297. controller.animateCamera(
  298. CameraUpdate.newCameraPosition(
  299. CameraPosition(
  300. target: latLng,
  301. zoom: 18.0,
  302. ),
  303. ),
  304. );
  305. });
  306. reverseGeocodeLatLng(latLng);
  307. getNearbyPlaces(latLng);
  308. }
  309. @override
  310. void dispose() {
  311. mapKey = null;
  312. appBarKey = null;
  313. clearOverlay();
  314. super.dispose();
  315. }
  316. @override
  317. Widget build(BuildContext context) {
  318. return MultiProvider(
  319. providers: [
  320. ChangeNotifierProvider(create: (_) => LocationProvider()),
  321. ],
  322. child: Builder(builder: (context) {
  323. return Scaffold(
  324. appBar: AppBar(
  325. backgroundColor: Colors.white,
  326. iconTheme: IconThemeData(color: Colors.black),
  327. key: appBarKey,
  328. title: SearchInput(
  329. (input) => searchPlace(input),
  330. key: searchInputKey,
  331. ),
  332. leading: CustomUI.buildCustomLeading(context),
  333. ),
  334. body: MapPicker(
  335. initialCenter: widget.initialCenter,
  336. key: mapKey,
  337. apiKey: widget.apiKey,
  338. requiredGPS: widget.requiredGPS,
  339. ),
  340. );
  341. }),
  342. );
  343. }
  344. }