├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── flutter_build.yml ├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── AUTHORS ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── assets └── images │ ├── marker_a.png │ └── marker_b.png ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ └── images │ │ └── map-marker-warehouse.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.yaml └── web │ └── index.html ├── lib ├── flutter_google_maps.dart └── src │ ├── core │ ├── google_map.dart │ ├── google_map.state.dart │ ├── map_items.dart │ ├── map_operations.dart │ ├── map_preferences.dart │ └── utils.dart │ ├── mobile │ ├── google_map.state.dart │ └── utils.dart │ └── web │ ├── google_map.state.dart │ └── utils.dart ├── publish_commands.txt ├── pubspec.yaml └── test └── flutter_google_maps_test.dart /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: I have a problem with my Flutter application. 3 | about: You are writing an application with Flutter but the application is crashing 4 | or throws an exception, a widget is buggy, or something looks wrong. 5 | title: '' 6 | labels: '' 7 | assignees: '' 8 | 9 | --- 10 | 11 | 23 | 24 | ## Steps to Reproduce 25 | 26 | 27 | 28 | 1. Run `flutter create bug`. 29 | 2. Update the files as follows: ... 30 | 3. ... 31 | 32 | **Expected results:** 33 | 34 | **Actual results:** 35 | 36 |
37 | Logs 38 | 39 | 45 | 46 | ``` 47 | ``` 48 | 49 | 53 | 54 | ``` 55 | ``` 56 | 57 | 58 | 59 | ``` 60 | ``` 61 | 62 |
63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new idea for Flutter. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 22 | 23 | ## Use case 24 | 25 | 35 | 36 | ## Proposal 37 | 38 | 47 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | *Replace this paragraph with a description of what this PR is doing. If you're modifying existing behavior, describe the existing behavior, how this PR is changing it, and what motivated the change. If you're changing visual properties, consider including before/after screenshots (and runnable code snippets to reproduce them).* 4 | 5 | ## Related Issues 6 | 7 | *Replace this paragraph with a list of issues related to this PR from our issue database. Indicate, which of these issues are resolved or fixed by this PR. There should be at least one issue listed here.* 8 | 9 | ## Tests 10 | 11 | I added the following tests: 12 | 13 | *Replace this with a list of the tests that you added as part of this PR. A change in behaviour with no test covering it 14 | will likely get reverted accidentally sooner or later. PRs must include tests for all changed/updated/fixed behaviors. See [Test Coverage].* 15 | 16 | ## Checklist 17 | 18 | Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (`[x]`). This will ensure a smooth and quick review process. 19 | 20 | - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. 21 | - [ ] I updated/added relevant documentation (doc comments with `///`). 22 | - [ ] All existing and new tests are passing. 23 | - [ ] The analyzer (`flutter analyze --flutter-repo`) does not report any problems on my PR. 24 | - [ ] I am willing to follow-up on review comments in a timely manner. 25 | 26 | ## Breaking Change 27 | 28 | Did any tests fail when you ran them? 29 | 30 | - [ ] No, no existing tests failed, so this is *not* a breaking change. 31 | - [ ] Yes, this is a breaking change. *If not, delete the remainder of this section.* 32 | - [ ] I wrote a design doc: https://flutter.dev/go/template *Replace this with a link to your design doc's short link* 33 | - [ ] I got input from the developer relations team, specifically from: *Replace with the names of who gave advice* 34 | - [ ] I wrote a migration guide: *Replace with a link to your migration guide* 35 | 36 | 37 | [Test Coverage]: https://github.com/flutter/flutter/wiki/Test-coverage-for-package%3Aflutter 38 | [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo 39 | [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement 40 | -------------------------------------------------------------------------------- /.github/workflows/flutter_build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: subosito/flutter-action@v1 17 | with: 18 | channel: 'beta' 19 | - run: flutter pub get 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | *.lock 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | 43 | # iOS/XCode related 44 | **/ios/**/*.mode1v3 45 | **/ios/**/*.mode2v3 46 | **/ios/**/*.moved-aside 47 | **/ios/**/*.pbxuser 48 | **/ios/**/*.perspectivev3 49 | **/ios/**/*sync/ 50 | **/ios/**/.sconsign.dblite 51 | **/ios/**/.tags* 52 | **/ios/**/.vagrant/ 53 | **/ios/**/DerivedData/ 54 | **/ios/**/Icon? 55 | **/ios/**/Pods/ 56 | **/ios/**/.symlinks/ 57 | **/ios/**/profile 58 | **/ios/**/xcuserdata 59 | **/ios/.generated/ 60 | **/ios/Flutter/App.framework 61 | **/ios/Flutter/Flutter.framework 62 | **/ios/Flutter/Flutter.podspec 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 77 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2b4b6eef5d361b4114ee4198aa15075890717440 8 | channel: unknown 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the MarchDev Toolkit projects. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | MarchDev Toolkit 7 | - Oleh Marchenko 8 | - Elena Marchenko -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 4.0.1 4 | 5 | * TypeError: this.widget.markers is not iterable (thanks to [slovnicki](https://github.com/slovnicki)) 6 | 7 | ## 4.0.0 8 | 9 | * Added Circles support (thanks to [jan-pavlovsky](https://github.com/jan-pavlovsky)) 10 | 11 | ## 3.8.0 12 | 13 | * Added moveCameraBounds (old moveCamera), moveCamera (with zooming ability) and zoomCamera (thanks to [travisjayday](https://github.com/travisjayday) for zoomControlsEnabled on mobile) 14 | * Added center getter for center coordinates of the map 15 | * Fixed issue when onTap/onLongPress was not specified 16 | 17 | ## 3.7.1 18 | 19 | * Fixed bug when app crash, when there's no any marker on mobile map (thanks to [Chojecki](https://github.com/Chojecki)) 20 | 21 | ## 3.7.0 22 | 23 | * Changed onTap VoidCallback to onTap ValueChanged with marker ID as an argument 24 | * Added onTap for polygon with polygon ID as an argument 25 | * Added option for marker icon to be a path to an asset as well as to be a ByteString 26 | 27 | ### BREAKING CHANGE 28 | * addMarker now is addMarkerRaw 29 | * addMarker now accepts instance of a Marker object 30 | 31 | ## 3.6.0 32 | 33 | * Added infoSnippet as an argument to addMarker method 34 | 35 | ## 3.5.0 36 | 37 | * Fixed animateCamera issue and possible changeMapStyle issue 38 | 39 | ## 3.4.0 40 | 41 | * Added rotate/scroll/tilt/zoom gestures to mobile map preferences 42 | * Added map callbacks onTap, onLongPress 43 | * Added onInfoWindowTap for addMarker method on web 44 | 45 | ## 3.3.0 46 | 47 | * Added onInfoWindowTap for addMarker method (works only on Android/iOS) 48 | * Added map iteractivity setup 49 | 50 | ## 3.2.0 51 | 52 | * Added onTap VoidCallback for addMarker method 53 | 54 | ## 3.1.0 55 | 56 | * Added gesture boolean to WebMapPreferences, that indicates whether to enable gestures or not 57 | 58 | ## 3.0.0 59 | 60 | * Implemented setting up Map Style 61 | 62 | ## 2.0.0 63 | 64 | * Fixed roadmap/normal Map Type 65 | 66 | ## 1.1.0 67 | 68 | * Update docs 69 | 70 | ## 1.0.0 71 | 72 | * Created wrapper for Mobile map and Web map 73 | * Created common interface 74 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at eo.march.dev+support@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md and CHANGELOG.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of one other developer, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, march.dev 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_google_maps 2 | 3 | ![Build](https://github.com/marchdev-tk/flutter_google_maps/workflows/build/badge.svg) 4 | [![Pub](https://img.shields.io/pub/v/flutter_google_maps.svg)](https://pub.dartlang.org/packages/flutter_google_maps) 5 | ![GitHub](https://img.shields.io/github/license/marchdev-tk/flutter_google_maps) 6 | ![GitHub stars](https://img.shields.io/github/stars/marchdev-tk/flutter_google_maps?style=social) 7 | 8 | A Flutter plugin for integrating Google Maps in iOS, Android and Web applications. It is a wrapper of google_maps_flutter for Mobile and google_maps for Web. 9 | 10 | ## Getting Started 11 | 12 | * Get an API key at . 13 | 14 | * Enable Google Map SDK for each platform. 15 | * Go to [Google Developers Console](https://console.cloud.google.com/). 16 | * Choose the project that you want to enable Google Maps on. 17 | * Select the navigation menu and then select "Google Maps". 18 | * Select "APIs" under the Google Maps menu. 19 | * To enable Google Maps for Android, select "Maps SDK for Android" in the "Additional APIs" section, then select "ENABLE". 20 | * To enable Google Maps for iOS, select "Maps SDK for iOS" in the "Additional APIs" section, then select "ENABLE". 21 | * Make sure the APIs you enabled are under the "Enabled APIs" section. 22 | 23 | * You can also find detailed steps to get start with Google Maps Platform [here](https://developers.google.com/maps/gmp-get-started). 24 | 25 | ### Web 26 | 27 | ```html 28 | 29 | 30 | 31 | 32 | ``` 33 | 34 | ### Android 35 | 36 | Specify your API key in the application manifest `android/app/src/main/AndroidManifest.xml`: 37 | 38 | ```xml 39 | 43 | ``` 44 | 45 | ### iOS 46 | 47 | Specify your API key in the application delegate `ios/Runner/AppDelegate.m`: 48 | 49 | ```objectivec 50 | #include "AppDelegate.h" 51 | #include "GeneratedPluginRegistrant.h" 52 | #import "GoogleMaps/GoogleMaps.h" 53 | 54 | @implementation AppDelegate 55 | 56 | - (BOOL)application:(UIApplication *)application 57 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 58 | [GMSServices provideAPIKey:@"YOUR KEY HERE"]; 59 | [GeneratedPluginRegistrant registerWithRegistry:self]; 60 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 61 | } 62 | @end 63 | ``` 64 | 65 | Or in your swift code, specify your API key in the application delegate `ios/Runner/AppDelegate.swift`: 66 | 67 | ```swift 68 | import UIKit 69 | import Flutter 70 | import GoogleMaps 71 | 72 | @UIApplicationMain 73 | @objc class AppDelegate: FlutterAppDelegate { 74 | override func application( 75 | _ application: UIApplication, 76 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 77 | ) -> Bool { 78 | GMSServices.provideAPIKey("YOUR KEY HERE") 79 | GeneratedPluginRegistrant.register(with: self) 80 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 81 | } 82 | } 83 | ``` 84 | Opt-in to the embedded views preview by adding a boolean property to the app's `Info.plist` file 85 | with the key `io.flutter.embedded_views_preview` and the value `YES`. 86 | 87 | #### Android/iOS Directions API 88 | 89 | Add in your `main.dart` within `main` function `GoogleMap.init('API_KEY');` before running the app. 90 | 91 | ```dart 92 | void main() { 93 | GoogleMap.init('API_KEY'); 94 | WidgetsFlutterBinding.ensureInitialized(); 95 | runApp(MyApp()); 96 | } 97 | ``` 98 | 99 | For more info about **mobile** map setup, view [google_maps_flutter](https://pub.dev/packages/google_maps_flutter) plugin. 100 | 101 | ### Add GoogleMap Widget 102 | 103 | ```dart 104 | import 'package:flutter/material.dart'; 105 | import 'package:flutter_google_maps/flutter_google_maps.dart'; 106 | 107 | ... 108 | GlobalKey _key = GlobalKey(); 109 | 110 | @override 111 | Widget build(BuildContext context) => GoogleMap( 112 | key: _key, 113 | ), 114 | ... 115 | ``` 116 | 117 | And now you're ready to go. 118 | 119 | ## Examples 120 | 121 | ### GoogleMap widget can be configured with: 122 | 123 | | Property | Type | Description | 124 | | :---------------: | :--------------------: | :------------------------------------------------------------------------------: | 125 | | initialPosition | GeoCoord | The initial position of the map's camera | 126 | | initialZoom | double | The initial zoom of the map's camera | 127 | | mapType | MapType | Type of map tiles to be rendered | 128 | | minZoom | double | The preferred minimum zoom level or null, if unbounded from below | 129 | | maxZoom | double | The preferred maximum zoom level or null, if unbounded from above | 130 | | mapStyle | String | Sets the styling of the base map | 131 | | mobilePreferences | MobileMapPreferences | Set of mobile map preferences | 132 | | webPreferences | WebMapPreferences | Set of web map preferences | 133 | | interactive | bool | Defines whether map is interactive or not | 134 | | onTap | ValueChanged | Called every time a GoogleMap is tapped | 135 | | onLongPress | ValueChanged | Called every time a GoogleMap is long pressed (for web when right mouse clicked) | 136 | | markers | Set | Markers to be placed on the map | 137 | 138 | **`MapType` is one of following variants:** 139 | 140 | * `none` -> do not display map tiles 141 | * `roadmap` -> normal tiles (traffic and labels, subtle terrain information) 142 | * `satellite` -> satellite imaging tiles (aerial photos) 143 | * `terrain` -> terrain tiles (indicates type and height of terrain) 144 | * `hybrid` -> hybrid tiles (satellite images with some labels/overlays) 145 | 146 | **`MobileMapPreferences` can be configured with:** 147 | 148 | | Property | Type | Description | 149 | | :----------------------: | :--------: | :--------------------------------------------------------------------------------: | 150 | | compassEnabled | bool | True if the map should show a compass when rotated | 151 | | mapToolbarEnabled | bool | True if the map should show a toolbar when you interact with the map. Android only | 152 | | myLocationEnabled | bool | True if a "My Location" layer should be shown on the map | 153 | | myLocationButtonEnabled | bool | Enables or disables the my-location button | 154 | | indoorViewEnabled | bool | Enables or disables the indoor view from the map | 155 | | trafficEnabled | bool | Enables or disables the traffic layer of the map | 156 | | buildingsEnabled | bool | Enables or disables showing 3D buildings where available | 157 | | padding | EdgeInsets | Padding to be set on mapdetails | 158 | | rotateGesturesEnabled | bool | True if the map view should respond to rotate gestures | 159 | | scrollGesturesEnabled | bool | True if the map view should respond to scroll gestures | 160 | | zoomGesturesEnabled | bool | True if the map view should respond to zoom gestures | 161 | | tiltGesturesEnabled | bool | True if the map view should respond to tilt gestures | 162 | 163 | **`WebMapPreferences` can be configured with:** 164 | 165 | | Property | Type | Description | 166 | | :----------------: | :--: | :---------------------------------------: | 167 | | streetViewControl | bool | Enables or disables streetViewControl | 168 | | fullscreenControl | bool | Enables or disables fullscreenControl | 169 | | mapTypeControl | bool | Enables or disables mapTypeControl | 170 | | scrollwheel | bool | Enables or disables scrollwheel | 171 | | panControl | bool | Enables or disables panControl | 172 | | overviewMapControl | bool | Enables or disables overviewMapControl | 173 | | rotateControl | bool | Enables or disables rotateControl | 174 | | scaleControl | bool | Enables or disables scaleControl | 175 | | zoomControl | bool | Enables or disables zoomControl | 176 | | dragGestures | bool | Enables or disables flutter drag gestures | 177 | 178 | ### To prepare for interacting with GoogleMap you will need to: 179 | 180 | Create a `key` and assign it to the `GoogleMap` widget. 181 | 182 | ### GoogleMap widget has 2 static methods, they are: 183 | 184 | * MapOperations of(GlobalKey key); 185 | 186 | Gets [MapOperations] interface via provided `key` of [GoogleMapStateBase] state. 187 | 188 | 189 | * void init(String apiKey); 190 | 191 | Initializer of [GoogleMap]. `Required` if `Directions API` will be needed. For other cases, could be ignored. 192 | 193 | 194 | ### To interact with GoogleMap you'll need to: 195 | 196 | **Use static `of` method** 197 | 198 | Here's list of interactions: 199 | 200 | * Move camera to the new bounds 201 | ```dart 202 | void moveCameraBounds( 203 | GeoCoordBounds newBounds, { 204 | double padding = 0, 205 | bool animated = true, 206 | bool waitUntilReady = true, 207 | }); 208 | ``` 209 | 210 | * Move camera to the new coordinates 211 | ```dart 212 | void moveCamera( 213 | GeoCoord latLng, { 214 | bool animated = true, 215 | bool waitUntilReady = true, 216 | double zoom, 217 | }); 218 | ``` 219 | 220 | * Zoom camera 221 | ```dart 222 | void zoomCamera( 223 | double zoom, { 224 | bool animated = true, 225 | bool waitUntilReady = true, 226 | }); 227 | ``` 228 | 229 | * Get center coordinates of the map 230 | ```dart 231 | FutureOr get center; 232 | ``` 233 | 234 | * Change Map Style. 235 | 236 | The style string can be generated using [map style tool](https://mapstyle.withgoogle.com/). 237 | Also, refer [iOS](https://developers.google.com/maps/documentation/ios-sdk/style-reference) 238 | and [Android](https://developers.google.com/maps/documentation/android-sdk/style-reference) 239 | style reference for more information regarding the supported styles. 240 | 241 | ```dart 242 | void changeMapStyle(String mapStyle); 243 | ``` 244 | 245 | * Add marker to the map by given [position] 246 | ```dart 247 | void addMarkerRaw( 248 | GeoCoord position, { 249 | String label, 250 | String icon, 251 | String info, 252 | ValueChanged onTap, 253 | VOidCallback onInfoWindowTap, 254 | }); 255 | ``` 256 | **Please note:** [icon] could be a *path to an image asset* or it could be an instance of *ByteString*. 257 | 258 | * Add marker to the map by given [marker] object 259 | ```dart 260 | void addMarker(Marker marker); 261 | ``` 262 | 263 | * Remove marker from the map by given [position] 264 | ```dart 265 | void removeMarker(GeoCoord position); 266 | ``` 267 | 268 | * Remove all markers from the map 269 | ```dart 270 | void clearMarkers(); 271 | ``` 272 | 273 | * Add direction to the map by given [origin] and [destination] coordinates 274 | ```dart 275 | void addDirection( 276 | dynamic origin, 277 | dynamic destination, { 278 | String startLabel, 279 | String startIcon, 280 | String startInfo, 281 | String endLabel, 282 | String endIcon, 283 | String endInfo, 284 | }); 285 | ``` 286 | 287 | * Remove direction from the map by given [origin] and [destination] coordinates 288 | ```dart 289 | void removeDirection(dynamic origin, dynamic destination); 290 | ``` 291 | 292 | * Remove all directions from the map 293 | ```dart 294 | void clearDirections(); 295 | ``` 296 | 297 | * Add polygon to the map by given [id] and [points] 298 | ```dart 299 | void addPolygon( 300 | String id, 301 | Iterable points, { 302 | ValueChanged onTap, 303 | Color strokeColor = const Color(0x000000), 304 | double strokeOpacity = 0.8, 305 | double strokeWidth = 1, 306 | Color fillColor = const Color(0x000000), 307 | double fillOpacity = 0.35, 308 | }); 309 | ``` 310 | 311 | * Edit polygon on the map by given [id] and [points] 312 | ```dart 313 | void editPolygon( 314 | String id, 315 | Iterable points, { 316 | ValueChanged onTap, 317 | Color strokeColor = const Color(0x000000), 318 | double strokeOpacity = 0.8, 319 | double strokeWeight = 1, 320 | Color fillColor = const Color(0x000000), 321 | double fillOpacity = 0.35, 322 | }); 323 | ``` 324 | 325 | * Remove polygon from the map by given [id]. 326 | ```dart 327 | void removePolygon(String id); 328 | ``` 329 | 330 | * Remove all polygones from the map. 331 | ```dart 332 | void clearPolygons(); 333 | ``` 334 | 335 | ## Feature requests and Bug reports 336 | 337 | Feel free to post a feature requests or report a bug [here](https://github.com/marchdev-tk/flutter_google_maps/issues). 338 | 339 | ## TODO 340 | 341 | * Add circles support 342 | * Add polyline support 343 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.1.8.0.yaml 2 | 3 | analyzer: 4 | errors: 5 | undefined_prefixed_name: ignore 6 | -------------------------------------------------------------------------------- /assets/images/marker_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/assets/images/marker_a.png -------------------------------------------------------------------------------- /assets/images/marker_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/assets/images/marker_b.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | *.lock 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Exceptions to above rules. 38 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 39 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2b4b6eef5d361b4114ee4198aa15075890717440 8 | channel: unknown 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_google_maps_example 2 | 3 | Demonstrates how to use the flutter_google_maps package. 4 | 5 | ## Usage 6 | 7 | ```dart 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/foundation.dart'; 10 | import 'package:flutter_google_maps/flutter_google_maps.dart'; 11 | 12 | void main() { 13 | GoogleMap.init('API_KEY'); 14 | WidgetsFlutterBinding.ensureInitialized(); 15 | runApp(MyApp()); 16 | } 17 | 18 | class MyApp extends StatelessWidget { 19 | @override 20 | Widget build(BuildContext context) { 21 | return MaterialApp( 22 | title: 'Google Map Demo', 23 | theme: ThemeData( 24 | primarySwatch: Colors.blue, 25 | ), 26 | home: MyHomePage(), 27 | ); 28 | } 29 | } 30 | 31 | class MyHomePage extends StatefulWidget { 32 | @override 33 | _MyHomePageState createState() => _MyHomePageState(); 34 | } 35 | 36 | class _MyHomePageState extends State { 37 | final _scaffoldKey = GlobalKey(); 38 | final _key = GlobalKey(); 39 | bool _polygonAdded = false; 40 | bool _darkMapStyle = false; 41 | String _mapStyle; 42 | 43 | List _buildClearButtons() => [ 44 | RaisedButton.icon( 45 | color: Colors.red, 46 | textColor: Colors.white, 47 | icon: Icon(Icons.bubble_chart), 48 | label: Text('CLEAR POLYGONS'), 49 | onPressed: () { 50 | GoogleMap.of(_key).clearPolygons(); 51 | setState(() => _polygonAdded = false); 52 | }, 53 | ), 54 | const SizedBox(width: 16), 55 | RaisedButton.icon( 56 | color: Colors.red, 57 | textColor: Colors.white, 58 | icon: Icon(Icons.pin_drop), 59 | label: Text('CLEAR MARKERS'), 60 | onPressed: () { 61 | GoogleMap.of(_key).clearMarkers(); 62 | }, 63 | ), 64 | const SizedBox(width: 16), 65 | RaisedButton.icon( 66 | color: Colors.red, 67 | textColor: Colors.white, 68 | icon: Icon(Icons.directions), 69 | label: Text('CLEAR DIRECTIONS'), 70 | onPressed: () { 71 | GoogleMap.of(_key).clearDirections(); 72 | }, 73 | ), 74 | ]; 75 | 76 | List _buildAddButtons() => [ 77 | FloatingActionButton( 78 | child: Icon(_polygonAdded ? Icons.edit : Icons.bubble_chart), 79 | backgroundColor: _polygonAdded ? Colors.orange : null, 80 | onPressed: () { 81 | if (!_polygonAdded) { 82 | GoogleMap.of(_key).addPolygon( 83 | '1', 84 | polygon, 85 | onTap: (polygonId) async { 86 | await showDialog( 87 | context: context, 88 | builder: (context) => AlertDialog( 89 | content: Text( 90 | 'This dialog was opened by tapping on the polygon!\n' 91 | 'Polygon ID is $polygonId', 92 | ), 93 | actions: [ 94 | FlatButton( 95 | onPressed: Navigator.of(context).pop, 96 | child: Text('CLOSE'), 97 | ), 98 | ], 99 | ), 100 | ); 101 | }, 102 | ); 103 | } else { 104 | GoogleMap.of(_key).editPolygon( 105 | '1', 106 | polygon, 107 | fillColor: Colors.purple, 108 | strokeColor: Colors.purple, 109 | ); 110 | } 111 | 112 | setState(() => _polygonAdded = true); 113 | }, 114 | ), 115 | const SizedBox(width: 16), 116 | FloatingActionButton( 117 | child: Icon(Icons.pin_drop), 118 | onPressed: () { 119 | GoogleMap.of(_key).addMarkerRaw( 120 | GeoCoord(33.875513, -117.550257), 121 | info: 'test info', 122 | onInfoWindowTap: () async { 123 | await showDialog( 124 | context: context, 125 | builder: (context) => AlertDialog( 126 | content: Text( 127 | 'This dialog was opened by tapping on the InfoWindow!'), 128 | actions: [ 129 | FlatButton( 130 | onPressed: Navigator.of(context).pop, 131 | child: Text('CLOSE'), 132 | ), 133 | ], 134 | ), 135 | ); 136 | }, 137 | ); 138 | GoogleMap.of(_key).addMarkerRaw( 139 | GeoCoord(33.775513, -117.450257), 140 | icon: 'assets/images/map-marker-warehouse.png', 141 | info: contentString, 142 | ); 143 | }, 144 | ), 145 | const SizedBox(width: 16), 146 | FloatingActionButton( 147 | child: Icon(Icons.directions), 148 | onPressed: () { 149 | GoogleMap.of(_key).addDirection( 150 | 'San Francisco, CA', 151 | 'San Jose, CA', 152 | startLabel: '1', 153 | startInfo: 'San Francisco, CA', 154 | endIcon: 'assets/images/map-marker-warehouse.png', 155 | endInfo: 'San Jose, CA', 156 | ); 157 | }, 158 | ), 159 | ]; 160 | 161 | @override 162 | Widget build(BuildContext context) => Scaffold( 163 | key: _scaffoldKey, 164 | appBar: AppBar( 165 | title: Text('Google Map'), 166 | ), 167 | body: Stack( 168 | children: [ 169 | Positioned.fill( 170 | child: GoogleMap( 171 | key: _key, 172 | markers: { 173 | Marker( 174 | GeoCoord(34.0469058, -118.3503948), 175 | ), 176 | }, 177 | initialZoom: 12, 178 | initialPosition: 179 | GeoCoord(34.0469058, -118.3503948), // Los Angeles, CA 180 | mapType: MapType.roadmap, 181 | mapStyle: _mapStyle, 182 | interactive: true, 183 | onTap: (coord) => 184 | _scaffoldKey.currentState.showSnackBar(SnackBar( 185 | content: Text(coord?.toString()), 186 | duration: const Duration(seconds: 2), 187 | )), 188 | mobilePreferences: const MobileMapPreferences( 189 | trafficEnabled: true, 190 | zoomControlsEnabled: false, 191 | ), 192 | webPreferences: WebMapPreferences( 193 | fullscreenControl: true, 194 | zoomControl: true, 195 | ), 196 | ), 197 | ), 198 | Positioned( 199 | top: 16, 200 | left: 16, 201 | child: FloatingActionButton( 202 | child: Icon(Icons.person_pin_circle), 203 | onPressed: () { 204 | final bounds = GeoCoordBounds( 205 | northeast: GeoCoord(34.021307, -117.432317), 206 | southwest: GeoCoord(33.835745, -117.712785), 207 | ); 208 | GoogleMap.of(_key).moveCameraBounds(bounds); 209 | GoogleMap.of(_key).addMarkerRaw( 210 | GeoCoord( 211 | (bounds.northeast.latitude + bounds.southwest.latitude) / 212 | 2, 213 | (bounds.northeast.longitude + 214 | bounds.southwest.longitude) / 215 | 2, 216 | ), 217 | onTap: (markerId) async { 218 | await showDialog( 219 | context: context, 220 | builder: (context) => AlertDialog( 221 | content: Text( 222 | 'This dialog was opened by tapping on the marker!\n' 223 | 'Marker ID is $markerId', 224 | ), 225 | actions: [ 226 | FlatButton( 227 | onPressed: Navigator.of(context).pop, 228 | child: Text('CLOSE'), 229 | ), 230 | ], 231 | ), 232 | ); 233 | }, 234 | ); 235 | }, 236 | ), 237 | ), 238 | Positioned( 239 | top: 16, 240 | right: kIsWeb ? 60 : 16, 241 | child: FloatingActionButton( 242 | onPressed: () { 243 | if (_darkMapStyle) { 244 | GoogleMap.of(_key).changeMapStyle(null); 245 | _mapStyle = null; 246 | } else { 247 | GoogleMap.of(_key).changeMapStyle(darkMapStyle); 248 | _mapStyle = darkMapStyle; 249 | } 250 | 251 | setState(() => _darkMapStyle = !_darkMapStyle); 252 | }, 253 | backgroundColor: _darkMapStyle ? Colors.black : Colors.white, 254 | child: Icon( 255 | _darkMapStyle ? Icons.wb_sunny : Icons.brightness_3, 256 | color: _darkMapStyle ? Colors.white : Colors.black, 257 | ), 258 | ), 259 | ), 260 | Positioned( 261 | left: 16, 262 | right: kIsWeb ? 60 : 16, 263 | bottom: 16, 264 | child: Row( 265 | children: [ 266 | LayoutBuilder( 267 | builder: (context, constraints) => 268 | constraints.maxWidth < 1000 269 | ? Row(children: _buildClearButtons()) 270 | : Column( 271 | crossAxisAlignment: CrossAxisAlignment.start, 272 | children: _buildClearButtons(), 273 | ), 274 | ), 275 | Spacer(), 276 | ..._buildAddButtons(), 277 | ], 278 | ), 279 | ), 280 | ], 281 | ), 282 | ); 283 | } 284 | 285 | const darkMapStyle = r''' 286 | [ 287 | { 288 | "elementType": "geometry", 289 | "stylers": [ 290 | { 291 | "color": "#212121" 292 | } 293 | ] 294 | }, 295 | { 296 | "elementType": "labels.icon", 297 | "stylers": [ 298 | { 299 | "visibility": "off" 300 | } 301 | ] 302 | }, 303 | { 304 | "elementType": "labels.text.fill", 305 | "stylers": [ 306 | { 307 | "color": "#757575" 308 | } 309 | ] 310 | }, 311 | { 312 | "elementType": "labels.text.stroke", 313 | "stylers": [ 314 | { 315 | "color": "#212121" 316 | } 317 | ] 318 | }, 319 | { 320 | "featureType": "administrative", 321 | "elementType": "geometry", 322 | "stylers": [ 323 | { 324 | "color": "#757575" 325 | } 326 | ] 327 | }, 328 | { 329 | "featureType": "administrative.country", 330 | "elementType": "labels.text.fill", 331 | "stylers": [ 332 | { 333 | "color": "#9e9e9e" 334 | } 335 | ] 336 | }, 337 | { 338 | "featureType": "administrative.land_parcel", 339 | "stylers": [ 340 | { 341 | "visibility": "off" 342 | } 343 | ] 344 | }, 345 | { 346 | "featureType": "administrative.locality", 347 | "elementType": "labels.text.fill", 348 | "stylers": [ 349 | { 350 | "color": "#bdbdbd" 351 | } 352 | ] 353 | }, 354 | { 355 | "featureType": "poi", 356 | "elementType": "labels.text.fill", 357 | "stylers": [ 358 | { 359 | "color": "#757575" 360 | } 361 | ] 362 | }, 363 | { 364 | "featureType": "poi.park", 365 | "elementType": "geometry", 366 | "stylers": [ 367 | { 368 | "color": "#181818" 369 | } 370 | ] 371 | }, 372 | { 373 | "featureType": "poi.park", 374 | "elementType": "labels.text.fill", 375 | "stylers": [ 376 | { 377 | "color": "#616161" 378 | } 379 | ] 380 | }, 381 | { 382 | "featureType": "poi.park", 383 | "elementType": "labels.text.stroke", 384 | "stylers": [ 385 | { 386 | "color": "#1b1b1b" 387 | } 388 | ] 389 | }, 390 | { 391 | "featureType": "road", 392 | "elementType": "geometry.fill", 393 | "stylers": [ 394 | { 395 | "color": "#2c2c2c" 396 | } 397 | ] 398 | }, 399 | { 400 | "featureType": "road", 401 | "elementType": "labels.text.fill", 402 | "stylers": [ 403 | { 404 | "color": "#8a8a8a" 405 | } 406 | ] 407 | }, 408 | { 409 | "featureType": "road.arterial", 410 | "elementType": "geometry", 411 | "stylers": [ 412 | { 413 | "color": "#373737" 414 | } 415 | ] 416 | }, 417 | { 418 | "featureType": "road.highway", 419 | "elementType": "geometry", 420 | "stylers": [ 421 | { 422 | "color": "#3c3c3c" 423 | } 424 | ] 425 | }, 426 | { 427 | "featureType": "road.highway.controlled_access", 428 | "elementType": "geometry", 429 | "stylers": [ 430 | { 431 | "color": "#4e4e4e" 432 | } 433 | ] 434 | }, 435 | { 436 | "featureType": "road.local", 437 | "elementType": "labels.text.fill", 438 | "stylers": [ 439 | { 440 | "color": "#616161" 441 | } 442 | ] 443 | }, 444 | { 445 | "featureType": "transit", 446 | "elementType": "labels.text.fill", 447 | "stylers": [ 448 | { 449 | "color": "#757575" 450 | } 451 | ] 452 | }, 453 | { 454 | "featureType": "water", 455 | "elementType": "geometry", 456 | "stylers": [ 457 | { 458 | "color": "#000000" 459 | } 460 | ] 461 | }, 462 | { 463 | "featureType": "water", 464 | "elementType": "labels.text.fill", 465 | "stylers": [ 466 | { 467 | "color": "#3d3d3d" 468 | } 469 | ] 470 | } 471 | ] 472 | '''; 473 | 474 | const contentString = r''' 475 |
476 |
477 |

Uluru

478 |
479 |

480 | Uluru, also referred to as Ayers Rock, is a large 481 | sandstone rock formation in the southern part of the 482 | Northern Territory, central Australia. It lies 335 km (208 mi) 483 | south west of the nearest large town, Alice Springs; 450 km 484 | (280 mi) by road. Kata Tjuta and Uluru are the two major 485 | features of the Uluru - Kata Tjuta National Park. Uluru is 486 | sacred to the Pitjantjatjara and Yankunytjatjara, the 487 | Aboriginal people of the area. It has many springs, waterholes, 488 | rock caves and ancient paintings. Uluru is listed as a World 489 | Heritage Site. 490 |

491 |

492 | Attribution: Uluru, 493 | 494 | http://en.wikipedia.org/w/index.php?title=Uluru 495 | 496 | (last visited June 22, 2009). 497 |

498 |
499 |
500 | '''; 501 | 502 | const polygon = [ 503 | GeoCoord(32.707868, -117.191018), 504 | GeoCoord(32.705645, -117.191096), 505 | GeoCoord(32.697756, -117.166664), 506 | GeoCoord(32.686486, -117.163206), 507 | GeoCoord(32.675876, -117.169452), 508 | GeoCoord(32.674726, -117.165233), 509 | GeoCoord(32.679833, -117.158487), 510 | GeoCoord(32.677571, -117.153893), 511 | GeoCoord(32.671987, -117.160079), 512 | GeoCoord(32.667547, -117.160477), 513 | GeoCoord(32.654748, -117.147579), 514 | GeoCoord(32.651933, -117.150312), 515 | GeoCoord(32.649676, -117.144334), 516 | GeoCoord(32.631665, -117.138201), 517 | GeoCoord(32.632033, -117.132249), 518 | GeoCoord(32.630156, -117.137234), 519 | GeoCoord(32.628072, -117.136479), 520 | GeoCoord(32.630315, -117.131443), 521 | GeoCoord(32.625930, -117.135312), 522 | GeoCoord(32.623754, -117.131664), 523 | GeoCoord(32.627465, -117.130883), 524 | GeoCoord(32.622598, -117.128791), 525 | GeoCoord(32.622622, -117.133183), 526 | GeoCoord(32.618690, -117.133634), 527 | GeoCoord(32.618980, -117.128403), 528 | GeoCoord(32.609847, -117.132502), 529 | GeoCoord(32.604198, -117.125333), 530 | GeoCoord(32.588260, -117.122032), 531 | GeoCoord(32.591164, -117.116851), 532 | GeoCoord(32.587601, -117.105968), 533 | GeoCoord(32.583792, -117.104434), 534 | GeoCoord(32.570566, -117.101382), 535 | GeoCoord(32.569256, -117.122378), 536 | GeoCoord(32.560825, -117.122903), 537 | GeoCoord(32.557753, -117.131040), 538 | GeoCoord(32.542737, -117.124883), 539 | GeoCoord(32.534156, -117.126062), 540 | GeoCoord(32.563255, -117.134963), 541 | GeoCoord(32.584055, -117.134263), 542 | GeoCoord(32.619405, -117.140001), 543 | GeoCoord(32.655293, -117.157349), 544 | GeoCoord(32.669944, -117.169624), 545 | GeoCoord(32.682710, -117.189445), 546 | GeoCoord(32.685297, -117.208773), 547 | GeoCoord(32.679814, -117.224882), 548 | GeoCoord(32.697212, -117.227058), 549 | GeoCoord(32.707701, -117.219816), 550 | GeoCoord(32.711931, -117.214107), 551 | GeoCoord(32.715026, -117.196521), 552 | GeoCoord(32.713053, -117.189703), 553 | GeoCoord(32.707868, -117.191018), 554 | ]; 555 | ``` 556 | 557 | ## Getting Started 558 | 559 | For help getting started with Flutter, view our 560 | [online documentation](https://flutter.dev/docs), which offers tutorials, 561 | samples, guidance on mobile development, and a full API reference. 562 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | applicationId "marchdev.tk.flutter_google_maps_example" 41 | minSdkVersion 16 42 | targetSdkVersion 28 43 | versionCode flutterVersionCode.toInteger() 44 | versionName flutterVersionName 45 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | 60 | dependencies { 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 62 | testImplementation 'junit:junit:4.12' 63 | androidTestImplementation 'androidx.test:runner:1.1.1' 64 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 65 | } 66 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package marchdev.tk.flutter_google_maps_example 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/assets/images/map-marker-warehouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/assets/images/map-marker-warehouse.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 18 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 19 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXCopyFilesBuildPhase section */ 23 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 24 | isa = PBXCopyFilesBuildPhase; 25 | buildActionMask = 2147483647; 26 | dstPath = ""; 27 | dstSubfolderSpec = 10; 28 | files = ( 29 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 30 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 31 | ); 32 | name = "Embed Frameworks"; 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXCopyFilesBuildPhase section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 39 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 40 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 41 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 42 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 43 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 44 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 45 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 46 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 47 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 48 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 50 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 51 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 52 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 61 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 9740EEB11CF90186004384FC /* Flutter */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 3B80C3931E831B6300D905FE /* App.framework */, 72 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 73 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 74 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 75 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 76 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 77 | ); 78 | name = Flutter; 79 | sourceTree = ""; 80 | }; 81 | 97C146E51CF9000F007C117D = { 82 | isa = PBXGroup; 83 | children = ( 84 | 9740EEB11CF90186004384FC /* Flutter */, 85 | 97C146F01CF9000F007C117D /* Runner */, 86 | 97C146EF1CF9000F007C117D /* Products */, 87 | ); 88 | sourceTree = ""; 89 | }; 90 | 97C146EF1CF9000F007C117D /* Products */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 97C146EE1CF9000F007C117D /* Runner.app */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | 97C146F01CF9000F007C117D /* Runner */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 102 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 103 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 104 | 97C147021CF9000F007C117D /* Info.plist */, 105 | 97C146F11CF9000F007C117D /* Supporting Files */, 106 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 107 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 108 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 109 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 110 | ); 111 | path = Runner; 112 | sourceTree = ""; 113 | }; 114 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | ); 118 | name = "Supporting Files"; 119 | sourceTree = ""; 120 | }; 121 | /* End PBXGroup section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | 97C146ED1CF9000F007C117D /* Runner */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 127 | buildPhases = ( 128 | 9740EEB61CF901F6004384FC /* Run Script */, 129 | 97C146EA1CF9000F007C117D /* Sources */, 130 | 97C146EB1CF9000F007C117D /* Frameworks */, 131 | 97C146EC1CF9000F007C117D /* Resources */, 132 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 133 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | ); 139 | name = Runner; 140 | productName = Runner; 141 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 142 | productType = "com.apple.product-type.application"; 143 | }; 144 | /* End PBXNativeTarget section */ 145 | 146 | /* Begin PBXProject section */ 147 | 97C146E61CF9000F007C117D /* Project object */ = { 148 | isa = PBXProject; 149 | attributes = { 150 | LastUpgradeCheck = 1020; 151 | ORGANIZATIONNAME = ""; 152 | TargetAttributes = { 153 | 97C146ED1CF9000F007C117D = { 154 | CreatedOnToolsVersion = 7.3.1; 155 | LastSwiftMigration = 1100; 156 | }; 157 | }; 158 | }; 159 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 160 | compatibilityVersion = "Xcode 3.2"; 161 | developmentRegion = en; 162 | hasScannedForEncodings = 0; 163 | knownRegions = ( 164 | en, 165 | Base, 166 | ); 167 | mainGroup = 97C146E51CF9000F007C117D; 168 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | 97C146ED1CF9000F007C117D /* Runner */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXResourcesBuildPhase section */ 178 | 97C146EC1CF9000F007C117D /* Resources */ = { 179 | isa = PBXResourcesBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 183 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 184 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 185 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | /* End PBXResourcesBuildPhase section */ 190 | 191 | /* Begin PBXShellScriptBuildPhase section */ 192 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 193 | isa = PBXShellScriptBuildPhase; 194 | buildActionMask = 2147483647; 195 | files = ( 196 | ); 197 | inputPaths = ( 198 | ); 199 | name = "Thin Binary"; 200 | outputPaths = ( 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | shellPath = /bin/sh; 204 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 205 | }; 206 | 9740EEB61CF901F6004384FC /* Run Script */ = { 207 | isa = PBXShellScriptBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | inputPaths = ( 212 | ); 213 | name = "Run Script"; 214 | outputPaths = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | shellPath = /bin/sh; 218 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 219 | }; 220 | /* End PBXShellScriptBuildPhase section */ 221 | 222 | /* Begin PBXSourcesBuildPhase section */ 223 | 97C146EA1CF9000F007C117D /* Sources */ = { 224 | isa = PBXSourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 228 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | /* End PBXSourcesBuildPhase section */ 233 | 234 | /* Begin PBXVariantGroup section */ 235 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 236 | isa = PBXVariantGroup; 237 | children = ( 238 | 97C146FB1CF9000F007C117D /* Base */, 239 | ); 240 | name = Main.storyboard; 241 | sourceTree = ""; 242 | }; 243 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 244 | isa = PBXVariantGroup; 245 | children = ( 246 | 97C147001CF9000F007C117D /* Base */, 247 | ); 248 | name = LaunchScreen.storyboard; 249 | sourceTree = ""; 250 | }; 251 | /* End PBXVariantGroup section */ 252 | 253 | /* Begin XCBuildConfiguration section */ 254 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 255 | isa = XCBuildConfiguration; 256 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_ANALYZER_NONNULL = YES; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_EMPTY_BODY = YES; 271 | CLANG_WARN_ENUM_CONVERSION = YES; 272 | CLANG_WARN_INFINITE_RECURSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 276 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 279 | CLANG_WARN_STRICT_PROTOTYPES = YES; 280 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 284 | COPY_PHASE_STRIP = NO; 285 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 286 | ENABLE_NS_ASSERTIONS = NO; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu99; 289 | GCC_NO_COMMON_BLOCKS = YES; 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 297 | MTL_ENABLE_DEBUG_INFO = NO; 298 | SDKROOT = iphoneos; 299 | SUPPORTED_PLATFORMS = iphoneos; 300 | TARGETED_DEVICE_FAMILY = "1,2"; 301 | VALIDATE_PRODUCT = YES; 302 | }; 303 | name = Profile; 304 | }; 305 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 306 | isa = XCBuildConfiguration; 307 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | CLANG_ENABLE_MODULES = YES; 311 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 312 | ENABLE_BITCODE = NO; 313 | FRAMEWORK_SEARCH_PATHS = ( 314 | "$(inherited)", 315 | "$(PROJECT_DIR)/Flutter", 316 | ); 317 | INFOPLIST_FILE = Runner/Info.plist; 318 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 319 | LIBRARY_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "$(PROJECT_DIR)/Flutter", 322 | ); 323 | PRODUCT_BUNDLE_IDENTIFIER = marchdev.tk.flutter_google_maps_example; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 326 | SWIFT_VERSION = 5.0; 327 | VERSIONING_SYSTEM = "apple-generic"; 328 | }; 329 | name = Profile; 330 | }; 331 | 97C147031CF9000F007C117D /* Debug */ = { 332 | isa = XCBuildConfiguration; 333 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_ANALYZER_NONNULL = YES; 337 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 338 | CLANG_CXX_LIBRARY = "libc++"; 339 | CLANG_ENABLE_MODULES = YES; 340 | CLANG_ENABLE_OBJC_ARC = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 347 | CLANG_WARN_EMPTY_BODY = YES; 348 | CLANG_WARN_ENUM_CONVERSION = YES; 349 | CLANG_WARN_INFINITE_RECURSION = YES; 350 | CLANG_WARN_INT_CONVERSION = YES; 351 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 353 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 354 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 356 | CLANG_WARN_STRICT_PROTOTYPES = YES; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = NO; 362 | DEBUG_INFORMATION_FORMAT = dwarf; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | ENABLE_TESTABILITY = YES; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_DYNAMIC_NO_PIC = NO; 367 | GCC_NO_COMMON_BLOCKS = YES; 368 | GCC_OPTIMIZATION_LEVEL = 0; 369 | GCC_PREPROCESSOR_DEFINITIONS = ( 370 | "DEBUG=1", 371 | "$(inherited)", 372 | ); 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 380 | MTL_ENABLE_DEBUG_INFO = YES; 381 | ONLY_ACTIVE_ARCH = YES; 382 | SDKROOT = iphoneos; 383 | TARGETED_DEVICE_FAMILY = "1,2"; 384 | }; 385 | name = Debug; 386 | }; 387 | 97C147041CF9000F007C117D /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 390 | buildSettings = { 391 | ALWAYS_SEARCH_USER_PATHS = NO; 392 | CLANG_ANALYZER_NONNULL = YES; 393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 394 | CLANG_CXX_LIBRARY = "libc++"; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 398 | CLANG_WARN_BOOL_CONVERSION = YES; 399 | CLANG_WARN_COMMA = YES; 400 | CLANG_WARN_CONSTANT_CONVERSION = YES; 401 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INFINITE_RECURSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 408 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 409 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 411 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 412 | CLANG_WARN_STRICT_PROTOTYPES = YES; 413 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 414 | CLANG_WARN_UNREACHABLE_CODE = YES; 415 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 416 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 417 | COPY_PHASE_STRIP = NO; 418 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 419 | ENABLE_NS_ASSERTIONS = NO; 420 | ENABLE_STRICT_OBJC_MSGSEND = YES; 421 | GCC_C_LANGUAGE_STANDARD = gnu99; 422 | GCC_NO_COMMON_BLOCKS = YES; 423 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 424 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 425 | GCC_WARN_UNDECLARED_SELECTOR = YES; 426 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 427 | GCC_WARN_UNUSED_FUNCTION = YES; 428 | GCC_WARN_UNUSED_VARIABLE = YES; 429 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 430 | MTL_ENABLE_DEBUG_INFO = NO; 431 | SDKROOT = iphoneos; 432 | SUPPORTED_PLATFORMS = iphoneos; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 434 | TARGETED_DEVICE_FAMILY = "1,2"; 435 | VALIDATE_PRODUCT = YES; 436 | }; 437 | name = Release; 438 | }; 439 | 97C147061CF9000F007C117D /* Debug */ = { 440 | isa = XCBuildConfiguration; 441 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | CLANG_ENABLE_MODULES = YES; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | ENABLE_BITCODE = NO; 447 | FRAMEWORK_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | INFOPLIST_FILE = Runner/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 453 | LIBRARY_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "$(PROJECT_DIR)/Flutter", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = marchdev.tk.flutter_google_maps_example; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 460 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 461 | SWIFT_VERSION = 5.0; 462 | VERSIONING_SYSTEM = "apple-generic"; 463 | }; 464 | name = Debug; 465 | }; 466 | 97C147071CF9000F007C117D /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | CLANG_ENABLE_MODULES = YES; 472 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 473 | ENABLE_BITCODE = NO; 474 | FRAMEWORK_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "$(PROJECT_DIR)/Flutter", 477 | ); 478 | INFOPLIST_FILE = Runner/Info.plist; 479 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 480 | LIBRARY_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "$(PROJECT_DIR)/Flutter", 483 | ); 484 | PRODUCT_BUNDLE_IDENTIFIER = marchdev.tk.flutter_google_maps_example; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 487 | SWIFT_VERSION = 5.0; 488 | VERSIONING_SYSTEM = "apple-generic"; 489 | }; 490 | name = Release; 491 | }; 492 | /* End XCBuildConfiguration section */ 493 | 494 | /* Begin XCConfigurationList section */ 495 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 97C147031CF9000F007C117D /* Debug */, 499 | 97C147041CF9000F007C117D /* Release */, 500 | 249021D3217E4FDB00AE95B9 /* Profile */, 501 | ); 502 | defaultConfigurationIsVisible = 0; 503 | defaultConfigurationName = Release; 504 | }; 505 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 506 | isa = XCConfigurationList; 507 | buildConfigurations = ( 508 | 97C147061CF9000F007C117D /* Debug */, 509 | 97C147071CF9000F007C117D /* Release */, 510 | 249021D4217E4FDB00AE95B9 /* Profile */, 511 | ); 512 | defaultConfigurationIsVisible = 0; 513 | defaultConfigurationName = Release; 514 | }; 515 | /* End XCConfigurationList section */ 516 | }; 517 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 518 | } 519 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import GoogleMaps 4 | 5 | @UIApplicationMain 6 | @objc class AppDelegate: FlutterAppDelegate { 7 | override func application( 8 | _ application: UIApplication, 9 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 10 | ) -> Bool { 11 | GMSServices.provideAPIKey("API_KEY") 12 | GeneratedPluginRegistrant.register(with: self) 13 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marchdev-tk/flutter_google_maps/664c61cb0f64972228a8001397d5e635765d103e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_google_maps_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | io.flutter.embedded_views_preview 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter_google_maps/flutter_google_maps.dart'; 8 | 9 | void main() { 10 | GoogleMap.init('API_KEY'); 11 | WidgetsFlutterBinding.ensureInitialized(); 12 | runApp(MyApp()); 13 | } 14 | 15 | class MyApp extends StatelessWidget { 16 | @override 17 | Widget build(BuildContext context) { 18 | return MaterialApp( 19 | title: 'Google Map Demo', 20 | theme: ThemeData( 21 | primarySwatch: Colors.blue, 22 | ), 23 | home: MyHomePage(), 24 | ); 25 | } 26 | } 27 | 28 | class MyHomePage extends StatefulWidget { 29 | @override 30 | _MyHomePageState createState() => _MyHomePageState(); 31 | } 32 | 33 | class _MyHomePageState extends State { 34 | final _scaffoldKey = GlobalKey(); 35 | final _key = GlobalKey(); 36 | bool _polygonAdded = false; 37 | bool _darkMapStyle = false; 38 | String _mapStyle; 39 | 40 | List _buildClearButtons() => [ 41 | RaisedButton.icon( 42 | color: Colors.red, 43 | textColor: Colors.white, 44 | icon: Icon(Icons.bubble_chart), 45 | label: Text('CLEAR POLYGONS'), 46 | onPressed: () { 47 | GoogleMap.of(_key).clearPolygons(); 48 | setState(() => _polygonAdded = false); 49 | }, 50 | ), 51 | const SizedBox(width: 16), 52 | RaisedButton.icon( 53 | color: Colors.red, 54 | textColor: Colors.white, 55 | icon: Icon(Icons.pin_drop), 56 | label: Text('CLEAR MARKERS'), 57 | onPressed: () { 58 | GoogleMap.of(_key).clearMarkers(); 59 | }, 60 | ), 61 | const SizedBox(width: 16), 62 | RaisedButton.icon( 63 | color: Colors.red, 64 | textColor: Colors.white, 65 | icon: Icon(Icons.directions), 66 | label: Text('CLEAR DIRECTIONS'), 67 | onPressed: () { 68 | GoogleMap.of(_key).clearDirections(); 69 | }, 70 | ), 71 | ]; 72 | 73 | List _buildAddButtons() => [ 74 | FloatingActionButton( 75 | child: Icon(_polygonAdded ? Icons.edit : Icons.bubble_chart), 76 | backgroundColor: _polygonAdded ? Colors.orange : null, 77 | onPressed: () { 78 | if (!_polygonAdded) { 79 | GoogleMap.of(_key).addPolygon( 80 | '1', 81 | polygon, 82 | onTap: (polygonId) async { 83 | await showDialog( 84 | context: context, 85 | builder: (context) => AlertDialog( 86 | content: Text( 87 | 'This dialog was opened by tapping on the polygon!\n' 88 | 'Polygon ID is $polygonId', 89 | ), 90 | actions: [ 91 | FlatButton( 92 | onPressed: Navigator.of(context).pop, 93 | child: Text('CLOSE'), 94 | ), 95 | ], 96 | ), 97 | ); 98 | }, 99 | ); 100 | } else { 101 | GoogleMap.of(_key).editPolygon( 102 | '1', 103 | polygon, 104 | fillColor: Colors.purple, 105 | strokeColor: Colors.purple, 106 | ); 107 | } 108 | 109 | setState(() => _polygonAdded = true); 110 | }, 111 | ), 112 | const SizedBox(width: 16), 113 | FloatingActionButton( 114 | child: Icon(Icons.pin_drop), 115 | onPressed: () { 116 | GoogleMap.of(_key).addMarkerRaw( 117 | GeoCoord(33.875513, -117.550257), 118 | info: 'test info', 119 | onInfoWindowTap: () async { 120 | await showDialog( 121 | context: context, 122 | builder: (context) => AlertDialog( 123 | content: Text( 124 | 'This dialog was opened by tapping on the InfoWindow!'), 125 | actions: [ 126 | FlatButton( 127 | onPressed: Navigator.of(context).pop, 128 | child: Text('CLOSE'), 129 | ), 130 | ], 131 | ), 132 | ); 133 | }, 134 | ); 135 | GoogleMap.of(_key).addMarkerRaw( 136 | GeoCoord(33.775513, -117.450257), 137 | icon: 'assets/images/map-marker-warehouse.png', 138 | info: contentString, 139 | ); 140 | }, 141 | ), 142 | const SizedBox(width: 16), 143 | FloatingActionButton( 144 | child: Icon(Icons.directions), 145 | onPressed: () { 146 | GoogleMap.of(_key).addDirection( 147 | 'San Francisco, CA', 148 | 'San Jose, CA', 149 | startLabel: '1', 150 | startInfo: 'San Francisco, CA', 151 | endIcon: 'assets/images/map-marker-warehouse.png', 152 | endInfo: 'San Jose, CA', 153 | ); 154 | }, 155 | ), 156 | ]; 157 | 158 | @override 159 | Widget build(BuildContext context) => Scaffold( 160 | key: _scaffoldKey, 161 | appBar: AppBar( 162 | title: Text('Google Map'), 163 | ), 164 | body: Stack( 165 | children: [ 166 | Positioned.fill( 167 | child: GoogleMap( 168 | key: _key, 169 | markers: { 170 | Marker( 171 | GeoCoord(34.0469058, -118.3503948), 172 | ), 173 | }, 174 | initialZoom: 12, 175 | initialPosition: 176 | GeoCoord(34.0469058, -118.3503948), // Los Angeles, CA 177 | mapType: MapType.roadmap, 178 | mapStyle: _mapStyle, 179 | interactive: true, 180 | onTap: (coord) => 181 | _scaffoldKey.currentState.showSnackBar(SnackBar( 182 | content: Text(coord?.toString()), 183 | duration: const Duration(seconds: 2), 184 | )), 185 | mobilePreferences: const MobileMapPreferences( 186 | trafficEnabled: true, 187 | zoomControlsEnabled: false, 188 | ), 189 | webPreferences: WebMapPreferences( 190 | fullscreenControl: true, 191 | zoomControl: true, 192 | ), 193 | ), 194 | ), 195 | Positioned( 196 | top: 16, 197 | left: 16, 198 | child: FloatingActionButton( 199 | child: Icon(Icons.person_pin_circle), 200 | onPressed: () { 201 | final bounds = GeoCoordBounds( 202 | northeast: GeoCoord(34.021307, -117.432317), 203 | southwest: GeoCoord(33.835745, -117.712785), 204 | ); 205 | GoogleMap.of(_key).moveCameraBounds(bounds); 206 | GoogleMap.of(_key).addMarkerRaw( 207 | GeoCoord( 208 | (bounds.northeast.latitude + bounds.southwest.latitude) / 209 | 2, 210 | (bounds.northeast.longitude + 211 | bounds.southwest.longitude) / 212 | 2, 213 | ), 214 | onTap: (markerId) async { 215 | await showDialog( 216 | context: context, 217 | builder: (context) => AlertDialog( 218 | content: Text( 219 | 'This dialog was opened by tapping on the marker!\n' 220 | 'Marker ID is $markerId', 221 | ), 222 | actions: [ 223 | FlatButton( 224 | onPressed: Navigator.of(context).pop, 225 | child: Text('CLOSE'), 226 | ), 227 | ], 228 | ), 229 | ); 230 | }, 231 | ); 232 | }, 233 | ), 234 | ), 235 | Positioned( 236 | top: 16, 237 | right: kIsWeb ? 60 : 16, 238 | child: FloatingActionButton( 239 | onPressed: () { 240 | if (_darkMapStyle) { 241 | GoogleMap.of(_key).changeMapStyle(null); 242 | _mapStyle = null; 243 | } else { 244 | GoogleMap.of(_key).changeMapStyle(darkMapStyle); 245 | _mapStyle = darkMapStyle; 246 | } 247 | 248 | setState(() => _darkMapStyle = !_darkMapStyle); 249 | }, 250 | backgroundColor: _darkMapStyle ? Colors.black : Colors.white, 251 | child: Icon( 252 | _darkMapStyle ? Icons.wb_sunny : Icons.brightness_3, 253 | color: _darkMapStyle ? Colors.white : Colors.black, 254 | ), 255 | ), 256 | ), 257 | Positioned( 258 | left: 16, 259 | right: kIsWeb ? 60 : 16, 260 | bottom: 16, 261 | child: Row( 262 | children: [ 263 | LayoutBuilder( 264 | builder: (context, constraints) => 265 | constraints.maxWidth < 1000 266 | ? Row(children: _buildClearButtons()) 267 | : Column( 268 | crossAxisAlignment: CrossAxisAlignment.start, 269 | children: _buildClearButtons(), 270 | ), 271 | ), 272 | Spacer(), 273 | ..._buildAddButtons(), 274 | ], 275 | ), 276 | ), 277 | ], 278 | ), 279 | ); 280 | } 281 | 282 | const darkMapStyle = r''' 283 | [ 284 | { 285 | "elementType": "geometry", 286 | "stylers": [ 287 | { 288 | "color": "#212121" 289 | } 290 | ] 291 | }, 292 | { 293 | "elementType": "labels.icon", 294 | "stylers": [ 295 | { 296 | "visibility": "off" 297 | } 298 | ] 299 | }, 300 | { 301 | "elementType": "labels.text.fill", 302 | "stylers": [ 303 | { 304 | "color": "#757575" 305 | } 306 | ] 307 | }, 308 | { 309 | "elementType": "labels.text.stroke", 310 | "stylers": [ 311 | { 312 | "color": "#212121" 313 | } 314 | ] 315 | }, 316 | { 317 | "featureType": "administrative", 318 | "elementType": "geometry", 319 | "stylers": [ 320 | { 321 | "color": "#757575" 322 | } 323 | ] 324 | }, 325 | { 326 | "featureType": "administrative.country", 327 | "elementType": "labels.text.fill", 328 | "stylers": [ 329 | { 330 | "color": "#9e9e9e" 331 | } 332 | ] 333 | }, 334 | { 335 | "featureType": "administrative.land_parcel", 336 | "stylers": [ 337 | { 338 | "visibility": "off" 339 | } 340 | ] 341 | }, 342 | { 343 | "featureType": "administrative.locality", 344 | "elementType": "labels.text.fill", 345 | "stylers": [ 346 | { 347 | "color": "#bdbdbd" 348 | } 349 | ] 350 | }, 351 | { 352 | "featureType": "poi", 353 | "elementType": "labels.text.fill", 354 | "stylers": [ 355 | { 356 | "color": "#757575" 357 | } 358 | ] 359 | }, 360 | { 361 | "featureType": "poi.park", 362 | "elementType": "geometry", 363 | "stylers": [ 364 | { 365 | "color": "#181818" 366 | } 367 | ] 368 | }, 369 | { 370 | "featureType": "poi.park", 371 | "elementType": "labels.text.fill", 372 | "stylers": [ 373 | { 374 | "color": "#616161" 375 | } 376 | ] 377 | }, 378 | { 379 | "featureType": "poi.park", 380 | "elementType": "labels.text.stroke", 381 | "stylers": [ 382 | { 383 | "color": "#1b1b1b" 384 | } 385 | ] 386 | }, 387 | { 388 | "featureType": "road", 389 | "elementType": "geometry.fill", 390 | "stylers": [ 391 | { 392 | "color": "#2c2c2c" 393 | } 394 | ] 395 | }, 396 | { 397 | "featureType": "road", 398 | "elementType": "labels.text.fill", 399 | "stylers": [ 400 | { 401 | "color": "#8a8a8a" 402 | } 403 | ] 404 | }, 405 | { 406 | "featureType": "road.arterial", 407 | "elementType": "geometry", 408 | "stylers": [ 409 | { 410 | "color": "#373737" 411 | } 412 | ] 413 | }, 414 | { 415 | "featureType": "road.highway", 416 | "elementType": "geometry", 417 | "stylers": [ 418 | { 419 | "color": "#3c3c3c" 420 | } 421 | ] 422 | }, 423 | { 424 | "featureType": "road.highway.controlled_access", 425 | "elementType": "geometry", 426 | "stylers": [ 427 | { 428 | "color": "#4e4e4e" 429 | } 430 | ] 431 | }, 432 | { 433 | "featureType": "road.local", 434 | "elementType": "labels.text.fill", 435 | "stylers": [ 436 | { 437 | "color": "#616161" 438 | } 439 | ] 440 | }, 441 | { 442 | "featureType": "transit", 443 | "elementType": "labels.text.fill", 444 | "stylers": [ 445 | { 446 | "color": "#757575" 447 | } 448 | ] 449 | }, 450 | { 451 | "featureType": "water", 452 | "elementType": "geometry", 453 | "stylers": [ 454 | { 455 | "color": "#000000" 456 | } 457 | ] 458 | }, 459 | { 460 | "featureType": "water", 461 | "elementType": "labels.text.fill", 462 | "stylers": [ 463 | { 464 | "color": "#3d3d3d" 465 | } 466 | ] 467 | } 468 | ] 469 | '''; 470 | 471 | const contentString = r''' 472 |
473 |
474 |

Uluru

475 |
476 |

477 | Uluru, also referred to as Ayers Rock, is a large 478 | sandstone rock formation in the southern part of the 479 | Northern Territory, central Australia. It lies 335 km (208 mi) 480 | south west of the nearest large town, Alice Springs; 450 km 481 | (280 mi) by road. Kata Tjuta and Uluru are the two major 482 | features of the Uluru - Kata Tjuta National Park. Uluru is 483 | sacred to the Pitjantjatjara and Yankunytjatjara, the 484 | Aboriginal people of the area. It has many springs, waterholes, 485 | rock caves and ancient paintings. Uluru is listed as a World 486 | Heritage Site. 487 |

488 |

489 | Attribution: Uluru, 490 | 491 | http://en.wikipedia.org/w/index.php?title=Uluru 492 | 493 | (last visited June 22, 2009). 494 |

495 |
496 |
497 | '''; 498 | 499 | const polygon = [ 500 | GeoCoord(32.707868, -117.191018), 501 | GeoCoord(32.705645, -117.191096), 502 | GeoCoord(32.697756, -117.166664), 503 | GeoCoord(32.686486, -117.163206), 504 | GeoCoord(32.675876, -117.169452), 505 | GeoCoord(32.674726, -117.165233), 506 | GeoCoord(32.679833, -117.158487), 507 | GeoCoord(32.677571, -117.153893), 508 | GeoCoord(32.671987, -117.160079), 509 | GeoCoord(32.667547, -117.160477), 510 | GeoCoord(32.654748, -117.147579), 511 | GeoCoord(32.651933, -117.150312), 512 | GeoCoord(32.649676, -117.144334), 513 | GeoCoord(32.631665, -117.138201), 514 | GeoCoord(32.632033, -117.132249), 515 | GeoCoord(32.630156, -117.137234), 516 | GeoCoord(32.628072, -117.136479), 517 | GeoCoord(32.630315, -117.131443), 518 | GeoCoord(32.625930, -117.135312), 519 | GeoCoord(32.623754, -117.131664), 520 | GeoCoord(32.627465, -117.130883), 521 | GeoCoord(32.622598, -117.128791), 522 | GeoCoord(32.622622, -117.133183), 523 | GeoCoord(32.618690, -117.133634), 524 | GeoCoord(32.618980, -117.128403), 525 | GeoCoord(32.609847, -117.132502), 526 | GeoCoord(32.604198, -117.125333), 527 | GeoCoord(32.588260, -117.122032), 528 | GeoCoord(32.591164, -117.116851), 529 | GeoCoord(32.587601, -117.105968), 530 | GeoCoord(32.583792, -117.104434), 531 | GeoCoord(32.570566, -117.101382), 532 | GeoCoord(32.569256, -117.122378), 533 | GeoCoord(32.560825, -117.122903), 534 | GeoCoord(32.557753, -117.131040), 535 | GeoCoord(32.542737, -117.124883), 536 | GeoCoord(32.534156, -117.126062), 537 | GeoCoord(32.563255, -117.134963), 538 | GeoCoord(32.584055, -117.134263), 539 | GeoCoord(32.619405, -117.140001), 540 | GeoCoord(32.655293, -117.157349), 541 | GeoCoord(32.669944, -117.169624), 542 | GeoCoord(32.682710, -117.189445), 543 | GeoCoord(32.685297, -117.208773), 544 | GeoCoord(32.679814, -117.224882), 545 | GeoCoord(32.697212, -117.227058), 546 | GeoCoord(32.707701, -117.219816), 547 | GeoCoord(32.711931, -117.214107), 548 | GeoCoord(32.715026, -117.196521), 549 | GeoCoord(32.713053, -117.189703), 550 | GeoCoord(32.707868, -117.191018), 551 | ]; 552 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_google_maps_example 2 | description: Demonstrates how to use the flutter_google_maps package. 3 | 4 | environment: 5 | sdk: ">=2.6.0 <3.0.0" 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | 11 | flutter_google_maps: 12 | path: ../ 13 | 14 | flutter: 15 | uses-material-design: true 16 | assets: 17 | - assets/images/map-marker-warehouse.png 18 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | flutter_google_maps_example 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/flutter_google_maps.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library flutter_google_maps; 6 | 7 | export 'src/core/map_items.dart'; 8 | export 'src/core/map_operations.dart'; 9 | export 'src/core/map_preferences.dart'; 10 | 11 | export 'src/core/utils.dart'; 12 | 13 | export 'src/core/google_map.dart'; 14 | export 'src/core/google_map.state.dart' 15 | if (dart.library.html) 'src/web/google_map.state.dart' 16 | if (dart.library.io) 'src/mobile/google_map.state.dart'; 17 | 18 | export 'package:google_polyline_algorithm/google_polyline_algorithm.dart'; 19 | export 'package:google_directions_api/google_directions_api.dart' 20 | show GeoCoord, GeoCoordBounds; 21 | -------------------------------------------------------------------------------- /lib/src/core/google_map.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | 8 | import 'package:google_directions_api/google_directions_api.dart' 9 | show GeoCoord, DirectionsService; 10 | 11 | import 'map_items.dart'; 12 | import 'map_operations.dart'; 13 | import 'map_preferences.dart'; 14 | 15 | import 'google_map.state.dart' 16 | if (dart.library.html) '../web/google_map.state.dart' 17 | if (dart.library.io) '../mobile/google_map.state.dart'; 18 | 19 | /// This widget will try to occupy all available space 20 | class GoogleMap extends StatefulWidget { 21 | /// Creates an instance of [GoogleMap]. 22 | const GoogleMap({ 23 | Key key, 24 | this.minZoom, 25 | this.maxZoom, 26 | this.mapStyle, 27 | this.markers = const {}, 28 | this.onTap, 29 | this.onLongPress, 30 | this.interactive = true, 31 | this.initialZoom = _zoom, 32 | this.mapType = MapType.roadmap, 33 | this.initialPosition = const GeoCoord(_defaultLat, _defaultLng), 34 | this.mobilePreferences = const MobileMapPreferences(), 35 | this.webPreferences = const WebMapPreferences(), 36 | }) : assert(mapType != null), 37 | assert(interactive != null), 38 | assert(initialPosition != null), 39 | assert(initialZoom != null), 40 | assert(mobilePreferences != null), 41 | assert(webPreferences != null), 42 | super(key: key); 43 | 44 | /// The initial position of the map's camera. 45 | final GeoCoord initialPosition; 46 | 47 | /// The initial zoom of the map's camera. 48 | final double initialZoom; 49 | 50 | /// Type of map tiles to be rendered. 51 | final MapType mapType; 52 | 53 | /// The preferred minimum zoom level or null, if unbounded from below. 54 | final double minZoom; 55 | 56 | /// The preferred maximum zoom level or null, if unbounded from above. 57 | final double maxZoom; 58 | 59 | /// Sets the styling of the base map. 60 | /// 61 | /// Set to `null` to clear any previous custom styling. 62 | /// 63 | /// If problems were detected with the [mapStyle], including un-parsable 64 | /// styling JSON, unrecognized feature type, unrecognized element type, or 65 | /// invalid styler keys: [MapStyleException] is thrown and the current 66 | /// style is left unchanged. 67 | /// 68 | /// The style string can be generated using [map style tool](https://mapstyle.withgoogle.com/). 69 | /// Also, refer [iOS](https://developers.google.com/maps/documentation/ios-sdk/style-reference) 70 | /// and [Android](https://developers.google.com/maps/documentation/android-sdk/style-reference) 71 | /// style reference for more information regarding the supported styles. 72 | final String mapStyle; 73 | 74 | /// Defines whether map is interactive or not. 75 | final bool interactive; 76 | 77 | /// Called every time a [GoogleMap] is tapped. 78 | final ValueChanged onTap; 79 | 80 | /// Markers to be placed on the map. 81 | final Set markers; 82 | 83 | /// Called every time a [GoogleMap] is long pressed. 84 | /// 85 | /// For `web` this will be called when `right mouse clicked`. 86 | final ValueChanged onLongPress; 87 | 88 | /// Set of mobile map preferences. 89 | final MobileMapPreferences mobilePreferences; 90 | 91 | /// Set of web map preferences. 92 | final WebMapPreferences webPreferences; 93 | 94 | static const _zoom = 12.0; 95 | static const _defaultLat = 34.0469058; 96 | static const _defaultLng = -118.3503948; 97 | 98 | /// Gets [MapOperations] interface via provided `key` of 99 | /// [GoogleMapStateBase] state. 100 | static MapOperations of(GlobalKey key) => 101 | key.currentState; 102 | 103 | /// Initializer of [GoogleMap]. 104 | /// 105 | /// `Required` if `Directions API` will be needed. 106 | /// For other cases, could be ignored. 107 | static void init(String apiKey) => DirectionsService.init(apiKey); 108 | 109 | @override 110 | GoogleMapState createState() => GoogleMapState(); 111 | } 112 | 113 | abstract class GoogleMapStateBase extends State 114 | implements MapOperations { 115 | @protected 116 | String fixAssetPath(String icon) => 117 | icon.endsWith('/marker_a.png') || icon.endsWith('/marker_b.png') 118 | ? 'packages/flutter_google_maps/' 119 | : ''; 120 | } 121 | -------------------------------------------------------------------------------- /lib/src/core/google_map.state.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async' show FutureOr; 6 | 7 | import 'package:flutter/widgets.dart'; 8 | 9 | import 'package:google_directions_api/google_directions_api.dart' 10 | show GeoCoord, GeoCoordBounds; 11 | 12 | import 'map_items.dart'; 13 | import 'google_map.dart'; 14 | 15 | class GoogleMapState extends GoogleMapStateBase { 16 | @override 17 | void moveCameraBounds( 18 | GeoCoordBounds newBounds, { 19 | double padding = 0, 20 | bool animated = true, 21 | bool waitUntilReady = true, 22 | }) => 23 | throw UnimplementedError(); 24 | 25 | @override 26 | void moveCamera( 27 | GeoCoord latLng, { 28 | bool animated = true, 29 | bool waitUntilReady = true, 30 | double zoom, 31 | }) => 32 | throw UnimplementedError(); 33 | 34 | @override 35 | void zoomCamera( 36 | double zoom, { 37 | bool animated = true, 38 | bool waitUntilReady = true, 39 | }) => 40 | throw UnimplementedError(); 41 | 42 | @override 43 | FutureOr get center => throw UnimplementedError(); 44 | 45 | @override 46 | void changeMapStyle( 47 | String mapStyle, { 48 | bool waitUntilReady = true, 49 | }) => 50 | throw UnimplementedError(); 51 | 52 | @override 53 | void addDirection( 54 | origin, 55 | destination, { 56 | String startLabel, 57 | String startIcon, 58 | String startInfo, 59 | String endLabel, 60 | String endIcon, 61 | String endInfo, 62 | }) => 63 | throw UnimplementedError(); 64 | 65 | @override 66 | void addMarkerRaw( 67 | GeoCoord position, { 68 | String label, 69 | String icon, 70 | String info, 71 | String infoSnippet, 72 | ValueChanged onTap, 73 | VoidCallback onInfoWindowTap, 74 | }) => 75 | throw UnimplementedError(); 76 | 77 | @override 78 | void addMarker(Marker marker) => throw UnimplementedError(); 79 | 80 | @override 81 | void addPolygon( 82 | String id, 83 | Iterable points, { 84 | ValueChanged onTap, 85 | Color strokeColor = const Color(0x000000), 86 | double strokeOpacity = 0.8, 87 | double strokeWidth = 1, 88 | Color fillColor = const Color(0x000000), 89 | double fillOpacity = 0.35, 90 | }) => 91 | throw UnimplementedError(); 92 | 93 | @override 94 | void clearDirections() => throw UnimplementedError(); 95 | 96 | @override 97 | void clearMarkers() => throw UnimplementedError(); 98 | 99 | @override 100 | void clearPolygons() => throw UnimplementedError(); 101 | 102 | @override 103 | void editPolygon( 104 | String id, 105 | Iterable points, { 106 | ValueChanged onTap, 107 | Color strokeColor = const Color(0x000000), 108 | double strokeOpacity = 0.8, 109 | double strokeWeight = 1, 110 | Color fillColor = const Color(0x000000), 111 | double fillOpacity = 0.35, 112 | }) => 113 | throw UnimplementedError(); 114 | 115 | @override 116 | void removeDirection(origin, destination) => throw UnimplementedError(); 117 | 118 | @override 119 | void removeMarker(GeoCoord position) => throw UnimplementedError(); 120 | 121 | @override 122 | void removePolygon(String id) => throw UnimplementedError(); 123 | 124 | @override 125 | void addCircle( 126 | String id, 127 | GeoCoord center, 128 | double radius, { 129 | ValueChanged onTap, 130 | Color strokeColor = const Color(0x000000), 131 | double strokeOpacity = 0.8, 132 | double strokeWidth = 1, 133 | Color fillColor = const Color(0x000000), 134 | double fillOpacity = 0.35, 135 | }) => 136 | throw UnimplementedError(); 137 | 138 | @override 139 | void clearCircles() => throw UnimplementedError(); 140 | 141 | @override 142 | void editCircle( 143 | String id, 144 | GeoCoord center, 145 | double radius, { 146 | ValueChanged onTap, 147 | Color strokeColor = const Color(0x000000), 148 | double strokeOpacity = 0.8, 149 | double strokeWidth = 1, 150 | Color fillColor = const Color(0x000000), 151 | double fillOpacity = 0.35, 152 | }) => 153 | throw UnimplementedError(); 154 | 155 | @override 156 | void removeCircle(String id) => throw UnimplementedError(); 157 | 158 | @override 159 | Widget build(BuildContext context) => throw UnimplementedError(); 160 | } 161 | -------------------------------------------------------------------------------- /lib/src/core/map_items.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/foundation.dart' show ValueChanged, VoidCallback; 6 | 7 | import 'package:google_directions_api/google_directions_api.dart' show GeoCoord; 8 | 9 | /// Marks a geographical location on the map. 10 | /// 11 | /// A marker icon is drawn oriented against the device's screen rather than 12 | /// the map's surface; that is, it will not necessarily change orientation 13 | /// due to map rotations, tilting, or zooming. 14 | class Marker { 15 | /// Creates an instance of [Marker]. 16 | const Marker( 17 | this.position, { 18 | this.label, 19 | this.icon, 20 | this.info, 21 | this.infoSnippet, 22 | this.onTap, 23 | this.onInfoWindowTap, 24 | }); 25 | 26 | /// Geographical location on the map. 27 | final GeoCoord position; 28 | 29 | /// [label] can be set only for `web`. 30 | final String label; 31 | 32 | /// If [icon] is set, must be a path to an image from project root 33 | /// as follows: `assets/images/image.png`. Or it must be an instance 34 | /// of [ByteString]. 35 | final String icon; 36 | 37 | /// If [info] is set and click event will be fired, will be shown popup with [info] within. 38 | /// * For `web` [info] could be a [String] or `HTML String` 39 | /// * For `mobile` [info] could be only a [String] 40 | final String info; 41 | 42 | /// [infoSnippet] sets snippet text for `InfoWindow`. 43 | final String infoSnippet; 44 | 45 | /// If [onTap] is not null, [info] popup will not be shown. 46 | final ValueChanged onTap; 47 | 48 | /// if [onInfoWindowTap] is set, it will be called once InfoWindow will be tapped. 49 | final VoidCallback onInfoWindowTap; 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/core/map_operations.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async' show FutureOr; 6 | import 'dart:ui' show Color, VoidCallback; 7 | 8 | import 'package:flutter/foundation.dart' show ValueChanged; 9 | import 'package:google_directions_api/google_directions_api.dart' 10 | show GeoCoord, GeoCoordBounds; 11 | 12 | import 'map_items.dart'; 13 | 14 | /// Interface of setting up map operations including: 15 | /// 16 | /// * Markers 17 | /// * Directions 18 | /// * Polygons 19 | /// * Camera position 20 | /// * Map Style 21 | abstract class MapOperations 22 | implements MapMarkers, MapDirections, MapPolygons, MapCircles { 23 | /// Moves camera to the new bounds. 24 | /// 25 | /// If `padding` not set, it defaults to `0`. 26 | /// 27 | /// if `animated` not set, it defaults to `true`. 28 | /// 29 | /// For safe execution of [moveCameraBounds] some actions must be performed, and if 30 | /// `waitUntilReady` is set to `true` (by default it's true), so this method 31 | /// will await of completion of all actions, and executes [moveCameraBounds] as soon 32 | /// as it possible. This argument only affects on **mobile** devices. 33 | void moveCameraBounds( 34 | GeoCoordBounds newBounds, { 35 | double padding = 0, 36 | bool animated = true, 37 | bool waitUntilReady = true, 38 | }); 39 | 40 | /// Moves camera to the new coordinates. 41 | /// 42 | /// if `animated` not set, it defaults to `true`. 43 | /// 44 | /// For safe execution of [moveCamera] some actions must be performed, and if 45 | /// `waitUntilReady` is set to `true` (by default it's true), so this method 46 | /// will await of completion of all actions, and executes [moveCamera] as soon 47 | /// as it possible. This argument only affects on **mobile** devices. 48 | void moveCamera( 49 | GeoCoord latLng, { 50 | bool animated = true, 51 | bool waitUntilReady = true, 52 | double zoom, 53 | }); 54 | 55 | /// Sets new camera zoom. 56 | /// 57 | /// if `animated` not set, it defaults to `true`. 58 | /// This argument only affects on **mobile** devices. 59 | /// 60 | /// For safe execution of [zoomCamera] some actions must be performed, and if 61 | /// `waitUntilReady` is set to `true` (by default it's true), so this method 62 | /// will await of completion of all actions, and executes [zoomCamera] as soon 63 | /// as it possible. This argument only affects on **mobile** devices. 64 | void zoomCamera( 65 | double zoom, { 66 | bool animated = true, 67 | bool waitUntilReady = true, 68 | }); 69 | 70 | /// Gets center coordinates of the map. 71 | FutureOr get center; 72 | 73 | /// Sets the styling of the base map. 74 | /// 75 | /// Set to `null` to clear any previous custom styling. 76 | /// 77 | /// For safe execution of [changeMapStyle] some actions must be performed, and if 78 | /// `waitUntilReady` is set to `true` (by default it's true), so this method 79 | /// will await of completion of all actions, and executes [changeMapStyle] as soon 80 | /// as it possible. This argument only affects on **mobile** devices. 81 | /// 82 | /// If problems were detected with the [mapStyle], including un-parsable 83 | /// styling JSON, unrecognized feature type, unrecognized element type, or 84 | /// invalid styler keys: [MapStyleException] is thrown and the current 85 | /// style is left unchanged. 86 | /// 87 | /// The style string can be generated using [map style tool](https://mapstyle.withgoogle.com/). 88 | /// Also, refer [iOS](https://developers.google.com/maps/documentation/ios-sdk/style-reference) 89 | /// and [Android](https://developers.google.com/maps/documentation/android-sdk/style-reference) 90 | /// style reference for more information regarding the supported styles. 91 | /// 92 | /// Please note, if widget rebuilds new map style will be ommited due to map style 93 | /// provided from the `widget`. So, if map will be scrolled out, make sure that 94 | /// new map style will be set to widgets [GoogleMap.mapStyle]. 95 | void changeMapStyle( 96 | String mapStyle, { 97 | bool waitUntilReady = true, 98 | }); 99 | } 100 | 101 | /// Interface of setting up markers 102 | abstract class MapMarkers { 103 | /// Adds a marker to the map by given [position]. 104 | /// 105 | /// [label] can be set only for `web`. 106 | /// 107 | /// If [icon] is set, must be a path to an image from project root 108 | /// as follows: `assets/images/image.png`. Or it must be an instance 109 | /// of [ByteString]. 110 | /// 111 | /// If [info] is set and click event will be fired, will be shown popup with [info] within. 112 | /// * For `web` [info] could be a [String] or `HTML String` 113 | /// * For `mobile` [info] could be only a [String] 114 | /// 115 | /// [infoSnippet] sets snippet text for `InfoWindow`. 116 | /// 117 | /// If [onTap] is not null, [info] popup will not be shown. 118 | /// 119 | /// if [onInfoWindowTap] is set, it will be called once InfoWindow will be tapped. 120 | /// 121 | /// If marker with same [position] have been already added, addition of a new marker will be ignored. 122 | void addMarkerRaw( 123 | GeoCoord position, { 124 | String label, 125 | String icon, 126 | String info, 127 | String infoSnippet, 128 | ValueChanged onTap, 129 | VoidCallback onInfoWindowTap, 130 | }); 131 | 132 | /// Adds a marker to the map by given [position]. 133 | /// 134 | /// If marker with same [position] have been already added, addition of a new marker will be ignored. 135 | void addMarker(Marker marker); 136 | 137 | /// Removes a marker from the map by given [position]. 138 | void removeMarker(GeoCoord position); 139 | 140 | /// Removes all markers from the map. 141 | void clearMarkers(); 142 | } 143 | 144 | /// Interface of setting up directions 145 | abstract class MapDirections { 146 | /// Adds a direction to the map by given [origin] and [destination] coordinates. 147 | /// 148 | /// [origin] and [destination] are `dynamic` due to following variations: 149 | /// * [LatLng], better use [Point], it will be converted into [LatLng] 150 | /// * [Place] 151 | /// * [String] 152 | /// 153 | /// If direction with same [origin] and [destination] have been already added, 154 | /// addition of a new polygon will be ignored. 155 | void addDirection( 156 | dynamic origin, 157 | dynamic destination, { 158 | String startLabel, 159 | String startIcon, 160 | String startInfo, 161 | String endLabel, 162 | String endIcon, 163 | String endInfo, 164 | }); 165 | 166 | /// Removes a direction from the map by given [origin] and [destination] coordinates. 167 | /// 168 | /// [origin] and [destination] are `dynamic` due to following variations: 169 | /// * [LatLng], better use [GeoCoord], it will be converted into [LatLng] 170 | /// * [Place] 171 | /// * [String] 172 | void removeDirection(dynamic origin, dynamic destination); 173 | 174 | /// Removes all directions from the map. 175 | void clearDirections(); 176 | } 177 | 178 | /// Interface of setting up polygons 179 | abstract class MapPolygons { 180 | /// Adds a polygon to the map by given [id] and [points]. 181 | /// 182 | /// Where [id] must be **unique**. 183 | /// 184 | /// If [id] have been already added, addition of a new polygon will be ignored. 185 | void addPolygon( 186 | String id, 187 | Iterable points, { 188 | ValueChanged onTap, 189 | Color strokeColor = const Color(0x000000), 190 | double strokeOpacity = 0.8, 191 | double strokeWidth = 1, 192 | Color fillColor = const Color(0x000000), 193 | double fillOpacity = 0.35, 194 | }); 195 | 196 | /// Removes and then adds a polygon to the map by given [id] and [points]. 197 | /// 198 | /// Where [id] must be **unique**. 199 | /// 200 | /// If [id] have been already added, addition of a new polygon will be ignored. 201 | void editPolygon( 202 | String id, 203 | Iterable points, { 204 | ValueChanged onTap, 205 | Color strokeColor = const Color(0x000000), 206 | double strokeOpacity = 0.8, 207 | double strokeWeight = 1, 208 | Color fillColor = const Color(0x000000), 209 | double fillOpacity = 0.35, 210 | }); 211 | 212 | /// Removes a polygon from the map by given [id]. 213 | void removePolygon(String id); 214 | 215 | /// Removes all polygones from the map. 216 | void clearPolygons(); 217 | } 218 | 219 | /// Interface of setting up circles 220 | abstract class MapCircles { 221 | /// Adds a circle to the map by given [id], [center] and [radius]. 222 | /// 223 | /// Where [id] must be **unique**. 224 | /// 225 | /// If [id] have been already added, addition of a new circle will be ignored. 226 | void addCircle( 227 | String id, 228 | GeoCoord center, 229 | double radius, { 230 | ValueChanged onTap, 231 | Color strokeColor = const Color(0x000000), 232 | double strokeOpacity = 0.8, 233 | double strokeWidth = 1, 234 | Color fillColor = const Color(0x000000), 235 | double fillOpacity = 0.35, 236 | }); 237 | 238 | /// Removes and then adds a circles to the map by given [id], [center] and [radius]. 239 | /// 240 | /// Where [id] must be **unique**. 241 | /// 242 | /// If [id] have been already added, addition of a new circle will be ignored. 243 | void editCircle( 244 | String id, 245 | GeoCoord center, 246 | double radius, { 247 | ValueChanged onTap, 248 | Color strokeColor = const Color(0x000000), 249 | double strokeOpacity = 0.8, 250 | double strokeWidth = 1, 251 | Color fillColor = const Color(0x000000), 252 | double fillOpacity = 0.35, 253 | }); 254 | 255 | /// Removes a circle from the map by given [id]. 256 | void removeCircle(String id); 257 | 258 | /// Removes all circles from the map. 259 | void clearCircles(); 260 | } 261 | -------------------------------------------------------------------------------- /lib/src/core/map_preferences.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:flutter/widgets.dart'; 6 | 7 | /// Type of map tiles to display. 8 | enum MapType { 9 | /// Do not display map tiles. 10 | none, 11 | 12 | /// Normal tiles (traffic and labels, subtle terrain information). 13 | roadmap, 14 | 15 | /// Satellite imaging tiles (aerial photos) 16 | satellite, 17 | 18 | /// Terrain tiles (indicates type and height of terrain) 19 | terrain, 20 | 21 | /// Hybrid tiles (satellite images with some labels/overlays) 22 | hybrid, 23 | } 24 | 25 | /// Set of mobile map preferences 26 | class MobileMapPreferences { 27 | /// Creates an instance of [MobileMapPreferences]. 28 | const MobileMapPreferences({ 29 | this.rotateGesturesEnabled = true, 30 | this.scrollGesturesEnabled = true, 31 | this.zoomGesturesEnabled = true, 32 | this.tiltGesturesEnabled = true, 33 | this.compassEnabled = true, 34 | this.mapToolbarEnabled = true, 35 | this.myLocationEnabled = false, 36 | this.myLocationButtonEnabled = true, 37 | this.zoomControlsEnabled = true, 38 | this.indoorViewEnabled = false, 39 | this.trafficEnabled = false, 40 | this.buildingsEnabled = true, 41 | this.padding = const EdgeInsets.all(0), 42 | }); 43 | 44 | /// True if the map should show a compass when rotated. 45 | final bool compassEnabled; 46 | 47 | /// True if the map should show a toolbar when you interact with the map. Android only. 48 | final bool mapToolbarEnabled; 49 | 50 | /// True if a "My Location" layer should be shown on the map. 51 | /// 52 | /// This layer includes a location indicator at the current device location, 53 | /// as well as a My Location button. 54 | /// * The indicator is a small blue dot if the device is stationary, or a 55 | /// chevron if the device is moving. 56 | /// * The My Location button animates to focus on the user's current location 57 | /// if the user's location is currently known. 58 | /// 59 | /// Enabling this feature requires adding location permissions to both native 60 | /// platforms of your app. 61 | /// * On Android add either 62 | /// `` 63 | /// or `` 64 | /// to your `AndroidManifest.xml` file. `ACCESS_COARSE_LOCATION` returns a 65 | /// location with an accuracy approximately equivalent to a city block, while 66 | /// `ACCESS_FINE_LOCATION` returns as precise a location as possible, although 67 | /// it consumes more battery power. You will also need to request these 68 | /// permissions during run-time. If they are not granted, the My Location 69 | /// feature will fail silently. 70 | /// * On iOS add a `NSLocationWhenInUseUsageDescription` key to your 71 | /// `Info.plist` file. This will automatically prompt the user for permissions 72 | /// when the map tries to turn on the My Location layer. 73 | final bool myLocationEnabled; 74 | 75 | /// Enables or disables the my-location button. 76 | /// 77 | /// The my-location button causes the camera to move such that the user's 78 | /// location is in the center of the map. If the button is enabled, it is 79 | /// only shown when the my-location layer is enabled. 80 | /// 81 | /// By default, the my-location button is enabled (and hence shown when the 82 | /// my-location layer is enabled). 83 | /// 84 | /// See also: 85 | /// * [myLocationEnabled] parameter. 86 | final bool myLocationButtonEnabled; 87 | 88 | /// Enables or disables the zoom in / zoom out (+/-) buttons. 89 | /// By default, they are enabled. 90 | final bool zoomControlsEnabled; 91 | 92 | /// Enables or disables the indoor view from the map. 93 | final bool indoorViewEnabled; 94 | 95 | /// Enables or disables the traffic layer of the map. 96 | final bool trafficEnabled; 97 | 98 | /// Enables or disables showing 3D buildings where available. 99 | final bool buildingsEnabled; 100 | 101 | /// Padding to be set on map. See https://developers.google.com/maps/documentation/android-sdk/map#map_padding for more details. 102 | final EdgeInsets padding; 103 | 104 | /// True if the map view should respond to rotate gestures. 105 | final bool rotateGesturesEnabled; 106 | 107 | /// True if the map view should respond to scroll gestures. 108 | final bool scrollGesturesEnabled; 109 | 110 | /// True if the map view should respond to zoom gestures. 111 | final bool zoomGesturesEnabled; 112 | 113 | /// True if the map view should respond to tilt gestures. 114 | final bool tiltGesturesEnabled; 115 | } 116 | 117 | /// Set of web map preferences 118 | class WebMapPreferences { 119 | /// Creates an instance of [WebMapPreferences]. 120 | const WebMapPreferences({ 121 | this.streetViewControl = false, 122 | this.fullscreenControl = false, 123 | this.mapTypeControl = false, 124 | this.panControl = false, 125 | this.overviewMapControl = false, 126 | this.rotateControl = false, 127 | this.scaleControl = false, 128 | this.zoomControl = false, 129 | this.dragGestures = true, 130 | this.scrollwheel = true, 131 | }); 132 | 133 | /// Predefined support for fullscreen map. 134 | /// 135 | /// Scrollwheel zomming and dragging gestures are enabled. 136 | const WebMapPreferences.fullscreen({ 137 | this.streetViewControl = false, 138 | this.fullscreenControl = false, 139 | this.mapTypeControl = false, 140 | this.panControl = false, 141 | this.overviewMapControl = false, 142 | this.rotateControl = false, 143 | this.scaleControl = false, 144 | this.zoomControl = false, 145 | }) : dragGestures = true, 146 | scrollwheel = true; 147 | 148 | /// Predefined support for map that will be scrolled. 149 | /// 150 | /// Scrollwheel zomming and dragging gestures are disabled. 151 | const WebMapPreferences.scrollable({ 152 | this.streetViewControl = false, 153 | this.fullscreenControl = false, 154 | this.mapTypeControl = false, 155 | this.panControl = false, 156 | this.overviewMapControl = false, 157 | this.rotateControl = false, 158 | this.scaleControl = false, 159 | this.zoomControl = false, 160 | }) : dragGestures = false, 161 | scrollwheel = false; 162 | 163 | /// Enables or disables streetViewControl. 164 | final bool streetViewControl; 165 | 166 | /// Enables or disables fullscreenControl. 167 | final bool fullscreenControl; 168 | 169 | /// Enables or disables mapTypeControl. 170 | final bool mapTypeControl; 171 | 172 | /// Enables or disables scrollwheel. 173 | final bool scrollwheel; 174 | 175 | /// Enables or disables panControl. 176 | final bool panControl; 177 | 178 | /// Enables or disables overviewMapControl. 179 | final bool overviewMapControl; 180 | 181 | /// Enables or disables rotateControl. 182 | final bool rotateControl; 183 | 184 | /// Enables or disables scaleControl. 185 | final bool scaleControl; 186 | 187 | /// Enables or disables zoomControl. 188 | final bool zoomControl; 189 | 190 | /// Enables or disables flutter drag gestures. 191 | final bool dragGestures; 192 | } 193 | -------------------------------------------------------------------------------- /lib/src/core/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert' show base64; 6 | import 'dart:typed_data' show Uint8List; 7 | 8 | /// Exception when a map style is invalid or was unable to be set. 9 | /// 10 | /// See also: `mapStyle` on [GoogleMap] and `changeMapStyle` on 11 | /// [MapOperations] for why this exception might be thrown. 12 | class MapStyleException implements Exception { 13 | /// Default constructor for [MapStyleException]. 14 | const MapStyleException(this.cause); 15 | 16 | /// The reason `GoogleMap.mapStyle` or `MapOperations.changeMapStyle` 17 | /// would throw this exception. 18 | final String cause; 19 | } 20 | 21 | /// Wrapper for byte array image representation. 22 | class ByteString { 23 | /// Constructor an instance of [ByteString]. 24 | const ByteString(this.byteData); 25 | 26 | /// Byte representation of an image. 27 | final Uint8List byteData; 28 | 29 | static const String _prefix = 'bytes://'; 30 | 31 | /// Checks whether provided [String] is a string representation of byte array. 32 | static bool isByteString(String byteString) => byteString.startsWith(_prefix); 33 | 34 | /// Converts [String] to byte array. 35 | static Uint8List fromString(String byteString) => 36 | base64.decode(byteString.replaceFirst(_prefix, '')); 37 | 38 | @override 39 | String toString() => _prefix + base64.encode(byteData); 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/mobile/google_map.state.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:flutter/widgets.dart'; 8 | 9 | import 'package:flinq/flinq.dart'; 10 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 11 | import 'package:google_directions_api/google_directions_api.dart'; 12 | 13 | import 'utils.dart'; 14 | import '../core/utils.dart' as utils; 15 | import '../core/google_map.dart' as gmap; 16 | import '../core/map_items.dart' as items; 17 | 18 | class GoogleMapState extends gmap.GoogleMapStateBase { 19 | final directionsService = DirectionsService(); 20 | 21 | final _markers = {}; 22 | final _polygons = {}; 23 | final _circles = {}; 24 | final _polylines = {}; 25 | final _directionMarkerCoords = {}; 26 | 27 | final _waitUntilReadyCompleter = Completer(); 28 | 29 | GoogleMapController _controller; 30 | 31 | void _setState(VoidCallback fn) { 32 | if (mounted) { 33 | setState(fn); 34 | } else { 35 | fn(); 36 | } 37 | } 38 | 39 | FutureOr _getBmpDesc(String image) async { 40 | if (image == null) return BitmapDescriptor.defaultMarker; 41 | 42 | if (utils.ByteString.isByteString(image)) { 43 | return BitmapDescriptor.fromBytes(utils.ByteString.fromString(image)); 44 | } 45 | 46 | return await BitmapDescriptor.fromAssetImage( 47 | createLocalImageConfiguration(context), 48 | image, 49 | ); 50 | } 51 | 52 | @override 53 | void moveCameraBounds( 54 | GeoCoordBounds newBounds, { 55 | double padding = 0, 56 | bool animated = true, 57 | bool waitUntilReady = true, 58 | }) async { 59 | assert(() { 60 | if (newBounds == null) { 61 | throw ArgumentError.notNull('newBounds'); 62 | } 63 | 64 | return true; 65 | }()); 66 | 67 | if (waitUntilReady == true) { 68 | await _waitUntilReadyCompleter.future; 69 | } 70 | 71 | if (animated == true) { 72 | await _controller?.animateCamera(CameraUpdate.newLatLngBounds( 73 | newBounds.toLatLngBounds(), 74 | padding ?? 0, 75 | )); 76 | } else { 77 | await _controller?.moveCamera(CameraUpdate.newLatLngBounds( 78 | newBounds.toLatLngBounds(), 79 | padding ?? 0, 80 | )); 81 | } 82 | } 83 | 84 | @override 85 | void moveCamera( 86 | GeoCoord latLng, { 87 | bool animated = true, 88 | bool waitUntilReady = true, 89 | double zoom, 90 | }) async { 91 | assert(() { 92 | if (latLng == null) { 93 | throw ArgumentError.notNull('latLng'); 94 | } 95 | 96 | return true; 97 | }()); 98 | 99 | if (waitUntilReady == true) { 100 | await _waitUntilReadyCompleter.future; 101 | } 102 | 103 | if (animated == true) { 104 | await _controller?.animateCamera(CameraUpdate.newLatLngZoom( 105 | latLng.toLatLng(), 106 | zoom ?? await _controller?.getZoomLevel(), 107 | )); 108 | } else { 109 | await _controller?.moveCamera(CameraUpdate.newLatLngZoom( 110 | latLng.toLatLng(), 111 | zoom ?? await _controller?.getZoomLevel(), 112 | )); 113 | } 114 | } 115 | 116 | @override 117 | void zoomCamera( 118 | double zoom, { 119 | bool animated = true, 120 | bool waitUntilReady = true, 121 | }) async { 122 | assert(() { 123 | if (zoom == null) { 124 | throw ArgumentError.notNull('zoom'); 125 | } 126 | 127 | return true; 128 | }()); 129 | 130 | if (waitUntilReady == true) { 131 | await _waitUntilReadyCompleter.future; 132 | } 133 | 134 | if (animated == true) { 135 | await _controller?.animateCamera(CameraUpdate.zoomTo(zoom)); 136 | } else { 137 | await _controller?.moveCamera(CameraUpdate.zoomTo(zoom)); 138 | } 139 | } 140 | 141 | FutureOr get center async => 142 | (await _controller?.getVisibleRegion())?.toGeoCoordBounds()?.center; 143 | 144 | @override 145 | void changeMapStyle( 146 | String mapStyle, { 147 | bool waitUntilReady = true, 148 | }) async { 149 | if (waitUntilReady == true) { 150 | await _waitUntilReadyCompleter.future; 151 | } 152 | try { 153 | await _controller?.setMapStyle(mapStyle); 154 | } on MapStyleException catch (e) { 155 | throw utils.MapStyleException(e.cause); 156 | } 157 | } 158 | 159 | @override 160 | void addMarkerRaw( 161 | GeoCoord position, { 162 | String label, 163 | String icon, 164 | String info, 165 | String infoSnippet, 166 | ValueChanged onTap, 167 | VoidCallback onInfoWindowTap, 168 | }) async { 169 | assert(() { 170 | if (position == null) { 171 | throw ArgumentError.notNull('position'); 172 | } 173 | 174 | if (position.latitude == null || position.longitude == null) { 175 | throw ArgumentError.notNull('position.latitude && position.longitude'); 176 | } 177 | 178 | return true; 179 | }()); 180 | 181 | final key = position.toString(); 182 | 183 | if (_markers.containsKey(key)) return; 184 | 185 | final markerId = MarkerId(key); 186 | final marker = Marker( 187 | markerId: markerId, 188 | onTap: onTap != null ? () => onTap(key) : null, 189 | consumeTapEvents: onTap != null, 190 | position: position.toLatLng(), 191 | icon: icon == null 192 | ? BitmapDescriptor.defaultMarker 193 | : await _getBmpDesc('${fixAssetPath(icon)}$icon'), 194 | infoWindow: info != null 195 | ? InfoWindow( 196 | title: info, 197 | snippet: infoSnippet, 198 | onTap: onInfoWindowTap, 199 | ) 200 | : null, 201 | ); 202 | 203 | _setState(() => _markers[key] = marker); 204 | } 205 | 206 | @override 207 | void addMarker(items.Marker marker) => addMarkerRaw( 208 | marker.position, 209 | label: marker.label, 210 | icon: marker.icon, 211 | info: marker.info, 212 | infoSnippet: marker.infoSnippet, 213 | onTap: marker.onTap, 214 | onInfoWindowTap: marker.onInfoWindowTap, 215 | ); 216 | 217 | @override 218 | void removeMarker(GeoCoord position) { 219 | assert(() { 220 | if (position == null) { 221 | throw ArgumentError.notNull('position'); 222 | } 223 | 224 | if (position.latitude == null || position.longitude == null) { 225 | throw ArgumentError.notNull('position.latitude && position.longitude'); 226 | } 227 | 228 | return true; 229 | }()); 230 | 231 | final key = position.toString(); 232 | 233 | if (!_markers.containsKey(key)) return; 234 | 235 | _setState(() => _markers.remove(key)); 236 | } 237 | 238 | @override 239 | void clearMarkers() => _setState(() => _markers.clear()); 240 | 241 | @override 242 | void addDirection( 243 | dynamic origin, 244 | dynamic destination, { 245 | String startLabel, 246 | String startIcon, 247 | String startInfo, 248 | String endLabel, 249 | String endIcon, 250 | String endInfo, 251 | }) { 252 | assert(() { 253 | if (origin == null) { 254 | throw ArgumentError.notNull('origin'); 255 | } 256 | 257 | if (destination == null) { 258 | throw ArgumentError.notNull('destination'); 259 | } 260 | 261 | return true; 262 | }()); 263 | 264 | final request = DirectionsRequest( 265 | origin: origin is GeoCoord 266 | ? LatLng(origin.latitude, origin.longitude) 267 | : origin, 268 | destination: 269 | destination is GeoCoord ? destination.toLatLng() : destination, 270 | travelMode: TravelMode.driving, 271 | ); 272 | directionsService.route( 273 | request, 274 | (response, status) { 275 | if (status == DirectionsStatus.ok) { 276 | final key = '${origin}_$destination'; 277 | 278 | if (_polylines.containsKey(key)) return; 279 | 280 | moveCameraBounds( 281 | response?.routes?.firstOrNull?.bounds, 282 | padding: 80, 283 | ); 284 | 285 | final leg = response?.routes?.firstOrNull?.legs?.firstOrNull; 286 | 287 | final startLatLng = leg?.startLocation; 288 | if (startLatLng != null) { 289 | _directionMarkerCoords[startLatLng] = origin; 290 | if (startIcon != null || startInfo != null || startLabel != null) { 291 | addMarkerRaw( 292 | startLatLng, 293 | icon: startIcon ?? 'assets/images/marker_a.png', 294 | info: startInfo ?? leg.startAddress, 295 | label: startLabel, 296 | ); 297 | } else { 298 | addMarkerRaw( 299 | startLatLng, 300 | icon: 'assets/images/marker_a.png', 301 | info: leg.startAddress, 302 | ); 303 | } 304 | } 305 | 306 | final endLatLng = leg?.endLocation; 307 | if (endLatLng != null) { 308 | _directionMarkerCoords[endLatLng] = destination; 309 | if (endIcon != null || endInfo != null || endLabel != null) { 310 | addMarkerRaw( 311 | endLatLng, 312 | icon: endIcon ?? 'assets/images/marker_b.png', 313 | info: endInfo ?? leg.endAddress, 314 | label: endLabel, 315 | ); 316 | } else { 317 | addMarkerRaw( 318 | endLatLng, 319 | icon: 'assets/images/marker_b.png', 320 | info: leg.endAddress, 321 | ); 322 | } 323 | } 324 | 325 | final polylineId = PolylineId(key); 326 | final polyline = Polyline( 327 | polylineId: polylineId, 328 | points: response?.routes?.firstOrNull?.overviewPath 329 | ?.mapList((_) => _.toLatLng()) ?? 330 | [startLatLng?.toLatLng(), endLatLng?.toLatLng()], 331 | color: const Color(0xcc2196F3), 332 | startCap: Cap.roundCap, 333 | endCap: Cap.roundCap, 334 | width: 8, 335 | ); 336 | 337 | _setState(() => _polylines[key] = polyline); 338 | } 339 | }, 340 | ); 341 | } 342 | 343 | @override 344 | void removeDirection(dynamic origin, dynamic destination) { 345 | assert(() { 346 | if (origin == null) { 347 | throw ArgumentError.notNull('origin'); 348 | } 349 | 350 | if (destination == null) { 351 | throw ArgumentError.notNull('destination'); 352 | } 353 | 354 | return true; 355 | }()); 356 | 357 | var value = _polylines.remove('${origin}_$destination'); 358 | final start = value?.points?.firstOrNull?.toGeoCoord(); 359 | if (start != null) { 360 | removeMarker(start); 361 | _directionMarkerCoords.remove(start); 362 | } 363 | final end = value?.points?.lastOrNull?.toGeoCoord(); 364 | if (end != null) { 365 | removeMarker(end); 366 | _directionMarkerCoords.remove(end); 367 | } 368 | value = null; 369 | } 370 | 371 | @override 372 | void clearDirections() { 373 | for (var polyline in _polylines.values) { 374 | final start = polyline?.points?.firstOrNull?.toGeoCoord(); 375 | if (start != null) { 376 | removeMarker(start); 377 | _directionMarkerCoords.remove(start); 378 | } 379 | final end = polyline?.points?.lastOrNull?.toGeoCoord(); 380 | if (end != null) { 381 | removeMarker(end); 382 | _directionMarkerCoords.remove(end); 383 | } 384 | polyline = null; 385 | } 386 | _polylines.clear(); 387 | } 388 | 389 | @override 390 | void addPolygon( 391 | String id, 392 | Iterable points, { 393 | ValueChanged onTap, 394 | Color strokeColor = const Color(0x000000), 395 | double strokeOpacity = 0.8, 396 | double strokeWidth = 1, 397 | Color fillColor = const Color(0x000000), 398 | double fillOpacity = 0.35, 399 | }) { 400 | assert(() { 401 | if (id == null) { 402 | throw ArgumentError.notNull('id'); 403 | } 404 | 405 | if (points == null) { 406 | throw ArgumentError.notNull('position'); 407 | } 408 | 409 | if (points.isEmpty) { 410 | throw ArgumentError.value([], 'points'); 411 | } 412 | 413 | if (points.length < 3) { 414 | throw ArgumentError('Polygon must have at least 3 coordinates'); 415 | } 416 | 417 | return true; 418 | }()); 419 | 420 | _polygons.putIfAbsent( 421 | id, 422 | () => Polygon( 423 | polygonId: PolygonId(id), 424 | points: points.mapList((_) => _.toLatLng()), 425 | consumeTapEvents: onTap != null, 426 | onTap: onTap != null ? () => onTap(id) : null, 427 | strokeWidth: strokeWidth?.toInt() ?? 1, 428 | strokeColor: (strokeColor ?? const Color(0x000000)) 429 | .withOpacity(strokeOpacity ?? 0.8), 430 | fillColor: (fillColor ?? const Color(0x000000)) 431 | .withOpacity(fillOpacity ?? 0.35), 432 | ), 433 | ); 434 | } 435 | 436 | @override 437 | void editPolygon( 438 | String id, 439 | Iterable points, { 440 | ValueChanged onTap, 441 | Color strokeColor = const Color(0x000000), 442 | double strokeOpacity = 0.8, 443 | double strokeWeight = 1, 444 | Color fillColor = const Color(0x000000), 445 | double fillOpacity = 0.35, 446 | }) { 447 | removePolygon(id); 448 | addPolygon( 449 | id, 450 | points, 451 | onTap: onTap, 452 | strokeColor: strokeColor, 453 | strokeOpacity: strokeOpacity, 454 | strokeWidth: strokeWeight, 455 | fillColor: fillColor, 456 | fillOpacity: fillOpacity, 457 | ); 458 | } 459 | 460 | @override 461 | void removePolygon(String id) { 462 | assert(() { 463 | if (id == null) { 464 | throw ArgumentError.notNull('id'); 465 | } 466 | 467 | return true; 468 | }()); 469 | 470 | if (!_polygons.containsKey(id)) return; 471 | 472 | _setState(() => _polygons.remove(id)); 473 | } 474 | 475 | @override 476 | void clearPolygons() => _setState(() => _polygons.clear()); 477 | 478 | @override 479 | void addCircle( 480 | String id, 481 | GeoCoord center, 482 | double radius, { 483 | ValueChanged onTap, 484 | Color strokeColor = const Color(0x000000), 485 | double strokeOpacity = 0.8, 486 | double strokeWidth = 1, 487 | Color fillColor = const Color(0x000000), 488 | double fillOpacity = 0.35, 489 | }) { 490 | assert(() { 491 | if (id == null) { 492 | throw ArgumentError.notNull('id'); 493 | } 494 | 495 | if (center == null) { 496 | throw ArgumentError.notNull('center'); 497 | } 498 | 499 | if (radius == null) { 500 | throw ArgumentError.notNull('radius'); 501 | } 502 | 503 | return true; 504 | }()); 505 | 506 | setState(() { 507 | _circles.putIfAbsent( 508 | id, 509 | () => Circle( 510 | circleId: CircleId(id), 511 | center: center.toLatLng(), 512 | radius: radius, 513 | onTap: () => onTap(id), 514 | strokeColor: strokeColor.withOpacity(strokeOpacity), 515 | strokeWidth: strokeWidth.toInt(), 516 | fillColor: fillColor.withOpacity(fillOpacity), 517 | ), 518 | ); 519 | }); 520 | } 521 | 522 | @override 523 | void clearCircles() => setState(() => _circles.clear()); 524 | 525 | @override 526 | void editCircle( 527 | String id, 528 | GeoCoord center, 529 | double radius, { 530 | ValueChanged onTap, 531 | Color strokeColor = const Color(0x000000), 532 | double strokeOpacity = 0.8, 533 | double strokeWidth = 1, 534 | Color fillColor = const Color(0x000000), 535 | double fillOpacity = 0.35, 536 | }) { 537 | removeCircle(id); 538 | addCircle( 539 | id, 540 | center, 541 | radius, 542 | onTap: onTap, 543 | strokeColor: strokeColor, 544 | strokeOpacity: strokeOpacity, 545 | strokeWidth: strokeWidth, 546 | fillColor: fillColor, 547 | fillOpacity: fillOpacity, 548 | ); 549 | } 550 | 551 | @override 552 | void removeCircle(String id) { 553 | assert(() { 554 | if (id == null) { 555 | throw ArgumentError.notNull('id'); 556 | } 557 | 558 | return true; 559 | }()); 560 | 561 | if (!_circles.containsKey(id)) return; 562 | 563 | _setState(() => _circles.remove(id)); 564 | } 565 | 566 | @override 567 | void initState() { 568 | super.initState(); 569 | if (widget.markers != null) { 570 | for (var marker in widget.markers) { 571 | addMarker(marker); 572 | } 573 | } 574 | } 575 | 576 | @override 577 | Widget build(BuildContext context) => LayoutBuilder( 578 | builder: (context, constraints) => IgnorePointer( 579 | ignoring: !widget.interactive, 580 | child: Container( 581 | constraints: BoxConstraints(maxHeight: constraints.maxHeight), 582 | child: GoogleMap( 583 | markers: Set.of(_markers.values), 584 | polygons: Set.of(_polygons.values), 585 | polylines: Set.of(_polylines.values), 586 | circles: Set.of(_circles.values), 587 | mapType: MapType.values[widget.mapType.index], 588 | minMaxZoomPreference: 589 | MinMaxZoomPreference(widget.minZoom, widget.minZoom), 590 | initialCameraPosition: CameraPosition( 591 | target: widget.initialPosition.toLatLng(), 592 | zoom: widget.initialZoom, 593 | ), 594 | onTap: (coords) => widget.onTap?.call(coords?.toGeoCoord()), 595 | onLongPress: (coords) => 596 | widget.onLongPress?.call(coords?.toGeoCoord()), 597 | onMapCreated: (GoogleMapController controller) { 598 | _controller = controller; 599 | _controller.setMapStyle(widget.mapStyle); 600 | 601 | _waitUntilReadyCompleter.complete(); 602 | }, 603 | padding: widget.mobilePreferences.padding, 604 | compassEnabled: widget.mobilePreferences.compassEnabled, 605 | trafficEnabled: widget.mobilePreferences.trafficEnabled, 606 | buildingsEnabled: widget.mobilePreferences.buildingsEnabled, 607 | indoorViewEnabled: widget.mobilePreferences.indoorViewEnabled, 608 | mapToolbarEnabled: widget.mobilePreferences.mapToolbarEnabled, 609 | myLocationEnabled: widget.mobilePreferences.myLocationEnabled, 610 | myLocationButtonEnabled: 611 | widget.mobilePreferences.myLocationButtonEnabled, 612 | tiltGesturesEnabled: widget.mobilePreferences.tiltGesturesEnabled, 613 | zoomGesturesEnabled: widget.mobilePreferences.zoomGesturesEnabled, 614 | rotateGesturesEnabled: 615 | widget.mobilePreferences.rotateGesturesEnabled, 616 | zoomControlsEnabled: widget.mobilePreferences.zoomControlsEnabled, 617 | scrollGesturesEnabled: 618 | widget.mobilePreferences.scrollGesturesEnabled, 619 | ), 620 | ), 621 | ), 622 | ); 623 | 624 | @override 625 | void dispose() { 626 | super.dispose(); 627 | 628 | _markers.clear(); 629 | _polygons.clear(); 630 | _polylines.clear(); 631 | _directionMarkerCoords.clear(); 632 | 633 | _controller = null; 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /lib/src/mobile/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:google_maps_flutter/google_maps_flutter.dart'; 6 | import 'package:google_directions_api/google_directions_api.dart' 7 | show GeoCoord, GeoCoordBounds; 8 | 9 | extension MobileLatLngExtensions on LatLng { 10 | GeoCoord toGeoCoord() => GeoCoord(this.latitude, this.longitude); 11 | } 12 | 13 | extension MobileGeoCoordExtensions on GeoCoord { 14 | LatLng toLatLng() => LatLng(this.latitude, this.longitude); 15 | } 16 | 17 | extension MobileGeoCoordBoundsExtensions on GeoCoordBounds { 18 | LatLngBounds toLatLngBounds() => LatLngBounds( 19 | northeast: this.northeast.toLatLng(), 20 | southwest: this.southwest.toLatLng(), 21 | ); 22 | 23 | GeoCoord get center => GeoCoord( 24 | (this.northeast.latitude + this.southwest.latitude) / 2, 25 | (this.northeast.longitude + this.southwest.longitude) / 2, 26 | ); 27 | } 28 | 29 | extension MobileLatLngBoundsExtensions on LatLngBounds { 30 | GeoCoordBounds toGeoCoordBounds() => GeoCoordBounds( 31 | northeast: this.northeast.toGeoCoord(), 32 | southwest: this.southwest.toGeoCoord(), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/web/google_map.state.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:html'; 6 | import 'dart:async'; 7 | import 'dart:ui' as ui; 8 | 9 | import 'package:flutter/widgets.dart'; 10 | import 'package:flutter/scheduler.dart' show SchedulerBinding; 11 | 12 | import 'package:uuid/uuid.dart'; 13 | import 'package:flinq/flinq.dart'; 14 | import 'package:google_maps/google_maps.dart'; 15 | import 'package:google_directions_api/google_directions_api.dart' 16 | show GeoCoord, GeoCoordBounds; 17 | 18 | import 'utils.dart'; 19 | import '../core/google_map.dart'; 20 | import '../core/utils.dart' as utils; 21 | import '../core/map_items.dart' as items; 22 | 23 | class GoogleMapState extends GoogleMapStateBase { 24 | final htmlId = Uuid().v1(); 25 | final directionsService = DirectionsService(); 26 | 27 | final _markers = {}; 28 | final _infoState = {}; 29 | final _infos = {}; 30 | final _polygons = {}; 31 | final _circles = {}; 32 | final _subscriptions = []; 33 | final _directions = {}; 34 | 35 | GMap _map; 36 | MapOptions _mapOptions; 37 | 38 | String _getImage(String image) { 39 | if (image == null) return null; 40 | 41 | if (utils.ByteString.isByteString(image)) { 42 | final blob = Blob([utils.ByteString.fromString(image)], 'image/png'); 43 | return Url.createObjectUrlFromBlob(blob); 44 | } 45 | 46 | return '${fixAssetPath(image)}assets/$image'; 47 | } 48 | 49 | @override 50 | void moveCameraBounds( 51 | GeoCoordBounds newBounds, { 52 | double padding = 0, 53 | bool animated = true, 54 | bool waitUntilReady = true, 55 | }) { 56 | assert(() { 57 | if (newBounds == null) { 58 | throw ArgumentError.notNull('newBounds'); 59 | } 60 | 61 | return true; 62 | }()); 63 | 64 | _map.center = newBounds.center.toLatLng(); 65 | 66 | final zoom = _map.zoom; 67 | if (animated == true) { 68 | _map.panToBounds(newBounds.toLatLngBounds()); 69 | } else { 70 | _map.fitBounds(newBounds.toLatLngBounds()); 71 | } 72 | _map.zoom = zoom; 73 | } 74 | 75 | @override 76 | void moveCamera( 77 | GeoCoord latLng, { 78 | bool animated = true, 79 | bool waitUntilReady = true, 80 | double zoom, 81 | }) { 82 | assert(() { 83 | if (latLng == null) { 84 | throw ArgumentError.notNull('latLng'); 85 | } 86 | 87 | return true; 88 | }()); 89 | 90 | if (animated == true) { 91 | _map.panTo(latLng.toLatLng()); 92 | _map.zoom = zoom ?? _map.zoom; 93 | } else { 94 | _map.center = latLng.toLatLng(); 95 | _map.zoom = zoom ?? _map.zoom; 96 | } 97 | } 98 | 99 | @override 100 | void zoomCamera( 101 | double zoom, { 102 | bool animated = true, 103 | bool waitUntilReady = true, 104 | }) { 105 | assert(() { 106 | if (zoom == null) { 107 | throw ArgumentError.notNull('zoom'); 108 | } 109 | 110 | return true; 111 | }()); 112 | 113 | _map.zoom = zoom; 114 | } 115 | 116 | @override 117 | FutureOr get center => _map.center?.toGeoCoord(); 118 | 119 | @override 120 | void changeMapStyle( 121 | String mapStyle, { 122 | bool waitUntilReady = true, 123 | }) { 124 | try { 125 | _mapOptions.styles = mapStyle?.parseMapStyle(); 126 | _map.options = _mapOptions; 127 | } catch (e) { 128 | throw utils.MapStyleException(e.toString()); 129 | } 130 | } 131 | 132 | @override 133 | void addMarkerRaw( 134 | GeoCoord position, { 135 | String label, 136 | String icon, 137 | String info, 138 | String infoSnippet, 139 | ValueChanged onTap, 140 | ui.VoidCallback onInfoWindowTap, 141 | }) { 142 | assert(() { 143 | if (position == null) { 144 | throw ArgumentError.notNull('position'); 145 | } 146 | 147 | if (position.latitude == null || position.longitude == null) { 148 | throw ArgumentError.notNull('position.latitude && position.longitude'); 149 | } 150 | 151 | return true; 152 | }()); 153 | 154 | final key = position.toString(); 155 | 156 | if (_markers.containsKey(key)) return; 157 | 158 | final marker = Marker() 159 | ..map = _map 160 | ..label = label 161 | ..icon = _getImage(icon) 162 | ..position = position.toLatLng(); 163 | 164 | if (info != null || onTap != null) { 165 | _subscriptions.add(marker.onClick.listen((_) async { 166 | final key = position.toString(); 167 | 168 | if (onTap != null) { 169 | onTap(key); 170 | return; 171 | } 172 | 173 | int doubleToInt(double value) => (value * 100000).truncate(); 174 | final id = 175 | 'position${doubleToInt(position.latitude)}${doubleToInt(position.longitude)}'; 176 | 177 | if (_infos[key] == null) { 178 | print(id); 179 | final _info = onInfoWindowTap == null 180 | ? '$info${infoSnippet.isNotEmpty == true ? '\n$infoSnippet' : ''}' 181 | : '

$info${infoSnippet.isNotEmpty == true ? '

$infoSnippet

' : ''}

'; 182 | 183 | _infos[key] = InfoWindow(InfoWindowOptions()..content = _info); 184 | _subscriptions.add( 185 | _infos[key].onCloseclick.listen((_) => _infoState[key] = false)); 186 | } 187 | 188 | if (!(_infoState[key] ?? false)) { 189 | _infos[key].open(_map, marker); 190 | if (_infoState[key] == null) { 191 | await Future.delayed(const Duration(milliseconds: 100)); 192 | 193 | final infoElem = querySelector('flt-platform-view') 194 | .shadowRoot 195 | .getElementById('$htmlId') 196 | .querySelector('#$id'); 197 | 198 | infoElem.addEventListener('click', (event) => onInfoWindowTap()); 199 | } 200 | _infoState[key] = true; 201 | } else { 202 | _infos[key].close(); 203 | 204 | _infoState[key] = false; 205 | } 206 | })); 207 | } 208 | 209 | _markers[key] = marker; 210 | } 211 | 212 | @override 213 | void addMarker(items.Marker marker) => addMarkerRaw( 214 | marker.position, 215 | label: marker.label, 216 | icon: marker.icon, 217 | info: marker.info, 218 | infoSnippet: marker.infoSnippet, 219 | onTap: marker.onTap, 220 | onInfoWindowTap: marker.onInfoWindowTap, 221 | ); 222 | 223 | @override 224 | void removeMarker(GeoCoord position) { 225 | assert(() { 226 | if (position == null) { 227 | throw ArgumentError.notNull('position'); 228 | } 229 | 230 | if (position.latitude == null || position.longitude == null) { 231 | throw ArgumentError.notNull('position.latitude && position.longitude'); 232 | } 233 | 234 | return true; 235 | }()); 236 | 237 | final key = position.toString(); 238 | 239 | var marker = _markers.remove(key); 240 | marker?.map = null; 241 | marker = null; 242 | 243 | var info = _infos.remove(key); 244 | info?.close(); 245 | info = null; 246 | 247 | _infoState.remove(key); 248 | } 249 | 250 | @override 251 | void clearMarkers() { 252 | for (var marker in _markers.values) { 253 | marker?.map = null; 254 | marker = null; 255 | } 256 | _markers.clear(); 257 | 258 | for (var info in _infos.values) { 259 | info?.close(); 260 | info = null; 261 | } 262 | _infos.clear(); 263 | 264 | _infoState.clear(); 265 | } 266 | 267 | @override 268 | void addDirection( 269 | dynamic origin, 270 | dynamic destination, { 271 | String startLabel, 272 | String startIcon, 273 | String startInfo, 274 | String endLabel, 275 | String endIcon, 276 | String endInfo, 277 | }) { 278 | assert(() { 279 | if (origin == null) { 280 | throw ArgumentError.notNull('origin'); 281 | } 282 | 283 | if (destination == null) { 284 | throw ArgumentError.notNull('destination'); 285 | } 286 | 287 | return true; 288 | }()); 289 | 290 | _directions.putIfAbsent( 291 | '${origin}_$destination', 292 | () { 293 | DirectionsRenderer direction = DirectionsRenderer( 294 | DirectionsRendererOptions()..suppressMarkers = true); 295 | direction.map = _map; 296 | 297 | final request = DirectionsRequest() 298 | ..origin = origin is GeoCoord 299 | ? LatLng(origin.latitude, origin.longitude) 300 | : origin 301 | ..destination = 302 | destination is GeoCoord ? destination.toLatLng() : destination 303 | ..travelMode = TravelMode.DRIVING; 304 | directionsService.route( 305 | request, 306 | (response, status) { 307 | if (status == DirectionsStatus.OK) { 308 | direction.directions = response; 309 | 310 | final leg = response?.routes?.firstOrNull?.legs?.firstOrNull; 311 | 312 | final startLatLng = leg?.startLocation; 313 | if (startLatLng != null) { 314 | if (startIcon != null || 315 | startInfo != null || 316 | startLabel != null) { 317 | addMarkerRaw( 318 | startLatLng.toGeoCoord(), 319 | icon: startIcon, 320 | info: startInfo ?? leg.startAddress, 321 | label: startLabel, 322 | ); 323 | } else { 324 | addMarkerRaw( 325 | startLatLng.toGeoCoord(), 326 | icon: 'assets/images/marker_a.png', 327 | info: leg.startAddress, 328 | ); 329 | } 330 | } 331 | 332 | final endLatLng = leg?.endLocation; 333 | if (endLatLng != null) { 334 | if (endIcon != null || endInfo != null || endLabel != null) { 335 | addMarkerRaw( 336 | endLatLng.toGeoCoord(), 337 | icon: endIcon, 338 | info: endInfo ?? leg.endAddress, 339 | label: endLabel, 340 | ); 341 | } else { 342 | addMarkerRaw( 343 | endLatLng.toGeoCoord(), 344 | icon: 'assets/images/marker_b.png', 345 | info: leg.endAddress, 346 | ); 347 | } 348 | } 349 | } 350 | }, 351 | ); 352 | 353 | return direction; 354 | }, 355 | ); 356 | } 357 | 358 | @override 359 | void removeDirection(dynamic origin, dynamic destination) { 360 | assert(() { 361 | if (origin == null) { 362 | throw ArgumentError.notNull('origin'); 363 | } 364 | 365 | if (destination == null) { 366 | throw ArgumentError.notNull('destination'); 367 | } 368 | 369 | return true; 370 | }()); 371 | 372 | var value = _directions.remove('${origin}_$destination'); 373 | value?.map = null; 374 | final start = value 375 | ?.directions?.routes?.firstOrNull?.legs?.firstOrNull?.startLocation 376 | ?.toGeoCoord(); 377 | if (start != null) { 378 | removeMarker(start); 379 | } 380 | final end = value 381 | ?.directions?.routes?.firstOrNull?.legs?.lastOrNull?.endLocation 382 | ?.toGeoCoord(); 383 | if (end != null) { 384 | removeMarker(end); 385 | } 386 | value = null; 387 | } 388 | 389 | @override 390 | void clearDirections() { 391 | for (var direction in _directions.values) { 392 | direction?.map = null; 393 | final start = direction 394 | ?.directions?.routes?.firstOrNull?.legs?.firstOrNull?.startLocation 395 | ?.toGeoCoord(); 396 | if (start != null) { 397 | removeMarker(start); 398 | } 399 | final end = direction 400 | ?.directions?.routes?.firstOrNull?.legs?.lastOrNull?.endLocation 401 | ?.toGeoCoord(); 402 | if (end != null) { 403 | removeMarker(end); 404 | } 405 | direction = null; 406 | } 407 | _directions.clear(); 408 | } 409 | 410 | @override 411 | void addPolygon( 412 | String id, 413 | Iterable points, { 414 | ValueChanged onTap, 415 | Color strokeColor = const Color(0x000000), 416 | double strokeOpacity = 0.8, 417 | double strokeWidth = 1, 418 | Color fillColor = const Color(0x000000), 419 | double fillOpacity = 0.35, 420 | }) { 421 | assert(() { 422 | if (id == null) { 423 | throw ArgumentError.notNull('id'); 424 | } 425 | 426 | if (points == null) { 427 | throw ArgumentError.notNull('position'); 428 | } 429 | 430 | if (points.isEmpty) { 431 | throw ArgumentError.value([], 'points'); 432 | } 433 | 434 | if (points.length < 3) { 435 | throw ArgumentError('Polygon must have at least 3 coordinates'); 436 | } 437 | 438 | return true; 439 | }()); 440 | 441 | _polygons.putIfAbsent( 442 | id, 443 | () { 444 | final options = PolygonOptions() 445 | ..clickable = onTap != null 446 | ..paths = points.mapList((_) => _.toLatLng()) 447 | ..strokeColor = strokeColor?.toHashString() ?? '#000000' 448 | ..strokeOpacity = strokeOpacity ?? 0.8 449 | ..strokeWeight = strokeWidth ?? 1 450 | ..fillColor = strokeColor?.toHashString() ?? '#000000' 451 | ..fillOpacity = fillOpacity ?? 0.35; 452 | 453 | final polygon = Polygon(options)..map = _map; 454 | 455 | if (onTap != null) { 456 | _subscriptions.add(polygon.onClick.listen((_) => onTap(id))); 457 | } 458 | 459 | return polygon; 460 | }, 461 | ); 462 | } 463 | 464 | @override 465 | void editPolygon( 466 | String id, 467 | Iterable points, { 468 | ValueChanged onTap, 469 | Color strokeColor = const Color(0x000000), 470 | double strokeOpacity = 0.8, 471 | double strokeWeight = 1, 472 | Color fillColor = const Color(0x000000), 473 | double fillOpacity = 0.35, 474 | }) { 475 | removePolygon(id); 476 | addPolygon( 477 | id, 478 | points, 479 | onTap: onTap, 480 | strokeColor: strokeColor, 481 | strokeOpacity: strokeOpacity, 482 | strokeWidth: strokeWeight, 483 | fillColor: fillColor, 484 | fillOpacity: fillOpacity, 485 | ); 486 | } 487 | 488 | @override 489 | void removePolygon(String id) { 490 | assert(() { 491 | if (id == null) { 492 | throw ArgumentError.notNull('id'); 493 | } 494 | 495 | return true; 496 | }()); 497 | 498 | var value = _polygons.remove(id); 499 | value?.map = null; 500 | value = null; 501 | } 502 | 503 | @override 504 | void clearPolygons() { 505 | for (var polygon in _polygons.values) { 506 | polygon?.map = null; 507 | polygon = null; 508 | } 509 | _polygons.clear(); 510 | } 511 | 512 | void _createMapOptions() { 513 | _mapOptions = MapOptions() 514 | ..zoom = widget.initialZoom 515 | ..center = widget.initialPosition.toLatLng() 516 | ..streetViewControl = widget.webPreferences.streetViewControl 517 | ..fullscreenControl = widget.webPreferences.fullscreenControl 518 | ..mapTypeControl = widget.webPreferences.mapTypeControl 519 | ..scrollwheel = widget.webPreferences.scrollwheel 520 | ..panControl = widget.webPreferences.panControl 521 | ..overviewMapControl = widget.webPreferences.overviewMapControl 522 | ..rotateControl = widget.webPreferences.rotateControl 523 | ..scaleControl = widget.webPreferences.scaleControl 524 | ..zoomControl = widget.webPreferences.zoomControl 525 | ..minZoom = widget.minZoom 526 | ..maxZoom = widget.maxZoom 527 | ..styles = widget.mapStyle?.parseMapStyle() 528 | ..mapTypeId = widget.mapType.toString().split('.')[1] 529 | ..gestureHandling = widget.interactive ? 'auto' : 'none'; 530 | } 531 | 532 | @override 533 | void addCircle( 534 | String id, 535 | GeoCoord center, 536 | double radius, { 537 | ValueChanged onTap, 538 | ui.Color strokeColor = const Color(0x000000), 539 | double strokeOpacity = 0.8, 540 | double strokeWidth = 1, 541 | ui.Color fillColor = const Color(0x000000), 542 | double fillOpacity = 0.35, 543 | }) { 544 | assert(() { 545 | if (id == null) { 546 | throw ArgumentError.notNull('id'); 547 | } 548 | 549 | if (center == null) { 550 | throw ArgumentError.notNull('center'); 551 | } 552 | 553 | if (radius == null) { 554 | throw ArgumentError.notNull('radius'); 555 | } 556 | 557 | return true; 558 | }()); 559 | 560 | _circles.putIfAbsent( 561 | id, 562 | () { 563 | final options = CircleOptions() 564 | ..center = center.toLatLng() 565 | ..radius = radius 566 | ..clickable = onTap != null 567 | ..strokeColor = strokeColor?.toHashString() ?? '#000000' 568 | ..strokeOpacity = strokeOpacity ?? 0.8 569 | ..strokeWeight = strokeWidth ?? 1 570 | ..fillColor = strokeColor?.toHashString() ?? '#000000' 571 | ..fillOpacity = fillOpacity ?? 0.35; 572 | 573 | final circle = Circle(options)..map = _map; 574 | 575 | if (onTap != null) { 576 | _subscriptions.add(circle.onClick.listen((_) => onTap(id))); 577 | } 578 | 579 | return circle; 580 | }, 581 | ); 582 | } 583 | 584 | @override 585 | void clearCircles() { 586 | for (var circle in _circles.values) { 587 | circle?.map = null; 588 | circle = null; 589 | } 590 | _circles.clear(); 591 | } 592 | 593 | @override 594 | void editCircle( 595 | String id, 596 | GeoCoord center, 597 | double radius, { 598 | ValueChanged onTap, 599 | ui.Color strokeColor = const Color(0x000000), 600 | double strokeOpacity = 0.8, 601 | double strokeWidth = 1, 602 | ui.Color fillColor = const Color(0x000000), 603 | double fillOpacity = 0.35, 604 | }) { 605 | removeCircle(id); 606 | addCircle( 607 | id, 608 | center, 609 | radius, 610 | onTap: onTap, 611 | strokeColor: strokeColor, 612 | strokeOpacity: strokeOpacity, 613 | strokeWidth: strokeWidth, 614 | fillColor: fillColor, 615 | fillOpacity: fillOpacity, 616 | ); 617 | } 618 | 619 | @override 620 | void removeCircle(String id) { 621 | assert(() { 622 | if (id == null) { 623 | throw ArgumentError.notNull('id'); 624 | } 625 | 626 | return true; 627 | }()); 628 | 629 | var value = _circles.remove(id); 630 | value?.map = null; 631 | value = null; 632 | } 633 | 634 | @override 635 | void initState() { 636 | super.initState(); 637 | SchedulerBinding.instance.addPostFrameCallback((_) { 638 | for (var marker in widget.markers) { 639 | addMarker(marker); 640 | } 641 | }); 642 | } 643 | 644 | @override 645 | Widget build(BuildContext context) { 646 | _createMapOptions(); 647 | 648 | if (_map == null) { 649 | ui.platformViewRegistry.registerViewFactory(htmlId, (int viewId) { 650 | final elem = DivElement() 651 | ..id = htmlId 652 | ..style.width = '100%' 653 | ..style.height = '100%' 654 | ..style.border = 'none'; 655 | 656 | _map = GMap(elem, _mapOptions); 657 | 658 | _subscriptions.add(_map.onClick.listen( 659 | (event) => widget.onTap?.call(event?.latLng?.toGeoCoord()))); 660 | _subscriptions.add(_map.onRightclick.listen( 661 | (event) => widget.onLongPress?.call(event?.latLng?.toGeoCoord()))); 662 | 663 | return elem; 664 | }); 665 | } 666 | 667 | return LayoutBuilder( 668 | builder: (context, constraints) => GestureDetector( 669 | onVerticalDragUpdate: 670 | widget.webPreferences.dragGestures ? null : (_) {}, 671 | onHorizontalDragUpdate: 672 | widget.webPreferences.dragGestures ? null : (_) {}, 673 | child: Container( 674 | constraints: BoxConstraints(maxHeight: constraints.maxHeight), 675 | child: HtmlElementView(viewType: htmlId), 676 | ), 677 | ), 678 | ); 679 | } 680 | 681 | @override 682 | void dispose() { 683 | super.dispose(); 684 | _subscriptions.forEach((_) => _.cancel()); 685 | 686 | _infos.clear(); 687 | _markers.clear(); 688 | _polygons.clear(); 689 | _circles.clear(); 690 | _infoState.clear(); 691 | _directions.clear(); 692 | _subscriptions.clear(); 693 | 694 | _map = null; 695 | _mapOptions = null; 696 | } 697 | } 698 | -------------------------------------------------------------------------------- /lib/src/web/utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert'; 6 | import 'dart:ui' as ui show Color; 7 | 8 | import 'package:flinq/flinq.dart'; 9 | import 'package:google_maps/google_maps.dart'; 10 | import 'package:google_directions_api/google_directions_api.dart' 11 | show GeoCoord, GeoCoordBounds; 12 | 13 | extension WebLatLngExtensions on LatLng { 14 | GeoCoord toGeoCoord() => GeoCoord(this.lat, this.lng); 15 | } 16 | 17 | extension WebGeoCoordExtensions on GeoCoord { 18 | LatLng toLatLng() => LatLng(this.latitude, this.longitude); 19 | } 20 | 21 | extension WebGeoCoordBoundsExtensions on GeoCoordBounds { 22 | LatLngBounds toLatLngBounds() => LatLngBounds( 23 | this.southwest.toLatLng(), 24 | this.northeast.toLatLng(), 25 | ); 26 | 27 | GeoCoord get center => GeoCoord( 28 | (this.northeast.latitude + this.southwest.latitude) / 2, 29 | (this.northeast.longitude + this.southwest.longitude) / 2, 30 | ); 31 | } 32 | 33 | extension WebLatLngBoundsExtensions on LatLngBounds { 34 | GeoCoordBounds toGeoCoordBounds() => GeoCoordBounds( 35 | northeast: this.northEast.toGeoCoord(), 36 | southwest: this.southWest.toGeoCoord(), 37 | ); 38 | } 39 | 40 | extension WebColorExtensions on ui.Color { 41 | String toHashString() => 42 | '#${this.red.toRadixString(16)}${this.green.toRadixString(16)}${this.blue.toRadixString(16)}'; 43 | } 44 | 45 | extension WebMapStyleExtension on String { 46 | MapTypeStyleElementType _elementTypeFromString(String value) { 47 | switch (value) { 48 | case 'all': 49 | return MapTypeStyleElementType.ALL; 50 | case 'geometry': 51 | return MapTypeStyleElementType.GEOMETRY; 52 | case 'geometry.fill': 53 | return MapTypeStyleElementType.GEOMETRY_FILL; 54 | case 'geometry.stroke': 55 | return MapTypeStyleElementType.GEOMETRY_STROKE; 56 | case 'labels': 57 | return MapTypeStyleElementType.LABELS; 58 | case 'labels.icon': 59 | return MapTypeStyleElementType.LABELS_ICON; 60 | case 'labels.text': 61 | return MapTypeStyleElementType.LABELS_TEXT; 62 | case 'labels.text.fill': 63 | return MapTypeStyleElementType.LABELS_TEXT_FILL; 64 | case 'labels.text.stroke': 65 | return MapTypeStyleElementType.LABELS_TEXT_STROKE; 66 | 67 | default: 68 | return null; 69 | } 70 | } 71 | 72 | MapTypeStyleFeatureType _featureTypeFromString(String value) { 73 | switch (value) { 74 | case 'administrative': 75 | return MapTypeStyleFeatureType.ADMINISTRATIVE; 76 | case 'administrative.country': 77 | return MapTypeStyleFeatureType.ADMINISTRATIVE_COUNTRY; 78 | case 'administrative.land_parcel': 79 | return MapTypeStyleFeatureType.ADMINISTRATIVE_LAND_PARCEL; 80 | case 'administrative.locality': 81 | return MapTypeStyleFeatureType.ADMINISTRATIVE_LOCALITY; 82 | case 'administrative.neighborhood': 83 | return MapTypeStyleFeatureType.ADMINISTRATIVE_NEIGHBORHOOD; 84 | case 'administrative.province': 85 | return MapTypeStyleFeatureType.ADMINISTRATIVE_PROVINCE; 86 | case 'all': 87 | return MapTypeStyleFeatureType.ALL; 88 | case 'landscape': 89 | return MapTypeStyleFeatureType.LANDSCAPE; 90 | case 'landscape.man_made': 91 | return MapTypeStyleFeatureType.LANDSCAPE_MAN_MADE; 92 | case 'landscape.natural': 93 | return MapTypeStyleFeatureType.LANDSCAPE_NATURAL; 94 | case 'landscape.natural.landcover': 95 | return MapTypeStyleFeatureType.LANDSCAPE_NATURAL_LANDCOVER; 96 | case 'landscape.natural.terrain': 97 | return MapTypeStyleFeatureType.LANDSCAPE_NATURAL_TERRAIN; 98 | case 'poi': 99 | return MapTypeStyleFeatureType.POI; 100 | case 'poi.attraction': 101 | return MapTypeStyleFeatureType.POI_ATTRACTION; 102 | case 'poi.business': 103 | return MapTypeStyleFeatureType.POI_BUSINESS; 104 | case 'poi.government': 105 | return MapTypeStyleFeatureType.POI_GOVERNMENT; 106 | case 'poi.medical': 107 | return MapTypeStyleFeatureType.POI_MEDICAL; 108 | case 'poi.park': 109 | return MapTypeStyleFeatureType.POI_PARK; 110 | case 'poi.place_of_worship': 111 | return MapTypeStyleFeatureType.POI_PLACE_OF_WORSHIP; 112 | case 'poi.school': 113 | return MapTypeStyleFeatureType.POI_SCHOOL; 114 | case 'poi.sports_complex': 115 | return MapTypeStyleFeatureType.POI_SPORTS_COMPLEX; 116 | case 'road': 117 | return MapTypeStyleFeatureType.ROAD; 118 | case 'road.arterial': 119 | return MapTypeStyleFeatureType.ROAD_ARTERIAL; 120 | case 'road.highway': 121 | return MapTypeStyleFeatureType.ROAD_HIGHWAY; 122 | case 'road.highway.controlled_access': 123 | return MapTypeStyleFeatureType.ROAD_HIGHWAY_CONTROLLED_ACCESS; 124 | case 'road.local': 125 | return MapTypeStyleFeatureType.ROAD_LOCAL; 126 | case 'transit': 127 | return MapTypeStyleFeatureType.TRANSIT; 128 | case 'transit.line': 129 | return MapTypeStyleFeatureType.TRANSIT_LINE; 130 | case 'transit.station': 131 | return MapTypeStyleFeatureType.TRANSIT_STATION; 132 | case 'transit.station.airport': 133 | return MapTypeStyleFeatureType.TRANSIT_STATION_AIRPORT; 134 | case 'transit.station.bus': 135 | return MapTypeStyleFeatureType.TRANSIT_STATION_BUS; 136 | case 'transit.station.rail': 137 | return MapTypeStyleFeatureType.TRANSIT_STATION_RAIL; 138 | case 'water': 139 | return MapTypeStyleFeatureType.WATER; 140 | 141 | default: 142 | return null; 143 | } 144 | } 145 | 146 | MapTypeStyler _stylerFromMap(Map map) => MapTypeStyler() 147 | ..color = map['color'] 148 | ..gamma = map['gamma'] 149 | ..hue = map['hue'] 150 | ..invertLightness = map['invertLightness'] 151 | ..lightness = map['lightness'] 152 | ..saturation = map['saturation'] 153 | ..visibility = map['visibility'] 154 | ..weight = map['weight']; 155 | 156 | List parseMapStyle() { 157 | final List map = json.decode(this); 158 | return map.mapList( 159 | (style) => MapTypeStyle() 160 | ..elementType = _elementTypeFromString(style['elementType']) 161 | ..featureType = _featureTypeFromString(style['featureType']) 162 | ..stylers = (style['stylers'] as List)?.mapList( 163 | (styler) => _stylerFromMap(styler), 164 | ), 165 | ); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /publish_commands.txt: -------------------------------------------------------------------------------- 1 | flutter packages pub publish --dry-run 2 | flutter packages pub publish 3 | 4 | pub uploader --package= add -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_google_maps 2 | version: 4.0.0 3 | homepage: https://github.com/marchdev-tk/flutter_google_maps 4 | description: A Flutter plugin for integrating Google Maps in iOS, Android and Web applications. It is a wrapper of google_maps_flutter for Mobile and google_maps for Web. 5 | 6 | environment: 7 | sdk: ">=2.6.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | google_polyline_algorithm: ^2.0.0 14 | google_maps_flutter: ^0.5.28+1 15 | google_directions_api: ^0.5.0 16 | google_maps: ^3.4.3 17 | http: ^0.12.1 18 | flinq: ^1.1.0 19 | uuid: ^2.1.0 20 | 21 | dev_dependencies: 22 | flutter_test: 23 | sdk: flutter 24 | 25 | pedantic: ^1.8.0 26 | 27 | flutter: 28 | uses-material-design: true 29 | assets: 30 | - assets/images/marker_a.png 31 | - assets/images/marker_b.png -------------------------------------------------------------------------------- /test/flutter_google_maps_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the MarchDev Toolkit project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | void main() {} 6 | --------------------------------------------------------------------------------