summaryrefslogtreecommitdiffstats
path: root/lib/mapbox.dart
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mapbox.dart')
-rw-r--r--lib/mapbox.dart293
1 files changed, 293 insertions, 0 deletions
diff --git a/lib/mapbox.dart b/lib/mapbox.dart
new file mode 100644
index 0000000..f7bf91e
--- /dev/null
+++ b/lib/mapbox.dart
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: Apache-2.0
+
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:mapbox_search/mapbox_search.dart';
+
+class MapBoxPlaceSearchWidget extends StatefulWidget {
+ MapBoxPlaceSearchWidget({
+ required this.apiKey,
+ required this.onSelected,
+ // this.onSearch,
+ this.fontSize,
+ this.searchHint = 'Search',
+ required this.context,
+ this.height,
+ this.popOnSelect = false,
+ this.location,
+ this.country,
+ });
+
+ /// True if there is different search screen and you want to pop screen on select
+ final bool popOnSelect;
+
+ ///To get the height of the page
+ final BuildContext context;
+
+ /// Height of whole search widget
+ final double? height;
+
+ /// API Key of the MapBox.
+ final String apiKey;
+
+ /// The callback that is called when one Place is selected by the user.
+ final void Function(MapBoxPlace place) onSelected;
+
+ /// The callback that is called when the user taps on the search icon.
+ // final void Function(MapBoxPlaces place) onSearch;
+
+ /// The point around which you wish to retrieve place information.
+ final Location? location;
+
+ ///Limits the search to the given country
+ ///
+ /// Check the full list of [supported countries](https://docs.mapbox.com/api/search/) for the MapBox API
+ final String? country;
+
+ ///Search Hint Localization
+ final String? searchHint;
+
+ ///Font Size
+ final String? fontSize;
+
+ @override
+ _MapBoxPlaceSearchWidgetState createState() =>
+ _MapBoxPlaceSearchWidgetState();
+}
+
+class _MapBoxPlaceSearchWidgetState extends State<MapBoxPlaceSearchWidget>
+ with SingleTickerProviderStateMixin {
+ TextEditingController _textEditingController = TextEditingController();
+ late AnimationController _animationController;
+
+
+ late Animation _containerHeight;
+
+
+ late Animation _listOpacity;
+
+ List<MapBoxPlace> _placePredictions = [];
+
+
+
+ late Timer _debounceTimer;
+
+
+
+
+ @override
+ void initState() {
+ _animationController =
+ AnimationController(vsync: this, duration: Duration(milliseconds: 500));
+
+ _containerHeight = Tween<double>(
+ begin: 73,
+ end: widget.height ??
+ MediaQuery.of(widget.context).size.height - 60 ??
+ 300)
+ .animate(
+ CurvedAnimation(
+ curve: Interval(0.0, 0.5, curve: Curves.easeInOut),
+ parent: _animationController,
+ ),
+ );
+ _listOpacity = Tween<double>(
+ begin: 0,
+ end: 1,
+ ).animate(
+ CurvedAnimation(
+ curve: Interval(0.5, 1.0, curve: Curves.easeInOut),
+ parent: _animationController,
+ ),
+ );
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _debounceTimer.cancel();
+ _animationController?.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) => Container(
+ padding: EdgeInsets.symmetric(horizontal: 5),
+ width: MediaQuery.of(context).size.width,
+ child: _searchContainer(
+ child: _searchInput(context),
+ ),
+ );
+
+ // Widgets
+ Widget _searchContainer({Widget? child}) {
+ return AnimatedBuilder(
+ animation: _animationController,
+ builder: (context, _) {
+ return Container(
+ height: _containerHeight.value,
+ decoration: _containerDecoration(),
+ padding: EdgeInsets.only(left: 0, right: 0, top: 15),
+ alignment: Alignment.center,
+ child: Column(
+ children: <Widget>[
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: child,
+ ),
+ SizedBox(height: 10),
+ Expanded(
+ child: Opacity(
+ opacity: _listOpacity.value,
+ child: ListView(
+ // addSemanticIndexes: true,
+ // itemExtent: 10,
+ children: <Widget>[
+ for (var places in _placePredictions)
+ _placeOption(places),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ });
+ }
+
+ Widget _searchInput(BuildContext context) {
+ return Center(
+ child: Row(
+ children: <Widget>[
+ Expanded(
+ child: TextField(
+ decoration: _inputStyle(),
+ controller: _textEditingController,
+ style: TextStyle(
+ fontSize: MediaQuery.of(context).size.width * 0.04,
+ ),
+ onChanged: (value) async {
+ // _debounceTimer?.cancel();
+ _debounceTimer = Timer(
+ Duration(milliseconds: 750),
+ () async {
+ await _autocompletePlace(value);
+ if (mounted) {
+ setState(() {});
+ }
+ },
+ );
+ },
+ ),
+ ),
+ Container(width: 15),
+ GestureDetector(
+ child: Icon(Icons.search, color: Colors.blue),
+ onTap: () {},
+ )
+ ],
+ ),
+ );
+ }
+
+ Widget _placeOption(MapBoxPlace prediction) {
+ String? place = prediction.text;
+ String? fullName = prediction.placeName;
+
+ return MaterialButton(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 3),
+ onPressed: () => _selectPlace(prediction),
+ child: ListTile(
+ title: Text(
+ place?.length != 0 ? place!.length < 45
+ ? "$place"
+ : "${place?.replaceRange(45, place.length, "")} ..." : "",
+ style: TextStyle(fontSize: MediaQuery.of(context).size.width * 0.04),
+ maxLines: 1,
+ ),
+ subtitle: Text(
+ fullName!,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(fontSize: MediaQuery.of(context).size.width * 0.03),
+ maxLines: 1,
+ ),
+ contentPadding: EdgeInsets.symmetric(
+ horizontal: 10,
+ vertical: 0,
+ ),
+ ),
+ );
+ }
+
+ // Styling
+ InputDecoration _inputStyle() {
+ return InputDecoration(
+ hintText: widget.searchHint,
+ border: InputBorder.none,
+ contentPadding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 0.0),
+ );
+ }
+
+ BoxDecoration _containerDecoration() {
+ return BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.all(Radius.circular(6.0)),
+ boxShadow: [
+ BoxShadow(color: Colors.black, blurRadius: 0, spreadRadius: 0)
+ ],
+ );
+ }
+
+ // Methods
+ Future _autocompletePlace(String input) async {
+ /// Will be called when the input changes. Making callbacks to the Places
+ /// Api and giving the user Place options
+ ///
+ if (input.length > 0) {
+ var placesSearch = PlacesSearch(
+ apiKey: widget.apiKey,
+ country: widget.country,
+ );
+
+ final predictions = await placesSearch.getPlaces(
+ input,
+ location: widget.location,
+ );
+
+ await _animationController.animateTo(0.5);
+
+
+
+ setState(() => _placePredictions = predictions!);
+
+ await _animationController.forward();
+ } else {
+
+ await _animationController.animateTo(0.5);
+ setState(() => _placePredictions = []);
+ await _animationController.reverse();
+ }
+ }
+
+ void _selectPlace(MapBoxPlace prediction) async {
+ /// Will be called when a user selects one of the Place options.
+ // Sets TextField value to be the location selected
+ _textEditingController.value = TextEditingValue(
+ text: prediction.placeName.toString(),
+ selection: TextSelection.collapsed(offset: prediction.placeName!.length),
+ );
+
+ // Makes animation
+ await _animationController.animateTo(0.5);
+ setState(() {
+ _placePredictions = [];
+
+ });
+ _animationController.reverse();
+
+ // Calls the `onSelected` callback
+ widget.onSelected(prediction);
+ if (widget.popOnSelect) Navigator.pop(context);
+ }
+} \ No newline at end of file