Skip to content

Instantly share code, notes, and snippets.

@nimi0112
Created March 31, 2019 04:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nimi0112/f029e62edc4bb0d1b96e5bcbcf9fbc4c to your computer and use it in GitHub Desktop.
Save nimi0112/f029e62edc4bb0d1b96e5bcbcf9fbc4c to your computer and use it in GitHub Desktop.
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:google_maps_webservice/places.dart';
import 'package:rxdart/rxdart.dart';
GoogleMapsPlaces _places =
GoogleMapsPlaces(apiKey: "API_KEY");
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// home: CustomSearchScaffold(),
home: new ExampleWidget(),
);
}
}
class ExampleWidget extends StatefulWidget {
ExampleWidget({Key key}) : super(key: key);
@override
_ExampleWidgetState createState() => new _ExampleWidgetState();
}
/// State for [ExampleWidget] widgets.
class _ExampleWidgetState extends State<ExampleWidget> {
final TextEditingController _controller = new TextEditingController();
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new TextField(
controller: _controller,
decoration: new InputDecoration(
hintText: 'Search places',
),
),
],
),
);
}
}
final homeScaffoldKey = GlobalKey<ScaffoldState>();
final searchScaffoldKey = GlobalKey<ScaffoldState>();
class CustomSearchScaffold extends PlacesAutocompleteWidget {
CustomSearchScaffold()
: super(
apiKey: "API_KEY",
sessionToken: Uuid().generateV4(),
language: "en",
components: [Component(Component.country, "in")],
);
@override
_CustomSearchScaffoldState createState() => _CustomSearchScaffoldState();
}
class _CustomSearchScaffoldState extends PlacesAutocompleteState {
@override
Widget build(BuildContext context) {
final appBar = AppBar(title: AppBarPlacesAutoCompleteTextField());
final body = PlacesAutocompleteResult(
onTap: (p) {
displayPrediction(p, searchScaffoldKey.currentState);
debugPrint("tapped");
},
logo: Row(
// children: [FlutterLogo()],
mainAxisAlignment: MainAxisAlignment.center,
),
);
return Scaffold(key: searchScaffoldKey, appBar: appBar, body: body);
}
@override
void onResponseError(PlacesAutocompleteResponse response) {
super.onResponseError(response);
searchScaffoldKey.currentState.showSnackBar(
SnackBar(content: Text(response.errorMessage)),
);
}
@override
void onResponse(PlacesAutocompleteResponse response) {
super.onResponse(response);
if (response != null && response.predictions.isNotEmpty) {
searchScaffoldKey.currentState.showSnackBar(
SnackBar(content: Text("Got answer")),
);
}
}
}
class PlacesAutocompleteWidget extends StatefulWidget {
final String apiKey;
final String hint;
final String language;
final String sessionToken;
final List<Component> components;
final ValueChanged<PlacesAutocompleteResponse> onError;
final String proxyBaseUrl;
PlacesAutocompleteWidget({
@required this.apiKey,
this.hint = "Search",
this.language,
this.sessionToken,
this.components,
this.onError,
Key key,
this.proxyBaseUrl,
}) : super(key: key);
@override
State<PlacesAutocompleteWidget> createState() {
return _PlacesAutocompleteOverlayState();
}
static PlacesAutocompleteState of(BuildContext context) =>
context.ancestorStateOfType(const TypeMatcher<PlacesAutocompleteState>());
}
abstract class PlacesAutocompleteState extends State<PlacesAutocompleteWidget> {
TextEditingController _queryTextController;
PlacesAutocompleteResponse _response;
GoogleMapsPlaces _places;
bool _searching;
final _queryBehavior = BehaviorSubject<String>(seedValue: '');
@override
void initState() {
super.initState();
_queryTextController = TextEditingController(text: "");
_places = GoogleMapsPlaces(
apiKey: widget.apiKey,
baseUrl: widget.proxyBaseUrl,
);
_searching = false;
_queryTextController.addListener(_onQueryChange);
_queryBehavior.stream
.debounce(const Duration(milliseconds: 300))
.listen(doSearch);
}
Future<Null> doSearch(String value) async {
if (mounted && value.isNotEmpty) {
setState(() {
_searching = true;
});
final res = await _places.autocomplete(
value,
language: widget.language,
sessionToken: widget.sessionToken,
components: widget.components,
);
if (res.errorMessage?.isNotEmpty == true ||
res.status == "REQUEST_DENIED") {
onResponseError(res);
} else {
onResponse(res);
}
} else {
onResponse(null);
}
}
void _onQueryChange() {
_queryBehavior.add(_queryTextController.text);
}
@override
void dispose() {
super.dispose();
_places.dispose();
_queryBehavior.close();
_queryTextController.removeListener(_onQueryChange);
}
@mustCallSuper
void onResponseError(PlacesAutocompleteResponse res) {
if (!mounted) return;
if (widget.onError != null) {
widget.onError(res);
}
setState(() {
_response = null;
_searching = false;
});
}
@mustCallSuper
void onResponse(PlacesAutocompleteResponse res) {
if (!mounted) return;
setState(() {
_response = res;
_searching = false;
});
}
}
class _PlacesAutocompleteOverlayState extends PlacesAutocompleteState {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final header = Column(children: <Widget>[
Material(
color: theme.dialogBackgroundColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(2.0), topRight: Radius.circular(2.0)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
IconButton(
color: theme.brightness == Brightness.light
? Colors.black45
: null,
icon: _iconBack,
onPressed: () {
Navigator.pop(context);
},
),
Expanded(
child: Padding(
child: _textField(context),
padding: const EdgeInsets.only(right: 8.0),
)),
],
)),
Divider(
//height: 1.0,
)
]);
var body;
/*todo searching loader
// if (_searching) {
// body = Stack(
// children: <Widget>[_Loader()],
// alignment: FractionalOffset.bottomCenter,
// );
// } else*/
if (_queryTextController.text.isEmpty ||
_response == null ||
_response.predictions.isEmpty) {
body = Material(
color: theme.dialogBackgroundColor,
// child: widget.logo ?? PoweredByGoogleImage(),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(2.0),
bottomRight: Radius.circular(2.0)),
);
} else {
body = SingleChildScrollView(
child: Material(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(2.0),
bottomRight: Radius.circular(2.0),
),
color: theme.dialogBackgroundColor,
child: ListBody(
children: _response.predictions
.map(
(p) => PredictionTile(
prediction: p,
onTap: Navigator.of(context).pop,
),
)
.toList(),
),
),
);
}
final container = Container(
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0),
child: Stack(children: <Widget>[
header,
Padding(padding: EdgeInsets.only(top: 48.0), child: body),
]));
if (Platform.isIOS) {
return Padding(padding: EdgeInsets.only(top: 8.0), child: container);
}
return container;
}
Icon get _iconBack =>
Platform.isIOS ? Icon(Icons.arrow_back_ios) : Icon(Icons.arrow_back);
Widget _textField(BuildContext context) => TextField(
controller: _queryTextController,
autofocus: true,
style: TextStyle(
color: Theme.of(context).brightness == Brightness.light
? Colors.black87
: null,
fontSize: 16.0),
decoration: InputDecoration(
hintText: widget.hint,
hintStyle: TextStyle(
color: Theme.of(context).brightness == Brightness.light
? Colors.black45
: null,
fontSize: 16.0,
),
border: InputBorder.none,
),
);
}
class PredictionTile extends StatelessWidget {
final Prediction prediction;
final ValueChanged<Prediction> onTap;
PredictionTile({@required this.prediction, this.onTap});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(prediction.description),
onTap: () {
if (onTap != null) {
onTap(prediction);
}
},
);
}
}
class Uuid {
final Random _random = Random();
String generateV4() {
// Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
final int special = 8 + _random.nextInt(4);
return '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
'${_bitsDigits(16, 4)}-'
'4${_bitsDigits(12, 3)}-'
'${_printDigits(special, 1)}${_bitsDigits(12, 3)}-'
'${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
}
String _bitsDigits(int bitCount, int digitCount) =>
_printDigits(_generateBits(bitCount), digitCount);
int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);
String _printDigits(int value, int count) =>
value.toRadixString(16).padLeft(count, '0');
}
class PlacesAutocompleteResult extends StatefulWidget {
final ValueChanged<Prediction> onTap;
final Widget logo;
PlacesAutocompleteResult({this.onTap, this.logo});
@override
_PlacesAutocompleteResult createState() => _PlacesAutocompleteResult();
}
class _PlacesAutocompleteResult extends State<PlacesAutocompleteResult> {
@override
Widget build(BuildContext context) {
final state = PlacesAutocompleteWidget.of(context);
assert(state != null);
if (state._queryTextController.text.isEmpty ||
state._response == null ||
state._response.predictions.isEmpty) {
final children = <Widget>[];
if (state._searching) {
// children.add(_Loader());
}
// children.add(widget.logo ?? PoweredByGoogleImage());
return Stack(children: children);
}
return PredictionsListView(
predictions: state._response.predictions,
onTap: widget.onTap,
);
}
}
class PredictionsListView extends StatelessWidget {
final List<Prediction> predictions;
final ValueChanged<Prediction> onTap;
PredictionsListView({@required this.predictions, this.onTap});
@override
Widget build(BuildContext context) {
return ListView(
children: predictions
.map((Prediction p) => PredictionTile(prediction: p, onTap: onTap))
.toList(),
);
}
}
Future<Null> displayPrediction(Prediction p, ScaffoldState scaffold) async {
if (p != null) {
// get detail (lat/lng)
PlacesDetailsResponse detail = await _places.getDetailsByPlaceId(p.placeId);
final lat = detail.result.geometry.location.lat;
final lng = detail.result.geometry.location.lng;
scaffold.showSnackBar(
SnackBar(content: Text("${p.description} - $lat/$lng")),
);
}
}
class _AppBarPlacesAutoCompleteTextFieldState
extends State<AppBarPlacesAutoCompleteTextField> {
@override
Widget build(BuildContext context) {
final state = PlacesAutocompleteWidget.of(context);
assert(state != null);
return Container(
alignment: Alignment.topLeft,
margin: EdgeInsets.only(top: 4.0),
child: TextField(
controller: state._queryTextController,
autofocus: true,
style: TextStyle(
color: Theme.of(context).brightness == Brightness.light
? Colors.black.withOpacity(0.9)
: Colors.white.withOpacity(0.9),
fontSize: 16.0,
),
decoration: InputDecoration(
hintText: state.widget.hint,
filled: true,
fillColor: Theme.of(context).brightness == Brightness.light
? Colors.white30
: Colors.black38,
hintStyle: TextStyle(
color: Theme.of(context).brightness == Brightness.light
? Colors.black38
: Colors.white30,
fontSize: 16.0,
),
border: InputBorder.none,
),
));
}
}
class AppBarPlacesAutoCompleteTextField extends StatefulWidget {
@override
_AppBarPlacesAutoCompleteTextFieldState createState() =>
_AppBarPlacesAutoCompleteTextFieldState();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment