diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/example/lib/main.dart b/example/lib/main.dart index 6cbe8752..8ef7cefd 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -150,6 +150,47 @@ class _HomePageState extends State { ] ], ), + ElevatedButton( + child: const Text("Load Place Picker"), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) { + return PlacePicker.searchBuilder( + searchBuilder: ((c, d) => Row( + children: [ + const SizedBox( + width: 10, + ), + const Icon(Icons.pin_drop_outlined), + const SizedBox(width: 10), + SearchbarTextField( + controller: d, + contentPadding: + const EdgeInsets.only(bottom: 0), + focus: FocusNode(), + hintText: "Search here", + ), + const Icon(Icons.mic), + const SizedBox(width: 10) + ], + )), + apiKey: Platform.isAndroid + ? APIKeys.androidApiKey + : APIKeys.iosApiKey, + onPlacePicked: (result) { + print(result.adrAddress); + Navigator.of(context).pop(); + }, + initialPosition: HomePage.kInitialPosition, + useCurrentLocation: true, + resizeToAvoidBottomInset: + false, // only works in page mode, less flickery, remove if wrong offsets + ); + }), + ); + }), + !_showPlacePickerInContainer ? ElevatedButton( child: Text("Load Place Picker"), diff --git a/example/pubspec.lock b/example/pubspec.lock index 7dc44a07..56f897e8 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" crypto: dependency: transitive description: @@ -291,10 +291,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nested: dependency: transitive description: @@ -376,18 +376,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -416,10 +416,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" tuple: dependency: transitive description: @@ -456,10 +456,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" win32: dependency: transitive description: @@ -469,5 +469,5 @@ packages: source: hosted version: "5.0.5" sdks: - dart: ">=3.1.0 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/lib/google_maps_place_picker.dart b/lib/google_maps_place_picker.dart index f9466bf9..15cd768d 100644 --- a/lib/google_maps_place_picker.dart +++ b/lib/google_maps_place_picker.dart @@ -5,3 +5,4 @@ export 'src/components/floating_card.dart'; export 'src/components/rounded_frame.dart'; export 'src/models/circle_area.dart'; export 'src/place_picker.dart'; +export 'src/components/searchbar_text_field.dart'; diff --git a/lib/src/autocomplete_search.dart b/lib/src/autocomplete_search.dart index ca4a018b..10bc05e6 100644 --- a/lib/src/autocomplete_search.dart +++ b/lib/src/autocomplete_search.dart @@ -1,3 +1,5 @@ +// ignore_for_file: must_be_immutable + import 'dart:async'; import 'package:flutter/material.dart'; @@ -9,31 +11,35 @@ import 'package:google_maps_place_picker_mb/src/controllers/autocomplete_search_ import 'package:flutter_google_maps_webservices/places.dart'; import 'package:provider/provider.dart'; +typedef Row AutoCompleteSearchBuilder( + BuildContext context, TextEditingController controller); + class AutoCompleteSearch extends StatefulWidget { - const AutoCompleteSearch( - {Key? key, - required this.sessionToken, - required this.onPicked, - required this.appBarKey, - this.hintText = "Search here", - this.searchingText = "Searching...", - this.hidden = false, - this.height = 40, - this.contentPadding = EdgeInsets.zero, - this.debounceMilliseconds, - this.onSearchFailed, - required this.searchBarController, - this.autocompleteOffset, - this.autocompleteRadius, - this.autocompleteLanguage, - this.autocompleteComponents, - this.autocompleteTypes, - this.strictbounds, - this.region, - this.initialSearchString, - this.searchForInitialValue, - this.autocompleteOnTrailingWhitespace}) - : super(key: key); + AutoCompleteSearch({ + Key? key, + required this.sessionToken, + required this.onPicked, + required this.appBarKey, + this.hintText = "Search here", + this.searchingText = "Searching...", + this.hidden = false, + this.height = 40, + this.contentPadding = EdgeInsets.zero, + this.debounceMilliseconds, + this.onSearchFailed, + required this.searchBarController, + this.autocompleteOffset, + this.autocompleteRadius, + this.autocompleteLanguage, + this.autocompleteComponents, + this.autocompleteTypes, + this.strictbounds, + this.region, + this.initialSearchString, + this.searchForInitialValue, + this.autocompleteOnTrailingWhitespace, + this.builder, + }) : super(key: key); final String? sessionToken; final String? hintText; @@ -56,6 +62,7 @@ class AutoCompleteSearch extends StatefulWidget { final String? initialSearchString; final bool? searchForInitialValue; final bool? autocompleteOnTrailingWhitespace; + late AutoCompleteSearchBuilder? builder; @override AutoCompleteSearchState createState() => AutoCompleteSearchState(); @@ -109,35 +116,47 @@ class AutoCompleteSearchState extends State { : Colors.white, borderRadius: BorderRadius.circular(20), elevation: 4.0, - child: Row( - children: [ - SizedBox(width: 10), - Icon(Icons.search), - SizedBox(width: 10), - Expanded(child: _buildSearchTextField()), - _buildTextClearIcon(), - ], - ), + child: widget.builder == null + ? _buildSearchBoxContent() + : _buildSearchBoxContentWithBuilder(), ), ) : Container(); } + Widget _buildSearchBoxContentWithBuilder() { + var child = widget.builder!(context, controller); + + assert(child.children.any((element) => element is SearchbarTextField)); + + child.children.forEach((t) { + if (t is SearchbarTextField) { + assert(t.controller != null, + "The controller parameter must be passed if the searchBuilder is being used."); + } + }); + + return child; + } + + Widget _buildSearchBoxContent({Widget? prefix, Widget? suffix}) { + return Row( + children: [ + SizedBox(width: 10), + prefix != null ? prefix : Icon(Icons.search), + SizedBox(width: 10), + _buildSearchTextField(), + suffix != null ? suffix : _buildTextClearIcon(), + ], + ); + } + Widget _buildSearchTextField() { - return TextField( + return SearchbarTextField( controller: controller, - focusNode: focus, - decoration: InputDecoration( - hintText: widget.hintText, - border: InputBorder.none, - errorBorder: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - disabledBorder: InputBorder.none, - focusedErrorBorder: InputBorder.none, - isDense: true, - contentPadding: widget.contentPadding, - ), + contentPadding: widget.contentPadding, + focus: focus, + hintText: widget.hintText, ); } @@ -157,6 +176,8 @@ class AutoCompleteSearchState extends State { ), onTap: () { clearText(); + + //add a callbackaction here after clearing the text }, ), ); diff --git a/lib/src/components/searchbar_text_field.dart b/lib/src/components/searchbar_text_field.dart new file mode 100644 index 00000000..e5189dda --- /dev/null +++ b/lib/src/components/searchbar_text_field.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +class SearchbarTextField extends StatefulWidget { + final TextEditingController? controller; + + final FocusNode focus; + + final String? hintText; + + final EdgeInsetsGeometry contentPadding; + + const SearchbarTextField( + {super.key, + this.controller, + required this.focus, + this.hintText, + required this.contentPadding}); + + @override + State createState() => _SearchbarTextFieldState(); +} + +class _SearchbarTextFieldState extends State { + @override + Widget build(BuildContext context) { + return Expanded( + child: TextField( + controller: widget.controller, + focusNode: widget.focus, + decoration: InputDecoration( + hintText: widget.hintText, + border: InputBorder.none, + errorBorder: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + isDense: true, + contentPadding: widget.contentPadding, + ), + ), + ); + } +} diff --git a/lib/src/place_picker.dart b/lib/src/place_picker.dart index c3be9c9c..7150ffcb 100644 --- a/lib/src/place_picker.dart +++ b/lib/src/place_picker.dart @@ -75,6 +75,61 @@ class PlacePicker extends StatefulWidget { this.onCameraMove, this.onCameraIdle, this.onMapTypeChanged, + this.searchBuilder, + this.zoomGesturesEnabled = true, + this.zoomControlsEnabled = false, + }) : super(key: key); + + PlacePicker.searchBuilder({ + Key? key, + required this.apiKey, + required this.initialPosition, + required this.searchBuilder, + this.onPlacePicked, + this.useCurrentLocation, + this.desiredLocationAccuracy = LocationAccuracy.high, + this.onMapCreated, + this.hintText, + this.searchingText, + this.selectText, + this.outsideOfPickAreaText, + this.onAutoCompleteFailed, + this.onGeocodingSearchFailed, + this.proxyBaseUrl, + this.httpClient, + this.selectedPlaceWidgetBuilder, + this.pinBuilder, + this.introModalWidgetBuilder, + this.autoCompleteDebounceInMilliseconds = 500, + this.cameraMoveDebounceInMilliseconds = 750, + this.initialMapType = MapType.normal, + this.enableMapTypeButton = true, + this.enableMyLocationButton = true, + this.myLocationButtonCooldown = 10, + this.usePinPointingSearch = true, + this.usePlaceDetailSearch = false, + this.autocompleteOffset, + this.autocompleteRadius, + this.autocompleteLanguage, + this.autocompleteComponents, + this.autocompleteTypes, + this.strictbounds, + this.region, + this.pickArea, + this.selectInitialPosition = false, + this.resizeToAvoidBottomInset = true, + this.initialSearchString, + this.searchForInitialValue = false, + this.forceSearchOnZoomChanged = false, + this.automaticallyImplyAppBarLeading = true, + this.autocompleteOnTrailingWhitespace = false, + this.hidePlaceDetailsWhenDraggingPin = true, + this.ignoreLocationPermissionErrors = false, + this.onTapBack, + this.onCameraMoveStarted, + this.onCameraMove, + this.onCameraIdle, + this.onMapTypeChanged, this.zoomGesturesEnabled = true, this.zoomControlsEnabled = false, }) : super(key: key); @@ -232,6 +287,10 @@ class PlacePicker extends StatefulWidget { /// Allow user to make visible the zoom button final bool zoomControlsEnabled; + ///Allows users to build custom searchBox contents + ///The children must contain the custom widget [SearchbarTextField] and a valid [TextEditingController] + final AutoCompleteSearchBuilder? searchBuilder; + @override _PlacePickerState createState() => _PlacePickerState(); } @@ -277,10 +336,10 @@ class _PlacePickerState extends State { @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () { + return PopScope( + canPop: true, + onPopInvoked: (t) { searchBarController.clearOverlay(); - return Future.value(true); }, child: FutureBuilder( future: _futureProvider, @@ -370,6 +429,7 @@ class _PlacePickerState extends State { : Container(), Expanded( child: AutoCompleteSearch( + builder: widget.searchBuilder, appBarKey: appBarKey, searchBarController: searchBarController, sessionToken: provider!.sessionToken, diff --git a/pubspec.lock b/pubspec.lock index 8e136014..b12f4bc6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" crypto: dependency: transitive description: @@ -276,10 +276,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" nested: dependency: transitive description: @@ -361,18 +361,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -401,10 +401,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" tuple: dependency: "direct main" description: @@ -441,10 +441,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" win32: dependency: transitive description: @@ -454,5 +454,5 @@ packages: source: hosted version: "5.0.5" sdks: - dart: ">=3.1.0 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0"