From 4460f631b15d6f0205f20f99ec106e2ebd269cc2 Mon Sep 17 00:00:00 2001 From: bazrafkan Date: Fri, 3 Apr 2020 09:21:29 +0200 Subject: [PATCH] init --- .gitignore | 75 ++++++ .metadata | 10 + CHANGELOG.md | 8 + CONTRIBUTING.md | 22 ++ LICENSE | 21 ++ README.md | 32 +++ lib/google_place.dart | 31 +++ lib/src/autocomplete/autocomplete.dart | 54 ++++ .../autocomplete/autocomplete_parameters.dart | 98 +++++++ lib/src/autocomplete/autocomplete_result.dart | 26 ++ lib/src/autocomplete/prediction.dart | 51 ++++ lib/src/details/details.dart | 34 +++ lib/src/details/details_parameters.dart | 44 ++++ lib/src/details/details_result.dart | 30 +++ lib/src/details/result.dart | 109 ++++++++ lib/src/models/address_component.dart | 21 ++ lib/src/models/close.dart | 13 + lib/src/models/debug_log.dart | 13 + lib/src/models/geometry.dart | 18 ++ lib/src/models/input_type.dart | 7 + lib/src/models/lat_lng.dart | 46 ++++ lib/src/models/location.dart | 13 + lib/src/models/locationbias.dart | 65 +++++ .../models/main_text_matched_substring.dart | 13 + lib/src/models/matched_substring.dart | 13 + lib/src/models/northeast.dart | 13 + lib/src/models/open.dart | 13 + lib/src/models/opening_hours.dart | 27 ++ lib/src/models/period.dart | 16 ++ lib/src/models/photo.dart | 19 ++ lib/src/models/plus_code.dart | 13 + lib/src/models/rank-by.dart | 4 + lib/src/models/review.dart | 34 +++ lib/src/models/southwest.dart | 13 + lib/src/models/structured_formatting.dart | 21 ++ lib/src/models/term.dart | 13 + lib/src/models/viewport.dart | 20 ++ lib/src/photos/photo_parameters.dart | 26 ++ lib/src/photos/photos.dart | 33 +++ .../query_autocomplete.dart | 40 +++ .../query_autocomplete_parameters.dart | 48 ++++ lib/src/search/candidate.dart | 75 ++++++ lib/src/search/find_place_result.dart | 43 ++++ lib/src/search/near_by_search_result.dart | 43 ++++ lib/src/search/result.dart | 75 ++++++ lib/src/search/search.dart | 113 ++++++++ lib/src/search/search_parameters.dart | 243 ++++++++++++++++++ lib/src/search/text_search_result.dart | 43 ++++ lib/src/utils/network_utility.dart | 14 + pubspec.lock | 202 +++++++++++++++ pubspec.yaml | 19 ++ test/google_place_test.dart | 15 ++ 52 files changed, 2105 insertions(+) create mode 100644 .gitignore create mode 100644 .metadata create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lib/google_place.dart create mode 100644 lib/src/autocomplete/autocomplete.dart create mode 100644 lib/src/autocomplete/autocomplete_parameters.dart create mode 100644 lib/src/autocomplete/autocomplete_result.dart create mode 100644 lib/src/autocomplete/prediction.dart create mode 100644 lib/src/details/details.dart create mode 100644 lib/src/details/details_parameters.dart create mode 100644 lib/src/details/details_result.dart create mode 100644 lib/src/details/result.dart create mode 100644 lib/src/models/address_component.dart create mode 100644 lib/src/models/close.dart create mode 100644 lib/src/models/debug_log.dart create mode 100644 lib/src/models/geometry.dart create mode 100644 lib/src/models/input_type.dart create mode 100644 lib/src/models/lat_lng.dart create mode 100644 lib/src/models/location.dart create mode 100644 lib/src/models/locationbias.dart create mode 100644 lib/src/models/main_text_matched_substring.dart create mode 100644 lib/src/models/matched_substring.dart create mode 100644 lib/src/models/northeast.dart create mode 100644 lib/src/models/open.dart create mode 100644 lib/src/models/opening_hours.dart create mode 100644 lib/src/models/period.dart create mode 100644 lib/src/models/photo.dart create mode 100644 lib/src/models/plus_code.dart create mode 100644 lib/src/models/rank-by.dart create mode 100644 lib/src/models/review.dart create mode 100644 lib/src/models/southwest.dart create mode 100644 lib/src/models/structured_formatting.dart create mode 100644 lib/src/models/term.dart create mode 100644 lib/src/models/viewport.dart create mode 100644 lib/src/photos/photo_parameters.dart create mode 100644 lib/src/photos/photos.dart create mode 100644 lib/src/query_autocomplete/query_autocomplete.dart create mode 100644 lib/src/query_autocomplete/query_autocomplete_parameters.dart create mode 100644 lib/src/search/candidate.dart create mode 100644 lib/src/search/find_place_result.dart create mode 100644 lib/src/search/near_by_search_result.dart create mode 100644 lib/src/search/result.dart create mode 100644 lib/src/search/search.dart create mode 100644 lib/src/search/search_parameters.dart create mode 100644 lib/src/search/text_search_result.dart create mode 100644 lib/src/utils/network_utility.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/google_place_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb431f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..fd169eb --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2294d75bfa8d067ba90230c0fc2268f3636d7584 + channel: beta + +project_type: package diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b869297 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +## [0.0.1] - 2020-04-02 + +- The initial release supports all google place api requests + - Place Search returns a list of places based on a user's location or search string. + - Place Details returns more detailed information about a specific place, including user reviews. + - Place Photos provides access to the millions of place-related photos stored in Google's Place database. + - Place Autocomplete automatically fills in the name and/or address of a place as users type. + - Query Autocomplete provides a query prediction service for text-based geographic searches, returning suggested queries as users type. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..14fc5b9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing to the google_place package. +Thank you for your interest in contributing to the ***google_place*** package! I love receiving contributions, and your work helps quality of this package. This doc will walk you through the easiest way to make a change and have it submitted to the ***google_place*** package. + +## Developer workflow +The easiest workflow for adding a feature/fixing a bug is to test it out on the example app in this repo. + +### Environment +1. Fork the ***google_place*** repo on github. +2. Clone your fork of the ***google_place*** repo. +3. Build and run the example app in example/lib/main.dart (from the example/ directory, use $ flutter run). + +### Development +1. Make the changes to your local copy of the ***google_place*** package, testing the changes in the example app. +2. Write a unit test for your change, if possible, in one of the files in test/. +3. Update the CHANGELOG.md with a new version number, and a description of the change you're making. +4. Update the version: in the pubspec.yaml file to your new version. + +### Review +1. Make sure all the existing tests are passing by using the following command (from the root of the project): $ flutter test test/. +2. Make sure the repo is formatted using $ flutter format .. +3. Create a PR to merge the branch on your fork into ***google_place/master***. +4. Add bazrafkan as reviewers on the PR. I will take a look and add any comments. When the PR is ready to be merged, I will merge it and update the package on pub.dev! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7c5107a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 bazrafkan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c287964 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# google_place + +A Flutter plugin that provides a [Google Place API](https://developers.google.com/places/web-service/intro). + +## Preview + +The Places API is a service that returns information about places using HTTP requests. Places are defined within this API as establishments, geographic locations, or prominent points of interest. + +The following place requests are available: + +1. [Place Search](https://developers.google.com/places/web-service/search) returns a list of places based on a user's location or search string. +2. [Place Details](https://developers.google.com/places/web-service/details) returns more detailed information about a specific place, including user reviews. +3. [Place Photos](https://developers.google.com/places/web-service/photos) provides access to the millions of place-related photos stored in Google's Place database. +4. [Place Autocomplete](https://developers.google.com/places/web-service/autocomplete) automatically fills in the name and/or address of a place as users type. +5. [Query Autocomplete](https://developers.google.com/places/web-service/query) provides a query prediction service for text-based geographic searches, returning suggested queries as users type. + +## Usage + +To use this plugin, add **_google_place_** as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/packages-and-plugins/using-packages). + +## Getting Started + +- Get an API key at [https://cloud.google.com/maps-platform/](https://cloud.google.com/maps-platform/). + - Go to Google Developers Console. + - Choose the project that you want to enable Google Place on. + +## Sample Usage + +```dart +var googlePlace = GooglePlace("Your-Key"); +var risult = await googlePlace.autocomplete.get("1600 Amphitheatre"); +``` diff --git a/lib/google_place.dart b/lib/google_place.dart new file mode 100644 index 0000000..95b9f3d --- /dev/null +++ b/lib/google_place.dart @@ -0,0 +1,31 @@ +library google_place; + +import 'package:google_place/src/autocomplete/autocomplete.dart'; +import 'package:google_place/src/details/details.dart'; +import 'package:google_place/src/photos/photos.dart'; +import 'package:google_place/src/query_autocomplete/query_autocomplete.dart'; +import 'package:google_place/src/search/search.dart'; + +export 'package:google_place/src/autocomplete/autocomplete_result.dart'; +export 'package:google_place/src/models/input_type.dart'; +export 'package:google_place/src/models/lat_lng.dart'; +export 'package:google_place/src/models/locationbias.dart'; +export 'package:google_place/src/search/find_place_result.dart'; + +class GooglePlace { + final String apiKEY; + Search search; + Details details; + Photos photos; + Autocomplete autocomplete; + QueryAutocomplete queryAutocomplete; + + GooglePlace(this.apiKEY) { + assert(apiKEY != null); + this.search = Search(apiKEY); + this.details = Details(apiKEY); + this.photos = Photos(apiKEY); + this.autocomplete = Autocomplete(apiKEY); + this.queryAutocomplete = QueryAutocomplete(apiKEY); + } +} diff --git a/lib/src/autocomplete/autocomplete.dart b/lib/src/autocomplete/autocomplete.dart new file mode 100644 index 0000000..6987f8b --- /dev/null +++ b/lib/src/autocomplete/autocomplete.dart @@ -0,0 +1,54 @@ +import 'package:google_place/src/autocomplete/autocomplete_parameters.dart'; +import 'package:google_place/src/autocomplete/autocomplete_result.dart'; +import 'package:google_place/src/models/lat_lng.dart'; +import 'package:google_place/src/utils/network_utility.dart'; + +class Autocomplete { + final _authority = 'maps.googleapis.com'; + final _unencodedPath = 'maps/api/place/autocomplete/json'; + final String apiKEY; + + Autocomplete(this.apiKEY); + + /// Create a deprecation annotation which specifies the migration path and + /// expiration of the annotated feature. + /// + /// The [input] argument should be readable by programmers, and should state + /// an alternative feature (if available) as well as when an annotated feature + /// is expected to be removed. + Future get( + String input, { + String sessionToken, + int offset, + LatLng origin, + LatLng location, + int radius, + String language, + String types, + List components, + bool strictbounds = false, + }) async { + assert(input != null); + assert(input != ""); + if (strictbounds) { + assert(location != null); + assert(radius != null); + } + var queryParameters = AutocompleteParameters.createParameters( + apiKEY, + input, + sessionToken, + offset, + origin, + location, + radius, + language, + types, + components, + strictbounds, + ); + var uri = Uri.https(_authority, _unencodedPath, queryParameters); + var response = await NetworkUtility.fetchUrl(uri); + return AutocompleteResult.parseAutocompleteResult(response); + } +} diff --git a/lib/src/autocomplete/autocomplete_parameters.dart b/lib/src/autocomplete/autocomplete_parameters.dart new file mode 100644 index 0000000..8c8910e --- /dev/null +++ b/lib/src/autocomplete/autocomplete_parameters.dart @@ -0,0 +1,98 @@ +import 'package:google_place/src/models/lat_lng.dart'; + +class AutocompleteParameters { + static Map createParameters( + String apiKEY, + String input, + String sessionToken, + int offset, + LatLng origin, + LatLng location, + int radius, + String language, + String types, + List components, + bool strictbounds, + ) { + String result = input.trimRight(); + result = result.trimLeft(); + Map queryParameters = { + 'input': result, + 'key': apiKEY, + }; + + String componentsQuery; + if (components != null) { + for (int i = 0; i < components.length; i++) { + componentsQuery += 'country:${components[i]}'; + if (i + 1 != components.length) { + componentsQuery += '|'; + } + } + } + + if (origin != null) { + var item = { + 'origin': '${origin.latitude},${origin.latitude}', + }; + queryParameters.addAll(item); + } + + if (location != null) { + var item = { + 'location': '${location.latitude},${location.latitude}', + }; + queryParameters.addAll(item); + } + + if (offset != null) { + var item = { + 'offset': offset.toString(), + }; + queryParameters.addAll(item); + } + + if (radius != null) { + var item = { + 'radius': radius.toString(), + }; + queryParameters.addAll(item); + } + + if (sessionToken != null && sessionToken != '') { + var item = { + 'sessionToken': sessionToken, + }; + queryParameters.addAll(item); + } + + if (language != null && language != '') { + var item = { + 'language': language, + }; + queryParameters.addAll(item); + } + + if (types != null && types != '') { + var item = { + 'types': types, + }; + queryParameters.addAll(item); + } + + if (strictbounds) { + var item = { + 'strictbounds': 'strictbounds', + }; + queryParameters.addAll(item); + } + + if (componentsQuery != null && componentsQuery != '') { + var item = { + 'components': componentsQuery, + }; + queryParameters.addAll(item); + } + return queryParameters; + } +} diff --git a/lib/src/autocomplete/autocomplete_result.dart b/lib/src/autocomplete/autocomplete_result.dart new file mode 100644 index 0000000..19d9866 --- /dev/null +++ b/lib/src/autocomplete/autocomplete_result.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; + +import 'package:google_place/src/autocomplete/prediction.dart'; + +class AutocompleteResult { + final String status; + final List predictions; + + AutocompleteResult({this.status, this.predictions}); + + factory AutocompleteResult.fromJson(Map json) { + return AutocompleteResult( + status: json['status'] as String, + predictions: json['predictions'] != null + ? json['predictions'] + .map((json) => Prediction.fromJson(json)) + .toList() + : null, + ); + } + + static AutocompleteResult parseAutocompleteResult(String responseBody) { + final parsed = json.decode(responseBody).cast(); + return AutocompleteResult.fromJson(parsed); + } +} diff --git a/lib/src/autocomplete/prediction.dart b/lib/src/autocomplete/prediction.dart new file mode 100644 index 0000000..0b0dd62 --- /dev/null +++ b/lib/src/autocomplete/prediction.dart @@ -0,0 +1,51 @@ +import 'package:google_place/src/models/matched_substring.dart'; +import 'package:google_place/src/models/structured_formatting.dart'; +import 'package:google_place/src/models/term.dart'; + +class Prediction { + final String description; + final int distanceMeters; + final String id; + final List matchedSubstrings; + final String placeId; + final String reference; + final StructuredFormatting structuredFormatting; + final List terms; + final List types; + + Prediction({ + this.description, + this.distanceMeters, + this.id, + this.matchedSubstrings, + this.placeId, + this.reference, + this.structuredFormatting, + this.terms, + this.types, + }); + + factory Prediction.fromJson(Map json) { + return Prediction( + description: json['description'] as String, + distanceMeters: json['distance_meters'] as int, + id: json['id'] as String, + matchedSubstrings: json['matched_substrings'] != null + ? json['matched_substrings'] + .map((json) => MatchedSubstring.fromJson(json)) + .toList() + : null, + placeId: json['place_id'] as String, + reference: json['reference'] as String, + structuredFormatting: json['structured_formatting'] != null + ? StructuredFormatting.fromJson(json['structured_formatting']) + : null, + terms: json['terms'] != null + ? json['terms'].map((json) => Term.fromJson(json)).toList() + : null, + types: json['types'] != null + ? (json['types'] as List).cast() + : null, + ); + } +} diff --git a/lib/src/details/details.dart b/lib/src/details/details.dart new file mode 100644 index 0000000..c44b4e7 --- /dev/null +++ b/lib/src/details/details.dart @@ -0,0 +1,34 @@ +import 'package:google_place/src/details/details_parameters.dart'; +import 'package:google_place/src/details/details_result.dart'; +import 'package:google_place/src/utils/network_utility.dart'; + +class Details { + final _authority = 'maps.googleapis.com'; + final _unencodedPath = 'maps/api/place/details/json'; + final String apiKEY; + + Details(this.apiKEY); + + Future get( + String placeId, { + String language, + String region, + String sessionToken, + String fields, + }) async { + assert(placeId != null); + assert(placeId != ""); + var queryParameters = DetailsParameters.createParameters( + apiKEY, + placeId, + language, + region, + sessionToken, + fields, + ); + + var uri = Uri.https(_authority, _unencodedPath, queryParameters); + var response = await NetworkUtility.fetchUrl(uri); + return DetailsResult.parseDetailsResult(response); + } +} diff --git a/lib/src/details/details_parameters.dart b/lib/src/details/details_parameters.dart new file mode 100644 index 0000000..c00e486 --- /dev/null +++ b/lib/src/details/details_parameters.dart @@ -0,0 +1,44 @@ +class DetailsParameters { + static Map createParameters( + String apiKEY, + String placeId, + String language, + String region, + String sessionToken, + String fields, + ) { + Map queryParameters = { + 'key': apiKEY, + 'place_id': placeId, + }; + + if (language != null && language != '') { + var item = { + 'language': language, + }; + queryParameters.addAll(item); + } + + if (region != null && region != '') { + var item = { + 'region': region, + }; + queryParameters.addAll(item); + } + + if (sessionToken != null && sessionToken != '') { + var item = { + 'sessiontoken': sessionToken, + }; + queryParameters.addAll(item); + } + + if (fields != null && fields != '') { + var item = { + 'fields': fields, + }; + queryParameters.addAll(item); + } + return queryParameters; + } +} diff --git a/lib/src/details/details_result.dart b/lib/src/details/details_result.dart new file mode 100644 index 0000000..315abbc --- /dev/null +++ b/lib/src/details/details_result.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +import 'package:google_place/src/details/result.dart'; + +class DetailsResult { + final String status; + final List htmlAttributions; + final Result results; + + DetailsResult({ + this.status, + this.htmlAttributions, + this.results, + }); + + factory DetailsResult.fromJson(Map json) { + return DetailsResult( + status: json['status'], + htmlAttributions: json['html_attributions'] != null + ? (json['html_attributions'] as List).cast() + : null, + results: json['result'] != null ? Result.fromJson(json['result']) : null, + ); + } + + static DetailsResult parseDetailsResult(String responseBody) { + final parsed = json.decode(responseBody).cast(); + return DetailsResult.fromJson(parsed); + } +} diff --git a/lib/src/details/result.dart b/lib/src/details/result.dart new file mode 100644 index 0000000..2328f25 --- /dev/null +++ b/lib/src/details/result.dart @@ -0,0 +1,109 @@ +import 'package:google_place/src/models/address_component.dart'; +import 'package:google_place/src/models/geometry.dart'; +import 'package:google_place/src/models/opening_hours.dart'; +import 'package:google_place/src/models/photo.dart'; +import 'package:google_place/src/models/plus_code.dart'; +import 'package:google_place/src/models/review.dart'; + +class Result { + final List addressComponents; + final String adrAddress; + final String formattedAddress; + final String formattedPhoneNumber; + final Geometry geometry; + final String icon; + final String id; + final String internationalPhoneNumber; + final String name; + final OpeningHours openingHours; + final List photos; + final String placeId; + final PlusCode plusCode; + final double rating; + final String reference; + final List reviews; + final String scope; + final List types; + final String url; + final int userRatingsTotal; + final int utcOffset; + final String vicinity; + final String website; + final int priceLevel; + final bool permanentlyClosed; + + Result({ + this.addressComponents, + this.adrAddress, + this.formattedAddress, + this.formattedPhoneNumber, + this.geometry, + this.icon, + this.id, + this.internationalPhoneNumber, + this.name, + this.openingHours, + this.photos, + this.placeId, + this.plusCode, + this.rating, + this.reference, + this.reviews, + this.scope, + this.types, + this.url, + this.userRatingsTotal, + this.utcOffset, + this.vicinity, + this.website, + this.priceLevel, + this.permanentlyClosed, + }); + + factory Result.fromJson(Map json) { + return Result( + addressComponents: json['address_components'] != null + ? json['address_components'] + .map((json) => AddressComponent.fromJson(json)) + .toList() + : null, + adrAddress: json['adr_address'], + formattedAddress: json['formatted_address'], + formattedPhoneNumber: json['formatted_phone_number'], + geometry: + json['geometry'] != null ? Geometry.fromJson(json['geometry']) : null, + icon: json['icon'], + id: json['id'], + internationalPhoneNumber: json['international_phone_number'], + name: json['name'], + openingHours: json['opening_hours'] != null + ? OpeningHours.fromJson(json['opening_hours']) + : null, + photos: json['photos'] != null + ? json['photos'].map((json) => Photo.fromJson(json)).toList() + : null, + placeId: json['place_id'], + plusCode: json['plus_code'] != null + ? PlusCode.fromJson(json['plus_code']) + : null, + rating: json['rating'] != null ? json['rating'].toDouble() : null, + reference: json['reference'], + reviews: json['reviews'] != null + ? json['reviews'] + .map((json) => Review.fromJson(json)) + .toList() + : null, + scope: json['scope'], + types: json['types'] != null + ? (json['types'] as List).cast() + : null, + url: json['url'], + userRatingsTotal: json['user_ratings_total'], + utcOffset: json['utc_offset'], + vicinity: json['vicinity'], + website: json['website'], + priceLevel: json['price_level'], + permanentlyClosed: json['permanently_closed'], + ); + } +} diff --git a/lib/src/models/address_component.dart b/lib/src/models/address_component.dart new file mode 100644 index 0000000..a30e841 --- /dev/null +++ b/lib/src/models/address_component.dart @@ -0,0 +1,21 @@ +class AddressComponent { + final String longName; + final String shortName; + final List types; + + AddressComponent({ + this.longName, + this.shortName, + this.types, + }); + + factory AddressComponent.fromJson(Map json) { + return AddressComponent( + longName: json['long_name'], + shortName: json['short_name'], + types: json['types'] != null + ? (json['types'] as List).cast() + : null, + ); + } +} diff --git a/lib/src/models/close.dart b/lib/src/models/close.dart new file mode 100644 index 0000000..8ec5d08 --- /dev/null +++ b/lib/src/models/close.dart @@ -0,0 +1,13 @@ +class Close { + final int day; + final String time; + + Close({this.day, this.time}); + + factory Close.fromJson(Map json) { + return Close( + day: json['day'], + time: json['time'], + ); + } +} diff --git a/lib/src/models/debug_log.dart b/lib/src/models/debug_log.dart new file mode 100644 index 0000000..574a337 --- /dev/null +++ b/lib/src/models/debug_log.dart @@ -0,0 +1,13 @@ +class DebugLog { + final List line; + + DebugLog({this.line}); + + factory DebugLog.fromJson(Map json) { + return DebugLog( + line: json['line'] != null + ? (json['line'] as List).cast() + : null, + ); + } +} diff --git a/lib/src/models/geometry.dart b/lib/src/models/geometry.dart new file mode 100644 index 0000000..23d9633 --- /dev/null +++ b/lib/src/models/geometry.dart @@ -0,0 +1,18 @@ +import 'package:google_place/src/models/location.dart'; +import 'package:google_place/src/models/viewport.dart'; + +class Geometry { + final Location location; + final Viewport viewport; + + Geometry({this.location, this.viewport}); + + factory Geometry.fromJson(Map json) { + return Geometry( + location: + json['location'] != null ? Location.fromJson(json['location']) : null, + viewport: + json['viewport'] != null ? Viewport.fromJson(json['viewport']) : null, + ); + } +} diff --git a/lib/src/models/input_type.dart b/lib/src/models/input_type.dart new file mode 100644 index 0000000..03fbd91 --- /dev/null +++ b/lib/src/models/input_type.dart @@ -0,0 +1,7 @@ +/// [InputType] - The type of input. This can be one of either textquery or phonenumber. +/// Phone numbers must be in international format (prefixed by a plus sign ("+"), +/// followed by the country code, then the phone number itself). +enum InputType { + TextQuery, + PhoneNumber, +} diff --git a/lib/src/models/lat_lng.dart b/lib/src/models/lat_lng.dart new file mode 100644 index 0000000..592aa45 --- /dev/null +++ b/lib/src/models/lat_lng.dart @@ -0,0 +1,46 @@ +import 'dart:ui'; + +/// A pair of latitude and longitude coordinates, stored as degrees. +class LatLng { + /// Creates a geographical location specified in degrees [latitude] and + /// [longitude]. + /// + /// The latitude is clamped to the inclusive interval from -90.0 to +90.0. + /// + /// The longitude is normalized to the half-open interval from -180.0 + /// (inclusive) to +180.0 (exclusive) + const LatLng(double latitude, double longitude) + : assert(latitude != null), + assert(longitude != null), + latitude = + (latitude < -90.0 ? -90.0 : (90.0 < latitude ? 90.0 : latitude)), + longitude = (longitude + 180.0) % 360.0 - 180.0; + + /// The latitude in degrees between -90.0 and 90.0, both inclusive. + final double latitude; + + /// The longitude in degrees between -180.0 (inclusive) and 180.0 (exclusive). + final double longitude; + + dynamic _toJson() { + return [latitude, longitude]; + } + + static LatLng _fromJson(dynamic json) { + if (json == null) { + return null; + } + return LatLng(json[0], json[1]); + } + + @override + String toString() => '$runtimeType($latitude, $longitude)'; + + @override + bool operator ==(Object o) { + return o is LatLng && o.latitude == latitude && o.longitude == longitude; + } + + @override + int get hashCode => hashValues(latitude, longitude); +} diff --git a/lib/src/models/location.dart b/lib/src/models/location.dart new file mode 100644 index 0000000..ae7dd3d --- /dev/null +++ b/lib/src/models/location.dart @@ -0,0 +1,13 @@ +class Location { + final double lat; + final double lng; + + Location({this.lat, this.lng}); + + factory Location.fromJson(Map json) { + return Location( + lat: json['lat'], + lng: json['lng'], + ); + } +} diff --git a/lib/src/models/locationbias.dart b/lib/src/models/locationbias.dart new file mode 100644 index 0000000..780d329 --- /dev/null +++ b/lib/src/models/locationbias.dart @@ -0,0 +1,65 @@ +import 'package:google_place/src/models/lat_lng.dart'; + +/// [Locationbias] - Prefer results in a specified area, by specifying either a radius plus lat/lng, +/// or two lat/lng pairs representing the points of a rectangle. +/// If this parameter is not specified, the API uses IP address biasing by default. +class Locationbias { + /// [ipbias] - Instructs the API to use IP address biasing. + final bool ipbias; + + /// [point] - A single lat/lng coordinate. + final LatLng point; + + /// [circular] - A string specifying radius in meters, plus lat/lng in decimal degrees. + final Circular circular; + + /// [rectangular] - A string specifying two lat/lng pairs in decimal degrees, + /// representing the south/west and north/east points of a rectangle. + final Rectangular rectangular; + + Locationbias({this.ipbias, this.point, this.circular, this.rectangular}) { + if (ipbias != null && ipbias) { + assert(point == null); + assert(circular == null); + assert(rectangular == null); + } + if (point != null) { + assert(ipbias == null); + assert(circular == null); + assert(rectangular == null); + } + if (circular != null) { + assert(ipbias == null); + assert(point == null); + assert(rectangular == null); + } + if (rectangular != null) { + assert(ipbias == null); + assert(point == null); + assert(circular == null); + } + if (ipbias == null && ipbias) + assert((ipbias != null || ipbias != false) && + point != null && + circular != null && + rectangular != null); + } +} + +class Circular { + final int radius; + final LatLng latLng; + + Circular(this.radius, this.latLng) + : assert(radius != null), + assert(latLng != null); +} + +class Rectangular { + final LatLng southWest; + final LatLng northEast; + + Rectangular(this.southWest, this.northEast) + : assert(southWest != null), + assert(northEast != null); +} diff --git a/lib/src/models/main_text_matched_substring.dart b/lib/src/models/main_text_matched_substring.dart new file mode 100644 index 0000000..a0b21e0 --- /dev/null +++ b/lib/src/models/main_text_matched_substring.dart @@ -0,0 +1,13 @@ +class MainTextMatchedSubstring { + final int length; + final int offset; + + MainTextMatchedSubstring({this.length, this.offset}); + + factory MainTextMatchedSubstring.fromJson(Map json) { + return MainTextMatchedSubstring( + length: json['length'] as int, + offset: json['offset'] as int, + ); + } +} diff --git a/lib/src/models/matched_substring.dart b/lib/src/models/matched_substring.dart new file mode 100644 index 0000000..7ef88f2 --- /dev/null +++ b/lib/src/models/matched_substring.dart @@ -0,0 +1,13 @@ +class MatchedSubstring { + final int length; + final int offset; + + MatchedSubstring({this.length, this.offset}); + + factory MatchedSubstring.fromJson(Map json) { + return MatchedSubstring( + length: json['length'] as int, + offset: json['offset'] as int, + ); + } +} diff --git a/lib/src/models/northeast.dart b/lib/src/models/northeast.dart new file mode 100644 index 0000000..8e5c052 --- /dev/null +++ b/lib/src/models/northeast.dart @@ -0,0 +1,13 @@ +class Northeast { + final double lat; + final double lng; + + Northeast({this.lat, this.lng}); + + factory Northeast.fromJson(Map json) { + return Northeast( + lat: json['lat'], + lng: json['lng'], + ); + } +} diff --git a/lib/src/models/open.dart b/lib/src/models/open.dart new file mode 100644 index 0000000..3339d1a --- /dev/null +++ b/lib/src/models/open.dart @@ -0,0 +1,13 @@ +class Open { + final int day; + final String time; + + Open({this.day, this.time}); + + factory Open.fromJson(Map json) { + return Open( + day: json['day'], + time: json['time'], + ); + } +} diff --git a/lib/src/models/opening_hours.dart b/lib/src/models/opening_hours.dart new file mode 100644 index 0000000..69b7eb2 --- /dev/null +++ b/lib/src/models/opening_hours.dart @@ -0,0 +1,27 @@ +import 'package:google_place/src/models/period.dart'; + +class OpeningHours { + final bool openNow; + final List weekdayText; + final List periods; + + OpeningHours({ + this.openNow, + this.weekdayText, + this.periods, + }); + + factory OpeningHours.fromJson(Map json) { + return OpeningHours( + openNow: json['open_now'], + weekdayText: json['weekday_text'] != null + ? (json['weekday_text'] as List).cast() + : null, + periods: json['periods'] != null + ? json['periods'] + .map((json) => Period.fromJson(json)) + .toList() + : null, + ); + } +} diff --git a/lib/src/models/period.dart b/lib/src/models/period.dart new file mode 100644 index 0000000..cb1eaae --- /dev/null +++ b/lib/src/models/period.dart @@ -0,0 +1,16 @@ +import 'package:google_place/src/models/close.dart'; +import 'package:google_place/src/models/open.dart'; + +class Period { + final Close close; + final Open open; + + Period({this.close, this.open}); + + factory Period.fromJson(Map json) { + return Period( + close: json['close'] != null ? Close.fromJson(json['close']) : null, + open: json['open'] != null ? Open.fromJson(json['open']) : null, + ); + } +} diff --git a/lib/src/models/photo.dart b/lib/src/models/photo.dart new file mode 100644 index 0000000..9ec2253 --- /dev/null +++ b/lib/src/models/photo.dart @@ -0,0 +1,19 @@ +class Photo { + final String photoReference; + final int height; + final int width; + final List htmlAttributions; + + Photo({this.photoReference, this.height, this.width, this.htmlAttributions}); + + factory Photo.fromJson(Map json) { + return Photo( + photoReference: json['photo_reference'], + height: json['height'], + width: json['width'], + htmlAttributions: json['html_attributions'] != null + ? (json['html_attributions'] as List).cast() + : null, + ); + } +} diff --git a/lib/src/models/plus_code.dart b/lib/src/models/plus_code.dart new file mode 100644 index 0000000..2ebbd50 --- /dev/null +++ b/lib/src/models/plus_code.dart @@ -0,0 +1,13 @@ +class PlusCode { + final String globalCode; + final String compoundCode; + + PlusCode({this.globalCode, this.compoundCode}); + + factory PlusCode.fromJson(Map json) { + return PlusCode( + globalCode: json['global_code'], + compoundCode: json['compound_code'], + ); + } +} diff --git a/lib/src/models/rank-by.dart b/lib/src/models/rank-by.dart new file mode 100644 index 0000000..8c040b1 --- /dev/null +++ b/lib/src/models/rank-by.dart @@ -0,0 +1,4 @@ +enum RankBy { + Prominence, + Distance, +} diff --git a/lib/src/models/review.dart b/lib/src/models/review.dart new file mode 100644 index 0000000..faa72bb --- /dev/null +++ b/lib/src/models/review.dart @@ -0,0 +1,34 @@ +class Review { + final String authorName; + final String authorUrl; + final String language; + final String profilePhotoUrl; + final int rating; + final String relativeTimeDescription; + final String text; + final int time; + + Review({ + this.authorName, + this.authorUrl, + this.language, + this.profilePhotoUrl, + this.rating, + this.relativeTimeDescription, + this.text, + this.time, + }); + + factory Review.fromJson(Map json) { + return Review( + authorName: json['author_name'], + authorUrl: json['author_url'], + language: json['language'], + profilePhotoUrl: json['profile_photo_url'], + rating: json['rating'], + relativeTimeDescription: json['relative_time_description'], + text: json['text'], + time: json['time'], + ); + } +} diff --git a/lib/src/models/southwest.dart b/lib/src/models/southwest.dart new file mode 100644 index 0000000..e80254e --- /dev/null +++ b/lib/src/models/southwest.dart @@ -0,0 +1,13 @@ +class Southwest { + final double lat; + final double lng; + + Southwest({this.lat, this.lng}); + + factory Southwest.fromJson(Map json) { + return Southwest( + lat: json['lat'], + lng: json['lng'], + ); + } +} diff --git a/lib/src/models/structured_formatting.dart b/lib/src/models/structured_formatting.dart new file mode 100644 index 0000000..7503670 --- /dev/null +++ b/lib/src/models/structured_formatting.dart @@ -0,0 +1,21 @@ +import 'package:google_place/src/models/main_text_matched_substring.dart'; + +class StructuredFormatting { + final String mainText; + final List mainTextMatchedSubstrings; + final String secondaryText; + + StructuredFormatting( + {this.mainText, this.mainTextMatchedSubstrings, this.secondaryText}); + + factory StructuredFormatting.fromJson(Map json) { + return StructuredFormatting( + mainText: json['main_text'] as String, + mainTextMatchedSubstrings: json['main_text_matched_substrings'] + .map( + (json) => MainTextMatchedSubstring.fromJson(json)) + .toList(), + secondaryText: json['secondary_text'] as String, + ); + } +} diff --git a/lib/src/models/term.dart b/lib/src/models/term.dart new file mode 100644 index 0000000..c8c1988 --- /dev/null +++ b/lib/src/models/term.dart @@ -0,0 +1,13 @@ +class Term { + final int offset; + final String value; + + Term({this.offset, this.value}); + + factory Term.fromJson(Map json) { + return Term( + offset: json['offset'] as int, + value: json['value'] as String, + ); + } +} diff --git a/lib/src/models/viewport.dart b/lib/src/models/viewport.dart new file mode 100644 index 0000000..8a8ffda --- /dev/null +++ b/lib/src/models/viewport.dart @@ -0,0 +1,20 @@ +import 'package:google_place/src/models/northeast.dart'; +import 'package:google_place/src/models/southwest.dart'; + +class Viewport { + final Northeast northeast; + final Southwest southwest; + + Viewport({this.northeast, this.southwest}); + + factory Viewport.fromJson(Map json) { + return Viewport( + northeast: json['northeast'] != null + ? Northeast.fromJson(json['northeast']) + : null, + southwest: json['southwest'] != null + ? Southwest.fromJson(json['southwest']) + : null, + ); + } +} diff --git a/lib/src/photos/photo_parameters.dart b/lib/src/photos/photo_parameters.dart new file mode 100644 index 0000000..a5e7216 --- /dev/null +++ b/lib/src/photos/photo_parameters.dart @@ -0,0 +1,26 @@ +class PhotoParameters { + static Map createParameters( + String apiKEY, + String photoReference, + int maxHeight, + int maxWidth, + ) { + Map queryParameters = { + 'photoreference': photoReference, + 'key': apiKEY, + }; + if (maxHeight != null) { + var item = { + 'maxheight': maxHeight.toString(), + }; + queryParameters.addAll(item); + } + if (maxWidth != null) { + var item = { + 'maxwidth': maxWidth.toString(), + }; + queryParameters.addAll(item); + } + return queryParameters; + } +} diff --git a/lib/src/photos/photos.dart b/lib/src/photos/photos.dart new file mode 100644 index 0000000..14bebb5 --- /dev/null +++ b/lib/src/photos/photos.dart @@ -0,0 +1,33 @@ +import 'dart:typed_data'; + +import 'package:google_place/src/photos/photo_parameters.dart'; +import 'package:google_place/src/utils/network_utility.dart'; + +class Photos { + final _authority = 'maps.googleapis.com'; + final _unencodedPath = 'maps/api/place/photo'; + final String apiKEY; + + Photos(this.apiKEY); + + Future get( + String photoReference, { + int maxHeight, + int maxWidth, + }) async { + var queryParameters = PhotoParameters.createParameters( + apiKEY, + photoReference, + maxHeight, + maxWidth, + ); + var uri = Uri.https(_authority, _unencodedPath, queryParameters); + var response = await NetworkUtility.fetchUrl(uri); + if (response != null) { + List list = response.codeUnits; + return Uint8List.fromList(list); + } else { + return null; + } + } +} diff --git a/lib/src/query_autocomplete/query_autocomplete.dart b/lib/src/query_autocomplete/query_autocomplete.dart new file mode 100644 index 0000000..d632877 --- /dev/null +++ b/lib/src/query_autocomplete/query_autocomplete.dart @@ -0,0 +1,40 @@ +import 'package:google_place/src/autocomplete/autocomplete_result.dart'; +import 'package:google_place/src/models/lat_lng.dart'; +import 'package:google_place/src/query_autocomplete/query_autocomplete_parameters.dart'; +import 'package:google_place/src/utils/network_utility.dart'; + +class QueryAutocomplete { + final _authority = 'maps.googleapis.com'; + final _unencodedPath = 'maps/api/place/queryautocomplete/json'; + final String apiKEY; + + QueryAutocomplete(this.apiKEY); + + /// Create a deprecation annotation which specifies the migration path and + /// expiration of the annotated feature. + /// + /// The [input] argument should be readable by programmers, and should state + /// an alternative feature (if available) as well as when an annotated feature + /// is expected to be removed. + Future get( + String input, { + int offset, + LatLng location, + int radius, + String language, + }) async { + assert(input != null); + assert(input != ""); + var queryParameters = QueryAutocompleteParameters.createParameters( + apiKEY, + input, + offset, + location, + radius, + language, + ); + var uri = Uri.https(_authority, _unencodedPath, queryParameters); + var response = await NetworkUtility.fetchUrl(uri); + return AutocompleteResult.parseAutocompleteResult(response); + } +} diff --git a/lib/src/query_autocomplete/query_autocomplete_parameters.dart b/lib/src/query_autocomplete/query_autocomplete_parameters.dart new file mode 100644 index 0000000..0df23b9 --- /dev/null +++ b/lib/src/query_autocomplete/query_autocomplete_parameters.dart @@ -0,0 +1,48 @@ +import 'package:google_place/src/models/lat_lng.dart'; + +class QueryAutocompleteParameters { + static Map createParameters( + String apiKEY, + String input, + int offset, + LatLng location, + int radius, + String language, + ) { + String result = input.trimRight(); + result = result.trimLeft(); + Map queryParameters = { + 'input': result, + 'key': apiKEY, + }; + + if (location != null) { + var item = { + 'location': '${location.latitude},${location.latitude}', + }; + queryParameters.addAll(item); + } + + if (offset != null) { + var item = { + 'offset': offset.toString(), + }; + queryParameters.addAll(item); + } + + if (radius != null) { + var item = { + 'radius': radius.toString(), + }; + queryParameters.addAll(item); + } + + if (language != null && language != '') { + var item = { + 'language': language, + }; + queryParameters.addAll(item); + } + return queryParameters; + } +} diff --git a/lib/src/search/candidate.dart b/lib/src/search/candidate.dart new file mode 100644 index 0000000..02cb2aa --- /dev/null +++ b/lib/src/search/candidate.dart @@ -0,0 +1,75 @@ +import 'package:google_place/src/models/geometry.dart'; +import 'package:google_place/src/models/opening_hours.dart'; +import 'package:google_place/src/models/photo.dart'; +import 'package:google_place/src/models/plus_code.dart'; + +class Candidate { + final Geometry geometry; + final OpeningHours openingHours; + final List photos; + final PlusCode plusCode; + final String formattedAddress; + final String name; + final double rating; + final String icon; + final String id; + final String placeId; + final int priceLevel; + final String reference; + final String scope; + final List types; + final int userRatingsTotal; + final String vicinity; + final bool permanentlyClosed; + + Candidate({ + this.geometry, + this.openingHours, + this.photos, + this.plusCode, + this.formattedAddress, + this.name, + this.rating, + this.icon, + this.id, + this.placeId, + this.priceLevel, + this.reference, + this.scope, + this.types, + this.userRatingsTotal, + this.vicinity, + this.permanentlyClosed, + }); + + factory Candidate.fromJson(Map json) { + return Candidate( + geometry: + json['geometry'] != null ? Geometry.fromJson(json['geometry']) : null, + openingHours: json['opening_hours'] != null + ? OpeningHours.fromJson(json['opening_hours']) + : null, + photos: json['photos'] != null + ? json['photos'].map((json) => Photo.fromJson(json)).toList() + : null, + plusCode: json['plus_code'] != null + ? PlusCode.fromJson(json['plus_code']) + : null, + formattedAddress: json['formatted_address'], + name: json['name'], + rating: json['rating'] != null ? json['rating'].toDouble() : null, + icon: json['icon'], + id: json['id'], + placeId: json['place_id'], + priceLevel: json['price_level'], + reference: json['reference'], + scope: json['scope'], + types: json['types'] != null + ? (json['types'] as List).cast() + : null, + userRatingsTotal: json['user_ratings_total'], + vicinity: json['vicinity'], + permanentlyClosed: json['permanently_closed'], + ); + } +} diff --git a/lib/src/search/find_place_result.dart b/lib/src/search/find_place_result.dart new file mode 100644 index 0000000..e77f1ac --- /dev/null +++ b/lib/src/search/find_place_result.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:google_place/src/models/debug_log.dart'; +import 'package:google_place/src/search/candidate.dart'; + +class FindPlaceResult { + final String status; + final List htmlAttributions; + final String nextPageToken; + final DebugLog debugLog; + final List candidates; + + FindPlaceResult({ + this.status, + this.htmlAttributions, + this.nextPageToken, + this.debugLog, + this.candidates, + }); + + factory FindPlaceResult.fromJson(Map json) { + return FindPlaceResult( + status: json['status'], + htmlAttributions: json['html_attributions'] != null + ? (json['html_attributions'] as List).cast() + : null, + nextPageToken: json['next_page_token'], + debugLog: json['debug_log'] != null + ? DebugLog.fromJson(json['debug_log']) + : null, + candidates: json['candidates'] != null + ? json['candidates'] + .map((json) => Candidate.fromJson(json)) + .toList() + : null, + ); + } + + static FindPlaceResult parseFindPlaceResult(String responseBody) { + final parsed = json.decode(responseBody).cast(); + return FindPlaceResult.fromJson(parsed); + } +} diff --git a/lib/src/search/near_by_search_result.dart b/lib/src/search/near_by_search_result.dart new file mode 100644 index 0000000..a4af1ec --- /dev/null +++ b/lib/src/search/near_by_search_result.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:google_place/src/models/debug_log.dart'; +import 'package:google_place/src/search/result.dart'; + +class NearBySearchResult { + final String status; + final List htmlAttributions; + final String nextPageToken; + final DebugLog debugLog; + final List results; + + NearBySearchResult({ + this.status, + this.htmlAttributions, + this.nextPageToken, + this.debugLog, + this.results, + }); + + factory NearBySearchResult.fromJson(Map json) { + return NearBySearchResult( + status: json['status'], + htmlAttributions: json['html_attributions'] != null + ? (json['html_attributions'] as List).cast() + : null, + nextPageToken: json['next_page_token'], + debugLog: json['debug_log'] != null + ? DebugLog.fromJson(json['debug_log']) + : null, + results: json['results'] != null + ? json['results'] + .map((json) => Result.fromJson(json)) + .toList() + : null, + ); + } + + static NearBySearchResult parseNearBySearchResult(String responseBody) { + final parsed = json.decode(responseBody).cast(); + return NearBySearchResult.fromJson(parsed); + } +} diff --git a/lib/src/search/result.dart b/lib/src/search/result.dart new file mode 100644 index 0000000..726dbb8 --- /dev/null +++ b/lib/src/search/result.dart @@ -0,0 +1,75 @@ +import 'package:google_place/src/models/geometry.dart'; +import 'package:google_place/src/models/opening_hours.dart'; +import 'package:google_place/src/models/photo.dart'; +import 'package:google_place/src/models/plus_code.dart'; + +class Result { + final Geometry geometry; + final OpeningHours openingHours; + final List photos; + final PlusCode plusCode; + final String formattedAddress; + final String name; + final double rating; + final String icon; + final String id; + final String placeId; + final int priceLevel; + final String reference; + final String scope; + final List types; + final int userRatingsTotal; + final String vicinity; + final bool permanentlyClosed; + + Result({ + this.geometry, + this.openingHours, + this.photos, + this.plusCode, + this.formattedAddress, + this.name, + this.rating, + this.icon, + this.id, + this.placeId, + this.priceLevel, + this.reference, + this.scope, + this.types, + this.userRatingsTotal, + this.vicinity, + this.permanentlyClosed, + }); + + factory Result.fromJson(Map json) { + return Result( + geometry: + json['geometry'] != null ? Geometry.fromJson(json['geometry']) : null, + openingHours: json['opening_hours'] != null + ? OpeningHours.fromJson(json['opening_hours']) + : null, + photos: json['photos'] != null + ? json['photos'].map((json) => Photo.fromJson(json)).toList() + : null, + plusCode: json['plus_code'] != null + ? PlusCode.fromJson(json['plus_code']) + : null, + formattedAddress: json['formatted_address'], + name: json['name'], + rating: json['rating'] != null ? json['rating'].toDouble() : null, + icon: json['icon'], + id: json['id'], + placeId: json['place_id'], + priceLevel: json['price_level'], + reference: json['reference'], + scope: json['scope'], + types: json['types'] != null + ? (json['types'] as List).cast() + : null, + userRatingsTotal: json['user_ratings_total'], + vicinity: json['vicinity'], + permanentlyClosed: json['permanently_closed'], + ); + } +} diff --git a/lib/src/search/search.dart b/lib/src/search/search.dart new file mode 100644 index 0000000..0dccd94 --- /dev/null +++ b/lib/src/search/search.dart @@ -0,0 +1,113 @@ +import 'package:google_place/src/models/input_type.dart'; +import 'package:google_place/src/models/location.dart'; +import 'package:google_place/src/models/locationbias.dart'; +import 'package:google_place/src/models/rank-by.dart'; +import 'package:google_place/src/search/find_place_result.dart'; +import 'package:google_place/src/search/near_by_search_result.dart'; +import 'package:google_place/src/search/search_parameters.dart'; +import 'package:google_place/src/search/text_search_result.dart'; +import 'package:google_place/src/utils/network_utility.dart'; + +class Search { + final _authority = 'maps.googleapis.com'; + final _unencodedPathFindPlace = 'maps/api/place/findplacefromtext/json'; + final _unencodedPathNearBySearch = 'maps/api/place/nearbysearch/json'; + final _unencodedPathTextSearch = 'maps/api/place/textsearch/json'; + final String apiKEY; + + Search(this.apiKEY); + + Future getFindPlace( + String input, + InputType inputType, { + String language, + String fields, + Locationbias locationbias, + }) async { + assert(input != null); + assert(input != ""); + assert(inputType != null); + assert(inputType != null); + var queryParameters = SearchParameters.createFindPlaceParameters( + apiKEY, + input, + inputType, + language, + fields, + locationbias, + ); + + var uri = Uri.https(_authority, _unencodedPathFindPlace, queryParameters); + var response = await NetworkUtility.fetchUrl(uri); + return FindPlaceResult.parseFindPlaceResult(response); + } + + Future getNearBySearch( + Location location, + int radius, { + String keyword, + String language, + int minprice, + int maxprice, + String name, + bool opennow, + RankBy rankby, + String type, + String pagetoken, + }) async { + assert(location != null); + assert(radius != null); + var queryParameters = SearchParameters.createNearBySearchParameters( + apiKEY, + location, + radius, + keyword, + language, + minprice, + maxprice, + name, + opennow, + rankby, + type, + pagetoken, + ); + + var uri = + Uri.https(_authority, _unencodedPathNearBySearch, queryParameters); + var response = await NetworkUtility.fetchUrl(uri); + return NearBySearchResult.parseNearBySearchResult(response); + } + + Future getTextSearch( + String query, { + String region, + Location location, + int radius, + String language, + int minprice, + int maxprice, + bool opennow, + String type, + String pagetoken, + }) async { + assert(query != null); + assert(query != ""); + var queryParameters = SearchParameters.createTextSearchParameters( + apiKEY, + query, + region, + location, + radius, + language, + minprice, + maxprice, + opennow, + type, + pagetoken, + ); + + var uri = Uri.https(_authority, _unencodedPathTextSearch, queryParameters); + var response = await NetworkUtility.fetchUrl(uri); + return TextSearchResult.parseTextSearchResult(response); + } +} diff --git a/lib/src/search/search_parameters.dart b/lib/src/search/search_parameters.dart new file mode 100644 index 0000000..d76e868 --- /dev/null +++ b/lib/src/search/search_parameters.dart @@ -0,0 +1,243 @@ +import 'package:google_place/src/models/input_type.dart'; +import 'package:google_place/src/models/location.dart'; +import 'package:google_place/src/models/locationbias.dart'; +import 'package:google_place/src/models/rank-by.dart'; + +class SearchParameters { + static Map createFindPlaceParameters( + String apiKEY, + String input, + InputType inputType, + String language, + String fields, + Locationbias locationbias, + ) { + String result = input.trimRight(); + result = result.trimLeft(); + Map queryParameters = { + 'input': result, + 'key': apiKEY, + 'inputtype': inputType == InputType.TextQuery + ? 'textquery' + : inputType == InputType.PhoneNumber ? 'phonenumber' : 'textquery', + }; + + if (language != null && language != '') { + var item = { + 'language': language, + }; + queryParameters.addAll(item); + } + + if (fields != null && fields != '') { + var item = { + 'fields': fields, + }; + queryParameters.addAll(item); + } + + if (locationbias != null) { + String value; + if (locationbias.ipbias != null && locationbias.ipbias) { + value = 'ipbias'; + } + if (locationbias.point != null) { + value = + 'point:${locationbias.point.latitude},${locationbias.point.longitude}'; + } + if (locationbias.circular != null) { + value = + 'circle:${locationbias.circular.radius}@${locationbias.circular.latLng.latitude},${locationbias.circular.latLng.longitude}'; + } + if (locationbias.rectangular != null) { + value = + 'rectangle:${locationbias.rectangular.southWest.latitude},${locationbias.rectangular.southWest.longitude}|${locationbias.rectangular.northEast.latitude},${locationbias.rectangular.northEast.longitude}'; + } + var item = { + 'locationbias': value, + }; + queryParameters.addAll(item); + } + return queryParameters; + } + + static Map createNearBySearchParameters( + apiKEY, + Location location, + int radius, + String keyword, + String language, + int minprice, + int maxprice, + String name, + bool opennow, + RankBy rankby, + String type, + String pagetoken, + ) { + Map queryParameters = { + 'location': '${location.lat},${location.lng}', + 'key': apiKEY, + 'radius': radius.toString(), + }; + + if (keyword != null && keyword != '') { + var item = { + 'keyword': keyword, + }; + queryParameters.addAll(item); + } + + if (language != null && language != '') { + var item = { + 'language': language, + }; + queryParameters.addAll(item); + } + + if (minprice != null) { + var item = { + 'minprice': minprice.toString(), + }; + queryParameters.addAll(item); + } + + if (maxprice != null) { + var item = { + 'maxprice': maxprice.toString(), + }; + queryParameters.addAll(item); + } + + if (name != null && name != '') { + var item = { + 'name': name, + }; + queryParameters.addAll(item); + } + + if (opennow != null && opennow) { + var item = { + 'opennow': 'opennow', + }; + queryParameters.addAll(item); + } + + if (rankby != null) { + String value; + if (rankby == RankBy.Prominence) { + value = 'prominence'; + } + if (rankby == RankBy.Distance) { + value = 'distance'; + } + + var item = { + 'rankby': value, + }; + queryParameters.addAll(item); + } + + if (type != null && type != '') { + var item = { + 'type': type, + }; + queryParameters.addAll(item); + } + + if (pagetoken != null && pagetoken != '') { + var item = { + 'pagetoken': pagetoken, + }; + queryParameters.addAll(item); + } + + return queryParameters; + } + + static Map createTextSearchParameters( + apiKEY, + String query, + String region, + Location location, + int radius, + String language, + int minprice, + int maxprice, + bool opennow, + String type, + String pagetoken, + ) { + String result = query.trimRight(); + result = result.trimLeft(); + Map queryParameters = { + 'query': result, + 'key': apiKEY, + }; + + if (region != null && region != '') { + var item = { + 'region': region, + }; + queryParameters.addAll(item); + } + + if (location != null) { + var item = { + 'location': '${location.lat},${location.lng}', + }; + queryParameters.addAll(item); + } + + if (radius != null) { + var item = { + 'radius': radius.toString(), + }; + queryParameters.addAll(item); + } + + if (language != null && language != '') { + var item = { + 'language': language, + }; + queryParameters.addAll(item); + } + + if (minprice != null) { + var item = { + 'minprice': minprice.toString(), + }; + queryParameters.addAll(item); + } + + if (maxprice != null) { + var item = { + 'maxprice': maxprice.toString(), + }; + queryParameters.addAll(item); + } + + if (opennow != null && opennow) { + var item = { + 'opennow': 'opennow', + }; + queryParameters.addAll(item); + } + + if (type != null && type != '') { + var item = { + 'type': type, + }; + queryParameters.addAll(item); + } + + if (pagetoken != null && pagetoken != '') { + var item = { + 'pagetoken': pagetoken, + }; + queryParameters.addAll(item); + } + + return queryParameters; + } +} diff --git a/lib/src/search/text_search_result.dart b/lib/src/search/text_search_result.dart new file mode 100644 index 0000000..6b89bbf --- /dev/null +++ b/lib/src/search/text_search_result.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:google_place/src/models/debug_log.dart'; +import 'package:google_place/src/search/result.dart'; + +class TextSearchResult { + final String status; + final List htmlAttributions; + final String nextPageToken; + final DebugLog debugLog; + final List results; + + TextSearchResult({ + this.status, + this.htmlAttributions, + this.nextPageToken, + this.debugLog, + this.results, + }); + + factory TextSearchResult.fromJson(Map json) { + return TextSearchResult( + status: json['status'], + htmlAttributions: json['html_attributions'] != null + ? (json['html_attributions'] as List).cast() + : null, + nextPageToken: json['next_page_token'], + debugLog: json['debug_log'] != null + ? DebugLog.fromJson(json['debug_log']) + : null, + results: json['results'] != null + ? json['results'] + .map((json) => Result.fromJson(json)) + .toList() + : null, + ); + } + + static TextSearchResult parseTextSearchResult(String responseBody) { + final parsed = json.decode(responseBody).cast(); + return TextSearchResult.fromJson(parsed); + } +} diff --git a/lib/src/utils/network_utility.dart b/lib/src/utils/network_utility.dart new file mode 100644 index 0000000..84acaca --- /dev/null +++ b/lib/src/utils/network_utility.dart @@ -0,0 +1,14 @@ +import 'package:http/http.dart' as http; + +class NetworkUtility { + static Future fetchUrl( + Uri uri, { + Map headers, + }) async { + final response = await http.get(uri, headers: headers); + if (response.statusCode == 200) { + return response.body; + } + return null; + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..958cd68 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,202 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.11" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0+4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.6" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.5" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.15" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.6" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" +sdks: + dart: ">=2.4.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..8528af6 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,19 @@ +name: google_place +description: A new Flutter package for handle google place api +version: 0.0.1 +author: Mohammad Bazrafkan +homepage: https://github.com/bazrafkan/google_place + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + http: ^0.12.0+4 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: diff --git a/test/google_place_test.dart b/test/google_place_test.dart new file mode 100644 index 0000000..7227d76 --- /dev/null +++ b/test/google_place_test.dart @@ -0,0 +1,15 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_place/google_place.dart'; + +void main() { + test('init', () async { + String apiKey = "Your-Key"; + var googlePlace = GooglePlace(apiKey); + expect(googlePlace.apiKEY, apiKey); + expect(googlePlace.search.apiKEY, apiKey); + expect(googlePlace.details.apiKEY, apiKey); + expect(googlePlace.photos.apiKEY, apiKey); + expect(googlePlace.autocomplete.apiKEY, apiKey); + expect(googlePlace.queryAutocomplete.apiKEY, apiKey); + }); +}