diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml index 4168440c2..ef674f5c5 100644 --- a/.github/workflows/flutter_ci.yml +++ b/.github/workflows/flutter_ci.yml @@ -7,7 +7,6 @@ jobs: name: "Static code analysis" runs-on: ubuntu-latest - steps: - uses: actions/checkout@v1 - uses: actions/setup-java@v1 @@ -16,7 +15,7 @@ jobs: - uses: subosito/flutter-action@v1 - run: flutter pub get - name: Lint analysis - run: flutter analyze + run: cd example && flutter analyze build-android: name: "Build Android apk" @@ -29,7 +28,6 @@ jobs: java-version: '12.x' - uses: subosito/flutter-action@v1 - run: flutter pub get - - name: Build example APK run: cd example && flutter build apk # We might want to add a flutter test step in the future, when there actually are tests for this plugin @@ -37,17 +35,32 @@ jobs: build-iOS: name: Build iOS package runs-on: macos-latest + steps: - uses: actions/checkout@v1 - uses: actions/setup-java@v1 with: java-version: '12.x' - uses: subosito/flutter-action@v1 - - name: Upgrade flutter - run: | - flutter channel master - flutter upgrade + - run: flutter pub get - name: build iOS package run: | cd ./example flutter build ios --release --no-codesign + + build-web: + name: "Build web" + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + - uses: subosito/flutter-action@v1 + with: + channel: beta + - run: flutter config --enable-web + - run: flutter pub get + - name: Build web + run: cd example && flutter build web diff --git a/.gitignore b/.gitignore index c530f7be0..17c0893ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,114 @@ -android/.classpath -android/.project -.packages -.vscode/ +# Miscellaneous +*.class +*.lock +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws .idea/ -android/.settings/org.eclipse.buildship.core.prefs +# Visual Studio Code related +.classpath +.project +.settings/ +.vscode/ + +# Flutter repo-specific +/bin/cache/ +/bin/mingit/ +/dev/benchmarks/mega_gallery/ +/dev/bots/.recipe_deps +/dev/bots/android_tools/ +/dev/docs/doc/ +/dev/docs/flutter.docs.zip +/dev/docs/lib/ +/dev/docs/pubspec.yaml +/dev/integration_tests/**/xcuserdata +/dev/integration_tests/**/Pods +/packages/flutter/coverage/ +version + +# packages file containing multi-root paths +.packages.generated + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ +flutter_*.png +linked_*.ds +unlinked.ds +unlinked_spec.ds + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java +**/android/key.properties +*.jks + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# macOS +**/macos/Flutter/GeneratedPluginRegistrant.swift +**/macos/Flutter/Flutter-Debug.xcconfig +**/macos/Flutter/Flutter-Release.xcconfig +**/macos/Flutter/Flutter-Profile.xcconfig + +# Coverage +coverage/ + +# Symbols +app.*.symbols + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages +!/dev/ci/**/Gemfile.lock \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8538da84e..df882e874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 0.7.0, June 6, 2020 +* Introduction of mapbox_gl_platform_interface library +* Introduction of mapbox_gl_web library +* Integrate web support through mapbox-gl-js +* Add icon-allow-overlap configurations + +## 0.0.6, May 31, 2020 +* Update mapbox depdendency to 9.2.0 (android) and 5.6.0 (iOS) +* Long press handlers for both iOS as Android +* Change default location tracking to none +* OnCameraIdle listener support +* Add image to style +* Add animation duration to animateCamera +* Content insets +* Visible region support on iOS +* Numerous bug fixes + ## 0.0.5, December 21, 2019 * iOS support for annotation extensions (circle, symbol, line) * Update SDK to 8.5.0 (Android) and 5.5.0 (iOS) diff --git a/README.md b/README.md index a723ee01e..08b9ac1d0 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,54 @@ -# Flutter Mapbox GL Native +# Flutter Mapbox GL > **Please note that this project is community driven and is not an official Mapbox product.** We welcome [feedback](https://github.com/tobrun/flutter-mapbox-gl/issues) and contributions. -This Flutter plugin for [mapbox-gl-native](https://github.com/mapbox/mapbox-gl-native) enables -embedded interactive and customizable vector maps inside a Flutter widget by embedding Android and iOS views. +This Flutter plugin allows to show embedded interactive and customizable vector maps inside a Flutter widget. For the Android and iOS integration, we use [mapbox-gl-native](https://github.com/mapbox/mapbox-gl-native). For web, we rely on [mapbox-gl-js](https://github.com/mapbox/mapbox-gl-js). This project only supports a subset of the API exposed by these libraries. ![screenshot.png](screenshot.png) -## Install -This project is available on [pub.dev](https://pub.dev/packages/mapbox_gl), follow the [instructions](https://flutter.dev/docs/development/packages-and-plugins/using-packages#adding-a-package-dependency-to-an-app) to integrate a package into your flutter application. - -## :new: :new: Who's using this SDK :new: :new: - -We're compiling a list of apps using this SDK. If you want to be listed here, please open a PR and add yourself below (or open a ticket and we'll add you). - -- You? - -### Running example app +## Running the example app - Install [Flutter](https://flutter.io/get-started/) and validate its installation with `flutter doctor` -- Clone this repository with `git clone git@github.com:mapbox/flutter-mapbox-gl.git` -- Run the app with `cd flutter_mapbox/example && flutter run` +- Clone the repository with `git clone git@github.com:tobrun/flutter-mapbox-gl.git` +- Add a Mapbox access token to the example app (see next section) +- Connect a mobile device or start an emulator, simulator or chrome +- Locate the id of a the device with `flutter devices` +- Run the app with `cd flutter_mapbox/example && flutter packages get && flutter run -d {device_id}` -#### Mapbox Access Token +## Adding a Mapbox Access Token This project uses Mapbox vector tiles, which requires a Mapbox account and a Mapbox access token. Obtain a free access token on [your Mapbox account page](https://www.mapbox.com/account/access-tokens/). > **Even if you do not use Mapbox vector tiles but vector tiles from a different source (like self-hosted tiles) with this plugin, you will need to specify any non-empty string as Access Token as explained below!** -##### Android -Add Mapbox read token value in the application manifest ```android/app/src/main/AndroidManifest.xml:``` -``` -``` +The **recommended** way to provide your access token is through the `MapboxMap` constructor's `accessToken` parameter, which is available starting from the v0.8 release. Note that you should always use the same token throughout your entire app. -#### iOS -Add these lines to your Info.plist +An alternative method to provide access tokens that was required until the v0.7 release is described in [this wiki article](https://github.com/tobrun/flutter-mapbox-gl/wiki/Mapbox-access-tokens). -```plist -io.flutter.embedded_views_preview - -MGLMapboxAccessToken -YOUR_TOKEN_HERE -``` +## Using the SDK in your project + +This project is available on [pub.dev](https://pub.dev/packages/mapbox_gl), follow the [instructions](https://flutter.dev/docs/development/packages-and-plugins/using-packages#adding-a-package-dependency-to-an-app) to integrate a package into your flutter application. For platform specific integration, use the flutter application under the example folder as reference. ## Supported API -| Feature | Android | iOS | -| ------ | ------ | ----- | -| Style | :white_check_mark: | :white_check_mark: | -| Camera | :white_check_mark: | :white_check_mark: | -| Gesture | :white_check_mark: | :white_check_mark: | -| User Location | :white_check_mark: | :white_check_mark: | -| Symbol | :white_check_mark: | :white_check_mark: | -| Circle | :white_check_mark: | :white_check_mark: | -| Line | :white_check_mark: | :white_check_mark: | -| Fill | | | +| Feature | Android | iOS | Web | +| ------ | ------ | ----- | ----- | +| Style | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Camera | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Gesture | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| User Location | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Symbol | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Circle | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Line | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Fill | | | | + +## Map Styles + +Map styles can be supplied by setting the `styleString` in the `MapOptions`. The following formats are supported: + +1. Passing the URL of the map style. This can be one of the built-in map styles, also see `MapboxStyles` or a custom map style served remotely using a URL that start with 'http(s)://' or 'mapbox://' +2. Passing the style as a local asset. Create a JSON file in the `assets` and add a reference in `pubspec.yml`. Set the style string to the relative path for this asset in order to load it into the map. +3. Passing the raw JSON of the map style. This is only supported on Android. ## Offline Sideloading @@ -81,6 +73,19 @@ Support for offline maps is available by *"side loading"* the required map tiles } ``` +## Location features + +To enable location features in an iOS application: + +If you access your users' location, you should also add the following key to your Info.plist to explain why you need access to their location data: + +```xml +NSLocationWhenInUseUsageDescription +[Your explanation here] +``` + +Mapbox [recommends](https://docs.mapbox.com/help/tutorials/first-steps-ios-sdk/#display-the-users-location) the explanation "Shows your location on the map and helps improve the map". + ## Documentation This README file currently houses all of the documentation for this Flutter project. Please visit [mapbox.com/android-docs](https://www.mapbox.com/android-docs/) if you'd like more information about the Mapbox Maps SDK for Android and [mapbox.com/ios-sdk](https://www.mapbox.com/ios-sdk/) for more information about the Mapbox Maps SDK for iOS. @@ -91,12 +96,7 @@ This README file currently houses all of the documentation for this Flutter proj - **Have a bug to report?** [Open an issue](https://github.com/tobrun/flutter-mapbox-gl/issues/new). If possible, include a full log and information which shows the issue. - **Have a feature request?** [Open an issue](https://github.com/tobrun/flutter-mapbox-gl/issues/new). Tell us what the feature should do and why you want the feature. -## Sample code - -[This repository's example library](https://github.com/tobrun/flutter-mapbox-gl/tree/master/example/lib) is currently the best place for you to find reference code for this project. ## Contributing -We welcome contributions to this repository! - -If you're interested in helping build this Mapbox/Flutter integration, please read [the contribution guide](https://github.com/tobrun/flutter-mapbox-gl/blob/master/CONTRIBUTING.md) to learn how to get started. +We welcome contributions to this repository! If you're interested in helping build this Mapbox/Flutter integration, please read [the contribution guide](https://github.com/tobrun/flutter-mapbox-gl/blob/master/CONTRIBUTING.md) to learn how to get started. diff --git a/android/build.gradle b/android/build.gradle index 2454ee42d..40393c18e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,9 +37,9 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } dependencies { - implementation "com.mapbox.mapboxsdk:mapbox-android-sdk:8.5.0" - implementation "com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v8:0.7.0" - implementation 'com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v8:0.11.0' + implementation "com.mapbox.mapboxsdk:mapbox-android-sdk:9.2.0" + implementation "com.mapbox.mapboxsdk:mapbox-android-plugin-annotation-v9:0.8.0" + implementation "com.mapbox.mapboxsdk:mapbox-android-plugin-localization-v9:0.12.0" } compileOptions { sourceCompatibility 1.8 diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index 85cd80e00..c42b1fecc 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -374,7 +374,7 @@ static void interpretSymbolOptions(Object o, SymbolOptionsSink sink) { if (geometry != null) { sink.setGeometry(toLatLng(geometry)); } - final Object symbolSortKey = data.get("symbolSortKey"); + final Object symbolSortKey = data.get("zIndex"); if (symbolSortKey != null) { sink.setSymbolSortKey(toFloat(symbolSortKey)); } diff --git a/android/src/main/java/com/mapbox/mapboxgl/LineController.java b/android/src/main/java/com/mapbox/mapboxgl/LineController.java index 44f9a3980..815401a55 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/LineController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/LineController.java @@ -6,6 +6,7 @@ package com.mapbox.mapboxgl; +import java.util.ArrayList; import java.util.List; import android.graphics.PointF; @@ -87,6 +88,15 @@ public void setGeometry(List geometry) { line.setLatLngs(geometry); } + public List getGeometry() { + List points = line.getGeometry().coordinates(); + List latLngs = new ArrayList<>(); + for (Point point : points) { + latLngs.add(new LatLng(point.latitude(), point.longitude())); + } + return latLngs; + } + @Override public void setDraggable(boolean draggable) { line.setDraggable(draggable); diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java index 86001c7fd..d6fc0a6c7 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapBuilder.java @@ -31,9 +31,9 @@ class MapboxMapBuilder implements MapboxMapOptionsSink { private String styleString = Style.MAPBOX_STREETS; MapboxMapController build( - int id, Context context, AtomicInteger state, PluginRegistry.Registrar registrar) { + int id, Context context, AtomicInteger state, PluginRegistry.Registrar registrar, String accessToken) { final MapboxMapController controller = - new MapboxMapController(id, context, state, registrar, options, styleString); + new MapboxMapController(id, context, state, registrar, options, accessToken, styleString); controller.init(); controller.setMyLocationEnabled(myLocationEnabled); controller.setMyLocationTrackingMode(myLocationTrackingMode); diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 53df290d7..ffe1e6d93 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -26,6 +26,9 @@ import androidx.annotation.NonNull; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.mapbox.android.core.location.LocationEngine; import com.mapbox.android.core.location.LocationEngineCallback; import com.mapbox.android.core.location.LocationEngineProvider; @@ -60,6 +63,7 @@ import com.mapbox.mapboxsdk.plugins.annotation.Line; import com.mapbox.mapboxsdk.plugins.annotation.LineManager; import com.mapbox.geojson.Feature; +import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; import com.mapbox.mapboxsdk.style.expressions.Expression; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; @@ -90,6 +94,7 @@ final class MapboxMapController MapboxMap.OnCameraMoveStartedListener, OnAnnotationClickListener, MapboxMap.OnMapClickListener, + MapboxMap.OnMapLongClickListener, MapboxMapOptionsSink, MethodChannel.MethodCallHandler, com.mapbox.mapboxsdk.maps.OnMapReadyCallback, @@ -124,6 +129,7 @@ final class MapboxMapController private LocationComponent locationComponent = null; private LocationEngine locationEngine = null; private LocalizationPlugin localizationPlugin; + private Style style; MapboxMapController( int id, @@ -131,8 +137,9 @@ final class MapboxMapController AtomicInteger activityState, PluginRegistry.Registrar registrar, MapboxMapOptions options, + String accessToken, String styleStringInitial) { - Mapbox.getInstance(context, getAccessToken(context)); + Mapbox.getInstance(context, accessToken!=null ? accessToken : getAccessToken(context)); this.id = id; this.context = context; this.activityState = activityState; @@ -224,17 +231,6 @@ private CameraPosition getCameraPosition() { return trackCameraPosition ? mapboxMap.getCameraPosition() : null; } - private SymbolBuilder newSymbolBuilder() { - return new SymbolBuilder(symbolManager); - } - - private void removeSymbol(String symbolId) { - final SymbolController symbolController = symbols.remove(symbolId); - if (symbolController != null) { - symbolController.remove(symbolManager); - } - } - private SymbolController symbol(String symbolId) { final SymbolController symbol = symbols.get(symbolId); if (symbol == null) { @@ -311,6 +307,14 @@ public void setStyleString(String styleString) { Log.e(TAG, "setStyleString - string empty or null"); } else if (styleString.startsWith("{") || styleString.startsWith("[")) { mapboxMap.setStyle(new Style.Builder().fromJson(styleString), onStyleLoadedCallback); + } else if ( + !styleString.startsWith("http://") && + !styleString.startsWith("https://")&& + !styleString.startsWith("mapbox://")) { + // We are assuming that the style will be loaded from an asset here. + AssetManager assetManager = registrar.context().getAssets(); + String key = registrar.lookupKeyForAsset(styleString); + mapboxMap.setStyle(new Style.Builder().fromUri("asset://" + key), onStyleLoadedCallback); } else { mapboxMap.setStyle(new Style.Builder().fromUrl(styleString), onStyleLoadedCallback); } @@ -319,6 +323,7 @@ public void setStyleString(String styleString) { Style.OnStyleLoaded onStyleLoadedCallback = new Style.OnStyleLoaded() { @Override public void onStyleLoaded(@NonNull Style style) { + MapboxMapController.this.style = style; enableLineManager(style); enableSymbolManager(style); enableCircleManager(style); @@ -328,6 +333,7 @@ public void onStyleLoaded(@NonNull Style style) { // needs to be placed after SymbolManager#addClickListener, // is fixed with 0.6.0 of annotations plugin mapboxMap.addOnMapClickListener(MapboxMapController.this); + mapboxMap.addOnMapLongClickListener(MapboxMapController.this); localizationPlugin = new LocalizationPlugin(mapView, mapboxMap, style); @@ -369,6 +375,8 @@ private void enableSymbolManager(@NonNull Style style) { } } + + private void enableLineManager(@NonNull Style style) { if (lineManager == null) { lineManager = new LineManager(mapView, mapboxMap, style); @@ -459,24 +467,28 @@ public void onCancel() { } case "camera#animate": { final CameraUpdate cameraUpdate = Convert.toCameraUpdate(call.argument("cameraUpdate"), mapboxMap, density); - if (cameraUpdate != null) { - // camera transformation not handled yet - mapboxMap.animateCamera(cameraUpdate, new OnCameraMoveFinishedListener(){ - @Override - public void onFinish() { - super.onFinish(); - result.success(true); - } + final Integer duration = call.argument("duration"); - @Override - public void onCancel() { - super.onCancel(); - result.success(false); - } - }); + final OnCameraMoveFinishedListener onCameraMoveFinishedListener = new OnCameraMoveFinishedListener(){ + @Override + public void onFinish() { + super.onFinish(); + result.success(true); + } - // animateCamera(cameraUpdate); - }else { + @Override + public void onCancel() { + super.onCancel(); + result.success(false); + } + }; + if (cameraUpdate != null && duration != null) { + // camera transformation not handled yet + mapboxMap.animateCamera(cameraUpdate, duration, onCameraMoveFinishedListener); + } else if (cameraUpdate != null) { + // camera transformation not handled yet + mapboxMap.animateCamera(cameraUpdate, onCameraMoveFinishedListener); + } else { result.success(false); } break; @@ -487,9 +499,13 @@ public void onCancel() { String[] layerIds = ((List) call.argument("layerIds")).toArray(new String[0]); - String filter = (String) call.argument("filter"); - - Expression filterExpression = filter == null ? null : new Expression(filter); + List filter = call.argument("filter"); + JsonElement jsonElement = filter == null ? null : new Gson().toJsonTree(filter); + JsonArray jsonArray = null; + if (jsonElement != null && jsonElement.isJsonArray()) { + jsonArray = jsonElement.getAsJsonArray(); + } + Expression filterExpression = jsonArray == null ? null : Expression.Converter.convert(jsonArray); if (call.hasArgument("x")) { Double x = call.argument("x"); Double y = call.argument("y"); @@ -538,18 +554,44 @@ public void onError(@NonNull String message) { }); break; } - case "symbol#add": { - final SymbolBuilder symbolBuilder = newSymbolBuilder(); - Convert.interpretSymbolOptions(call.argument("options"), symbolBuilder); - final Symbol symbol = symbolBuilder.build(); - final String symbolId = String.valueOf(symbol.getId()); - symbols.put(symbolId, new SymbolController(symbol, true, this)); - result.success(symbolId); + case "symbols#addAll": { + List newSymbolIds = new ArrayList(); + final List options = call.argument("options"); + List symbolOptionsList = new ArrayList(); + if (options != null) { + SymbolBuilder symbolBuilder; + for (Object o : options) { + symbolBuilder = new SymbolBuilder(); + Convert.interpretSymbolOptions(o, symbolBuilder); + symbolOptionsList.add(symbolBuilder.getSymbolOptions()); + } + if (!symbolOptionsList.isEmpty()) { + List newSymbols = symbolManager.create(symbolOptionsList); + String symbolId; + for (Symbol symbol : newSymbols) { + symbolId = String.valueOf(symbol.getId()); + newSymbolIds.add(symbolId); + symbols.put(symbolId, new SymbolController(symbol, true, this)); + } + } + } + result.success(newSymbolIds); break; } - case "symbol#remove": { - final String symbolId = call.argument("symbol"); - removeSymbol(symbolId); + case "symbols#removeAll": { + final ArrayList symbolIds = call.argument("symbols"); + SymbolController symbolController; + + List symbolList = new ArrayList(); + for(String symbolId : symbolIds){ + symbolController = symbols.remove(symbolId); + if (symbolController != null) { + symbolList.add(symbolController.getSymbol()); + } + } + if(!symbolList.isEmpty()) { + symbolManager.delete(symbolList); + } result.success(null); break; } @@ -561,6 +603,39 @@ public void onError(@NonNull String message) { result.success(null); break; } + case "symbol#getGeometry": { + final String symbolId = call.argument("symbol"); + final SymbolController symbol = symbol(symbolId); + final LatLng symbolLatLng = symbol.getGeometry(); + Map hashMapLatLng = new HashMap<>(); + hashMapLatLng.put("latitude", symbolLatLng.getLatitude()); + hashMapLatLng.put("longitude", symbolLatLng.getLongitude()); + result.success(hashMapLatLng); + } + case "symbolManager#iconAllowOverlap": { + final Boolean value = call.argument("iconAllowOverlap"); + symbolManager.setIconAllowOverlap(value); + result.success(null); + break; + } + case "symbolManager#iconIgnorePlacement": { + final Boolean value = call.argument("iconIgnorePlacement"); + symbolManager.setIconIgnorePlacement(value); + result.success(null); + break; + } + case "symbolManager#textAllowOverlap": { + final Boolean value = call.argument("textAllowOverlap"); + symbolManager.setTextAllowOverlap(value); + result.success(null); + break; + } + case "symbolManager#textIgnorePlacement": { + final Boolean iconAllowOverlap = call.argument("textIgnorePlacement"); + symbolManager.setTextIgnorePlacement(iconAllowOverlap); + result.success(null); + break; + } case "line#add": { final LineBuilder lineBuilder = newLineBuilder(); Convert.interpretLineOptions(call.argument("options"), lineBuilder); @@ -584,6 +659,20 @@ public void onError(@NonNull String message) { result.success(null); break; } + case "line#getGeometry": { + final String lineId = call.argument("line"); + final LineController line = line(lineId); + final List lineLatLngs = line.getGeometry(); + final List resultList = new ArrayList<>(); + for (LatLng latLng: lineLatLngs){ + Map hashMapLatLng = new HashMap<>(); + hashMapLatLng.put("latitude", latLng.getLatitude()); + hashMapLatLng.put("longitude", latLng.getLongitude()); + resultList.add(hashMapLatLng); + } + result.success(resultList); + break; + } case "circle#add": { final CircleBuilder circleBuilder = newCircleBuilder(); Convert.interpretCircleOptions(call.argument("options"), circleBuilder); @@ -644,6 +733,14 @@ public void onFailure(@NonNull Exception exception) { } break; } + case "style#addImage":{ + if(style==null){ + result.error("STYLE IS NULL", "The style is null. Has onStyleLoaded() already been invoked?", null); + } + style.addImage(call.argument("name"), BitmapFactory.decodeByteArray(call.argument("bytes"),0,call.argument("length")), call.argument("sdf")); + result.success(null); + break; + } default: result.notImplemented(); } @@ -742,6 +839,18 @@ public boolean onMapClick(@NonNull LatLng point) { return true; } + @Override + public boolean onMapLongClick(@NonNull LatLng point) { + PointF pointf = mapboxMap.getProjection().toScreenLocation(point); + final Map arguments = new HashMap<>(5); + arguments.put("x", pointf.x); + arguments.put("y", pointf.y); + arguments.put("lng", point.getLongitude()); + arguments.put("lat", point.getLatitude()); + methodChannel.invokeMethod("map#onMapLongClick", arguments); + return true; + } + @Override public void dispose() { if (disposed) { diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java index bc8299eca..532e53adc 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapFactory.java @@ -34,6 +34,6 @@ public PlatformView create(Context context, int id, Object args) { CameraPosition position = Convert.toCameraPosition(params.get("initialCameraPosition")); builder.setInitialCameraPosition(position); } - return builder.build(id, context, mActivityState, mPluginRegistrar); + return builder.build(id, context, mActivityState, mPluginRegistrar, (String) params.get("accessToken")); } } diff --git a/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java index d0c4cf01b..5f0583138 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/SymbolBuilder.java @@ -13,17 +13,15 @@ import com.mapbox.mapboxsdk.plugins.annotation.SymbolOptions; class SymbolBuilder implements SymbolOptionsSink { - private final SymbolManager symbolManager; private final SymbolOptions symbolOptions; private static boolean customImage; - SymbolBuilder(SymbolManager symbolManager) { - this.symbolManager = symbolManager; + SymbolBuilder() { this.symbolOptions = new SymbolOptions(); } - Symbol build() { - return symbolManager.create(symbolOptions); + public SymbolOptions getSymbolOptions(){ + return this.symbolOptions; } @Override diff --git a/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java b/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java index b3c95a4c9..bd101032f 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/SymbolController.java @@ -34,6 +34,10 @@ boolean onTap() { return consumeTapEvents; } + public Symbol getSymbol(){ + return this.symbol; + } + void remove(SymbolManager symbolManager) { symbolManager.delete(symbol); } @@ -168,6 +172,11 @@ public void setGeometry(LatLng geometry) { symbol.setGeometry(Point.fromLngLat(geometry.getLongitude(), geometry.getLatitude())); } + public LatLng getGeometry() { + Point point = symbol.getGeometry(); + return new LatLng(point.latitude(), point.longitude()); + } + @Override public void setDraggable(boolean draggable) { symbol.setDraggable(draggable); diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index b819886fb..a337bd737 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -17,7 +17,6 @@ android:name="io.flutter.app.FlutterApplication" android:label="mapbox_gl_example" android:icon="@mipmap/ic_launcher"> - - - ADD_MAPBOX_ACCESS_TOKEN_HERE - diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..f11e4c8d0 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle index bb8a30389..e0d7ae2c1 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.2.1' + classpath 'com.android.tools.build:gradle:3.5.0' } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 53ae0ae47..b6e61b62b 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,4 @@ android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M +android.enableR8=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 2819f022f..c8f6d36d0 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 23 08:50:38 CEST 2017 +#Mon May 25 15:33:34 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle new file mode 100644 index 000000000..e7b4def49 --- /dev/null +++ b/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/example/assets/style.json b/example/assets/style.json new file mode 100644 index 000000000..f16c075f6 --- /dev/null +++ b/example/assets/style.json @@ -0,0 +1,36 @@ +{ + "version": 8, + "name": "Example taken from https://docs.mapbox.com/ios/maps/examples/source-custom-vector/", + "sources": { + "mapillary": { + "type": "vector", + "tiles": [ + "https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt" + ], + "attribution": "© Mapillary, CC BY", + "maxzoom": 14 + } + }, + "layers": [{ + "id": "background", + "type": "background", + "paint": { + "background-color": "#485E77" + } + }, + { + "id": "mapillary-sequences", + "type": "line", + "source": "mapillary", + "source-layer": "mapillary-sequences", + "filter": [ + "==", + "$type", + "LineString" + ], + "paint": { + "line-color": "#F56745" + } + } + ] +} \ No newline at end of file diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 000000000..e96ef602b --- /dev/null +++ b/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c53701713..4b3022495 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,10 +10,6 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1C318FD9FE81A3CF826CB6E0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9C618A260D4CE68F2F89632 /* Pods_Runner.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -41,7 +35,6 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 33EB4B753D90FC406A268B9A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 647A9CC8EAD456F68D57F590 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 77F62DAA39FA47F19A7FF5D8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -49,7 +42,6 @@ 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -64,8 +56,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 1C318FD9FE81A3CF826CB6E0 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -84,9 +74,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -232,7 +220,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 61A6A5795B0A22D55417D672 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -262,9 +250,12 @@ "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/Mapbox.framework", "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/Mapbox.framework.dSYM", - "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/F6FDF133-0198-394E-9C8F-5043F94B4790.bcsymbolmap", - "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/B4615DAE-86F8-35AB-B4D1-B1F1420E374A.bcsymbolmap", + "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/4794199C-B164-3A2D-A3B4-553B7D49EDD5.bcsymbolmap", + "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/097AD13C-9FDA-310F-8B76-64CB67B06A27.bcsymbolmap", + "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/491A77E9-7DBC-3309-A93C-BADAE0DDBC6E.bcsymbolmap", + "${PODS_ROOT}/Mapbox-iOS-SDK/dynamic/3F114CA8-302D-327F-87C3-670E0EC63C9A.bcsymbolmap", "${BUILT_PRODUCTS_DIR}/MapboxAnnotationExtension/MapboxAnnotationExtension.framework", + "${BUILT_PRODUCTS_DIR}/MapboxMobileEvents/MapboxMobileEvents.framework", "${BUILT_PRODUCTS_DIR}/location/location.framework", "${BUILT_PRODUCTS_DIR}/mapbox_gl/mapbox_gl.framework", ); @@ -273,9 +264,12 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Mapbox.framework", "${DWARF_DSYM_FOLDER_PATH}/Mapbox.framework.dSYM", - "${BUILT_PRODUCTS_DIR}/F6FDF133-0198-394E-9C8F-5043F94B4790.bcsymbolmap", - "${BUILT_PRODUCTS_DIR}/B4615DAE-86F8-35AB-B4D1-B1F1420E374A.bcsymbolmap", + "${BUILT_PRODUCTS_DIR}/4794199C-B164-3A2D-A3B4-553B7D49EDD5.bcsymbolmap", + "${BUILT_PRODUCTS_DIR}/097AD13C-9FDA-310F-8B76-64CB67B06A27.bcsymbolmap", + "${BUILT_PRODUCTS_DIR}/491A77E9-7DBC-3309-A93C-BADAE0DDBC6E.bcsymbolmap", + "${BUILT_PRODUCTS_DIR}/3F114CA8-302D-327F-87C3-670E0EC63C9A.bcsymbolmap", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxAnnotationExtension.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MapboxMobileEvents.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/location.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/mapbox_gl.framework", ); @@ -335,7 +329,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -409,7 +402,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -465,7 +457,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 77% rename from example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings index 949b67898..f9b0d7c5e 100644 --- a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -2,7 +2,7 @@ - BuildSystemType - Original + PreviewsEnabled + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index 76be30434..5ef0ea0b3 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -43,13 +43,11 @@ io.flutter.embedded_views_preview - MGLMapboxAccessToken - YOUR_TOKEN_HERE MGLMapboxMetricsEnabledSettingShownInApp NSLocationWhenInUseUsageDescription - + Shows your location on the map and helps improve the map NSLocationAlwaysUsageDescription - + Shows your location on the map and helps improve the map diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/lib/animate_camera.dart b/example/lib/animate_camera.dart index 1ab45d28d..b0cdf4c5b 100644 --- a/example/lib/animate_camera.dart +++ b/example/lib/animate_camera.dart @@ -5,9 +5,10 @@ import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; -class AnimateCameraPage extends Page { +class AnimateCameraPage extends ExamplePage { AnimateCameraPage() : super(const Icon(Icons.map), 'Camera control, animated'); @@ -41,6 +42,7 @@ class AnimateCameraState extends State { width: 300.0, height: 200.0, child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), diff --git a/example/lib/full_map.dart b/example/lib/full_map.dart index ee9b42950..f1bf6a7be 100644 --- a/example/lib/full_map.dart +++ b/example/lib/full_map.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; -class FullMapPage extends Page { +class FullMapPage extends ExamplePage { FullMapPage() : super(const Icon(Icons.map), 'Full screen map'); @@ -31,6 +32,7 @@ class FullMapState extends State { Widget build(BuildContext context) { return new Scaffold( body: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), diff --git a/example/lib/generated_plugin_registrant.dart b/example/lib/generated_plugin_registrant.dart new file mode 100644 index 000000000..6f912e320 --- /dev/null +++ b/example/lib/generated_plugin_registrant.dart @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +// ignore: unused_import +import 'dart:ui'; + +import 'package:mapbox_gl_web/mapbox_gl_web.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +// ignore: public_member_api_docs +void registerPlugins(PluginRegistry registry) { + MapboxMapPlugin.registerWith(registry.registrarFor(MapboxMapPlugin)); + registry.registerMessageHandler(); +} diff --git a/example/lib/line.dart b/example/lib/line.dart index 7dc27adb8..f7f5d9709 100644 --- a/example/lib/line.dart +++ b/example/lib/line.dart @@ -7,9 +7,10 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; -class LinePage extends Page { +class LinePage extends ExamplePage { LinePage() : super(const Icon(Icons.share), 'Line'); @override @@ -79,6 +80,7 @@ class LineBodyState extends State { lineColor: "#ff0000", lineWidth: 14.0, lineOpacity: 0.5, + draggable: true ), ); setState(() { @@ -143,6 +145,7 @@ class LineBodyState extends State { width: 300.0, height: 200.0, child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, onStyleLoadedCallback: onStyleLoadedCallback, initialCameraPosition: const CameraPosition( @@ -183,6 +186,16 @@ class LineBodyState extends State { onPressed: (_selectedLine == null) ? null : _toggleVisible, ), + FlatButton( + child: const Text('print current LatLng'), + onPressed: + (_selectedLine == null) ? null : () async{ + var latLngs = await controller.getLineLatLngs(_selectedLine); + for (var latLng in latLngs) { + print(latLng.toString()); + } + }, + ), ], ), ], diff --git a/example/lib/main.dart b/example/lib/main.dart index 4fec3b987..081c97712 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:location/location.dart'; import 'package:mapbox_gl_example/full_map.dart'; @@ -16,7 +17,7 @@ import 'place_circle.dart'; import 'place_symbol.dart'; import 'scrolling_map.dart'; -final List _allPages = [ +final List _allPages = [ MapUiPage(), FullMapPage(), AnimateCameraPage(), @@ -28,13 +29,18 @@ final List _allPages = [ ]; class MapsDemo extends StatelessWidget { - void _pushPage(BuildContext context, Page page) async { - final location = Location(); - final hasPermissions = await location.hasPermission(); - if (!hasPermissions) { - await location.requestPermission(); - } + //FIXME: Add your Mapbox access token here + static const String ACCESS_TOKEN = "YOUR_TOKEN_HERE"; + + void _pushPage(BuildContext context, ExamplePage page) async { + if (!kIsWeb) { + final location = Location(); + final hasPermissions = await location.hasPermission(); + if (hasPermissions != PermissionStatus.GRANTED) { + await location.requestPermission(); + } + } Navigator.of(context).push(MaterialPageRoute( builder: (_) => Scaffold( appBar: AppBar(title: Text(page.title)), diff --git a/example/lib/map_ui.dart b/example/lib/map_ui.dart index b86df1d66..6e344495b 100644 --- a/example/lib/map_ui.dart +++ b/example/lib/map_ui.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; final LatLngBounds sydneyBounds = LatLngBounds( @@ -12,7 +13,7 @@ final LatLngBounds sydneyBounds = LatLngBounds( northeast: const LatLng(-33.571835, 151.325952), ); -class MapUiPage extends Page { +class MapUiPage extends ExamplePage { MapUiPage() : super(const Icon(Icons.map), 'User interface'); @override @@ -42,7 +43,11 @@ class MapUiBodyState extends State { bool _compassEnabled = true; CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded; MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded; - String _styleString = MapboxStyles.MAPBOX_STREETS; + int _styleStringIndex = 0; + // Style string can a reference to a local or remote resources. + // On Android the raw JSON can also be passed via a styleString, on iOS this is not supported. + List _styleStrings = [MapboxStyles.MAPBOX_STREETS, MapboxStyles.SATELLITE, "assets/style.json"]; + List _styleStringLabels = ["MAPBOX_STREETS", "SATELLITE", "LOCAL_ASSET"]; bool _rotateGesturesEnabled = true; bool _scrollGesturesEnabled = true; bool _tiltGesturesEnabled = true; @@ -50,6 +55,7 @@ class MapUiBodyState extends State { bool _myLocationEnabled = true; bool _telemetryEnabled = true; MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.Tracking; + List _featureQueryFilter; @override void initState() { @@ -86,6 +92,21 @@ class MapUiBodyState extends State { ); } + Widget _queryFilterToggler() { + return FlatButton( + child: Text('filter zoo on click ${ _featureQueryFilter == null ? 'disabled' : 'enabled'}'), + onPressed: () { + setState(() { + if (_featureQueryFilter == null) { + _featureQueryFilter = ["==", ["get", "type"] , "zoo"]; + } else { + _featureQueryFilter = null; + } + }); + }, + ); + } + Widget _compassToggler() { return FlatButton( child: Text('${_compassEnabled ? 'disable' : 'enable'} compasss'), @@ -131,10 +152,10 @@ class MapUiBodyState extends State { Widget _setStyleToSatellite() { return FlatButton( - child: Text('change map style to Satellite'), + child: Text('change map style to ${_styleStringLabels[(_styleStringIndex + 1) % _styleStringLabels.length]}'), onPressed: () { setState(() { - _styleString = MapboxStyles.SATELLITE; + _styleStringIndex = (_styleStringIndex + 1) % _styleStrings.length; }); }, ); @@ -220,13 +241,14 @@ class MapUiBodyState extends State { @override Widget build(BuildContext context) { final MapboxMap mapboxMap = MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: onMapCreated, initialCameraPosition: _kInitialPosition, trackCameraPosition: true, compassEnabled: _compassEnabled, cameraTargetBounds: _cameraTargetBounds, minMaxZoomPreference: _minMaxZoomPreference, - styleString: _styleString, + styleString: _styleStrings[_styleStringIndex], rotateGesturesEnabled: _rotateGesturesEnabled, scrollGesturesEnabled: _scrollGesturesEnabled, tiltGesturesEnabled: _tiltGesturesEnabled, @@ -235,8 +257,16 @@ class MapUiBodyState extends State { myLocationTrackingMode: _myLocationTrackingMode, myLocationRenderMode: MyLocationRenderMode.GPS, onMapClick: (point, latLng) async { - print("${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); - List features = await mapController.queryRenderedFeatures(point, [],null); + print("Map click: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); + print("Filter $_featureQueryFilter"); + List features = await mapController.queryRenderedFeatures(point, [], _featureQueryFilter); + if (features.length>0) { + print(features[0]); + } + }, + onMapLongClick: (point, latLng) async { + print("Map long press: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); + List features = await mapController.queryRenderedFeatures(point, [], null); if (features.length>0) { print(features[0]); } @@ -273,6 +303,7 @@ class MapUiBodyState extends State { Text('camera zoom: ${_position.zoom}'), Text('camera tilt: ${_position.tilt}'), Text(_isMoving ? '(Camera moving)' : '(Camera idle)'), + _queryFilterToggler(), _compassToggler(), _myLocationTrackingModeCycler(), _latLngBoundsToggler(), diff --git a/example/lib/move_camera.dart b/example/lib/move_camera.dart index ea42b022e..3805b0200 100644 --- a/example/lib/move_camera.dart +++ b/example/lib/move_camera.dart @@ -5,9 +5,10 @@ import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; -class MoveCameraPage extends Page { +class MoveCameraPage extends ExamplePage { MoveCameraPage() : super(const Icon(Icons.map), 'Camera control'); @override @@ -40,7 +41,9 @@ class MoveCameraState extends State { width: 300.0, height: 200.0, child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, + onCameraIdle: ()=>print("onCameraIdle"), initialCameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)), ), diff --git a/example/lib/page.dart b/example/lib/page.dart index c9f834b77..ab895ac4e 100644 --- a/example/lib/page.dart +++ b/example/lib/page.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; -abstract class Page extends StatelessWidget { - const Page(this.leading, this.title); +abstract class ExamplePage extends StatelessWidget { + const ExamplePage(this.leading, this.title); final Widget leading; final String title; diff --git a/example/lib/place_circle.dart b/example/lib/place_circle.dart index e596ba45f..bc4a17541 100644 --- a/example/lib/place_circle.dart +++ b/example/lib/place_circle.dart @@ -8,9 +8,10 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; -class PlaceCirclePage extends Page { +class PlaceCirclePage extends ExamplePage { PlaceCirclePage() : super(const Icon(Icons.check_circle), 'Place circle'); @override @@ -217,6 +218,7 @@ class PlaceCircleBodyState extends State { width: 300.0, height: 200.0, child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), diff --git a/example/lib/place_symbol.dart b/example/lib/place_symbol.dart index 3e06d31ff..6ec86719b 100644 --- a/example/lib/place_symbol.dart +++ b/example/lib/place_symbol.dart @@ -4,13 +4,17 @@ import 'dart:async'; import 'dart:math'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; -class PlaceSymbolPage extends Page { +class PlaceSymbolPage extends ExamplePage { PlaceSymbolPage() : super(const Icon(Icons.place), 'Place symbol'); @override @@ -34,18 +38,37 @@ class PlaceSymbolBodyState extends State { MapboxMapController controller; int _symbolCount = 0; Symbol _selectedSymbol; + bool _iconAllowOverlap = false; void _onMapCreated(MapboxMapController controller) { this.controller = controller; controller.onSymbolTapped.add(_onSymbolTapped); } + void _onStyleLoaded() { + addImageFromAsset("assetImage", "assets/symbols/custom-icon.png"); + addImageFromUrl("networkImage", "https://via.placeholder.com/50"); + } + @override void dispose() { controller?.onSymbolTapped?.remove(_onSymbolTapped); super.dispose(); } + /// Adds an asset image to the currently displayed style + Future addImageFromAsset(String name, String assetName) async { + final ByteData bytes = await rootBundle.load(assetName); + final Uint8List list = bytes.buffer.asUint8List(); + return controller.addImage(name, list); + } + + /// Adds a network image to the currently displayed style + Future addImageFromUrl(String name, String url) async { + var response = await get(url); + return controller.addImage(name, response.bodyBytes); + } + void _onSymbolTapped(Symbol symbol) { if (_selectedSymbol != null) { _updateSelectedSymbol( @@ -67,18 +90,50 @@ class PlaceSymbolBodyState extends State { } void _add(String iconImage) { - controller.addSymbol( - SymbolOptions( - geometry: LatLng( - center.latitude + sin(_symbolCount * pi / 6.0) / 20.0, - center.longitude + cos(_symbolCount * pi / 6.0) / 20.0, - ), - iconImage: iconImage, + List availableNumbers = Iterable.generate(12).toList(); + controller.symbols.forEach( + (s) => availableNumbers.removeWhere((i) => i == s.data['count']) + ); + if (availableNumbers.isNotEmpty) { + controller.addSymbol( + _getSymbolOptions(iconImage, availableNumbers.first), + {'count': availableNumbers.first} + ); + setState(() { + _symbolCount += 1; + }); + } + } + + SymbolOptions _getSymbolOptions(String iconImage, int symbolCount){ + return SymbolOptions( + geometry: LatLng( + center.latitude + sin(symbolCount * pi / 6.0) / 20.0, + center.longitude + cos(symbolCount * pi / 6.0) / 20.0, ), + iconImage: iconImage, ); - setState(() { - _symbolCount += 1; - }); + } + + Future _addAll(String iconImage) async { + List symbolsToAddNumbers = Iterable.generate(12).toList(); + controller.symbols.forEach( + (s) => symbolsToAddNumbers.removeWhere((i) => i == s.data['count']) + ); + + if (symbolsToAddNumbers.isNotEmpty) { + final List symbolOptionsList = symbolsToAddNumbers.map( + (i) => _getSymbolOptions(iconImage, i) + ).toList(); + controller.addSymbols( + symbolOptionsList, + symbolsToAddNumbers.map((i) => {'count': i}).toList() + ); + + setState(() { + _symbolCount += symbolOptionsList.length; + }); + } } void _remove() { @@ -89,6 +144,14 @@ class PlaceSymbolBodyState extends State { }); } + void _removeAll() { + controller.removeSymbols(controller.symbols); + setState(() { + _selectedSymbol = null; + _symbolCount = 0; + }); + } + void _changePosition() { final LatLng current = _selectedSymbol.options.geometry; final Offset offset = Offset( @@ -185,6 +248,22 @@ class PlaceSymbolBodyState extends State { ); } + void _getLatLng() async { + LatLng latLng = await controller.getSymbolLatLng(_selectedSymbol); + Scaffold.of(context).showSnackBar( + SnackBar( + content: Text(latLng.toString()), + ), + ); + } + + Future _changeIconOverlap() async { + setState(() { + _iconAllowOverlap = !_iconAllowOverlap; + }); + controller.setSymbolIconAllowOverlap(_iconAllowOverlap); + } + @override Widget build(BuildContext context) { return Column( @@ -196,7 +275,9 @@ class PlaceSymbolBodyState extends State { width: 300.0, height: 200.0, child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, + onStyleLoadedCallback: _onStyleLoaded, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), zoom: 11.0, @@ -218,6 +299,11 @@ class PlaceSymbolBodyState extends State { onPressed: () => (_symbolCount == 12) ? null : _add("airport-15"), ), + FlatButton( + child: const Text('add all'), + onPressed: () => + (_symbolCount == 12) ? null : _addAll("airport-15"), + ), FlatButton( child: const Text('add (custom icon)'), onPressed: () => (_symbolCount == 12) @@ -228,6 +314,26 @@ class PlaceSymbolBodyState extends State { child: const Text('remove'), onPressed: (_selectedSymbol == null) ? null : _remove, ), + FlatButton( + child: Text('${_iconAllowOverlap ? 'disable' : 'enable'} icon overlap'), + onPressed: _changeIconOverlap, + ), + FlatButton( + child: const Text('remove all'), + onPressed: (_symbolCount == 0) ? null : _removeAll, + ), + FlatButton( + child: const Text('add (asset image)'), + onPressed: () => (_symbolCount == 12) + ? null + : _add( + "assetImage"), //assetImage added to the style in _onStyleLoaded + ), + FlatButton( + child: const Text('add (network image)'), + onPressed: () => + (_symbolCount == 12) ? null : _add("networkImage"), //networkImage added to the style in _onStyleLoaded + ), ], ), Column( @@ -239,8 +345,9 @@ class PlaceSymbolBodyState extends State { ), FlatButton( child: const Text('change icon offset'), - onPressed: - (_selectedSymbol == null) ? null : _changeIconOffset, + onPressed: (_selectedSymbol == null) + ? null + : _changeIconOffset, ), FlatButton( child: const Text('change icon anchor'), @@ -276,6 +383,12 @@ class PlaceSymbolBodyState extends State { onPressed: (_selectedSymbol == null) ? null : _changeZIndex, ), + FlatButton( + child: const Text('get current LatLng'), + onPressed: (_selectedSymbol == null) + ? null + : _getLatLng, + ), ], ), ], diff --git a/example/lib/scrolling_map.dart b/example/lib/scrolling_map.dart index d31cf9dba..e1d693dc1 100644 --- a/example/lib/scrolling_map.dart +++ b/example/lib/scrolling_map.dart @@ -8,19 +8,28 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'main.dart'; import 'page.dart'; -class ScrollingMapPage extends Page { +class ScrollingMapPage extends ExamplePage { ScrollingMapPage() : super(const Icon(Icons.map), 'Scrolling map'); @override Widget build(BuildContext context) { - return const ScrollingMapBody(); + return ScrollingMapBody(); } } -class ScrollingMapBody extends StatelessWidget { - const ScrollingMapBody(); +class ScrollingMapBody extends StatefulWidget { + ScrollingMapBody(); + + @override + _ScrollingMapBodyState createState() => _ScrollingMapBodyState(); +} + +class _ScrollingMapBodyState extends State { + MapboxMapController controllerOne; + MapboxMapController controllerTwo; final LatLng center = const LatLng(32.080664, 34.9563837); @@ -42,7 +51,9 @@ class ScrollingMapBody extends StatelessWidget { width: 300.0, height: 300.0, child: MapboxMap( - onMapCreated: onMapCreated, + accessToken: MapsDemo.ACCESS_TOKEN, + onMapCreated: onMapCreatedOne, + onStyleLoadedCallback: () => onStyleLoaded(controllerOne), initialCameraPosition: CameraPosition( target: center, zoom: 11.0, @@ -76,7 +87,9 @@ class ScrollingMapBody extends StatelessWidget { width: 300.0, height: 300.0, child: MapboxMap( - onMapCreated: onMapCreated, + accessToken: MapsDemo.ACCESS_TOKEN, + onMapCreated: onMapCreatedTwo, + onStyleLoadedCallback: () => onStyleLoaded(controllerTwo), initialCameraPosition: CameraPosition( target: center, zoom: 11.0, @@ -98,7 +111,15 @@ class ScrollingMapBody extends StatelessWidget { ); } - void onMapCreated(MapboxMapController controller) { + void onMapCreatedOne(MapboxMapController controller) { + this.controllerOne = controller; + } + + void onMapCreatedTwo(MapboxMapController controller) { + this.controllerTwo = controller; + } + + void onStyleLoaded(MapboxMapController controller) { controller.addSymbol(SymbolOptions( geometry: LatLng( center.latitude, diff --git a/example/macos/.gitignore b/example/macos/.gitignore new file mode 100644 index 000000000..d2fd37723 --- /dev/null +++ b/example/macos/.gitignore @@ -0,0 +1,6 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/xcuserdata/ diff --git a/example/macos/Podfile b/example/macos/Podfile new file mode 100644 index 000000000..d60ec7102 --- /dev/null +++ b/example/macos/Podfile @@ -0,0 +1,82 @@ +platform :osx, '10.11' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary +end + +def pubspec_supports_macos(file) + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return false; + end + File.foreach(file_abs_path) { |line| + return true if line =~ /^\s*macos:/ + } + return false +end + +target 'Runner' do + use_frameworks! + use_modular_headers! + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + ephemeral_dir = File.join('Flutter', 'ephemeral') + symlink_dir = File.join(ephemeral_dir, '.symlinks') + symlink_plugins_dir = File.join(symlink_dir, 'plugins') + system("rm -rf #{symlink_dir}") + system("mkdir -p #{symlink_plugins_dir}") + + # Flutter Pods + generated_xcconfig = parse_KV_file(File.join(ephemeral_dir, 'Flutter-Generated.xcconfig')) + if generated_xcconfig.empty? + puts "Flutter-Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." + end + generated_xcconfig.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join(symlink_dir, 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'FlutterMacOS', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join(symlink_plugins_dir, p[:name]) + File.symlink(p[:path], symlink) + if pubspec_supports_macos(File.join(symlink, 'pubspec.yaml')) + pod p[:name], :path => File.join(symlink, 'macos') + end + } +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..331479d39 --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,596 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; + 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; + D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */, + 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */, + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, + 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + D73912EF22F37F9E000D13A0 /* App.framework */, + 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = "The Flutter Authors"; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter/ephemeral", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter/ephemeral", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter/ephemeral", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..764c74b8d --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..df12c333e --- /dev/null +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..d53ef6437 --- /dev/null +++ b/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a2ec33f19 --- /dev/null +++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 000000000..3c4935a7c Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 000000000..ed4cc1642 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 000000000..483be6138 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 000000000..bcbf36df2 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 000000000..9c0a65286 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 000000000..e71a72613 Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 000000000..8a31fe2dd Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/example/macos/Runner/Base.lproj/MainMenu.xib b/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 000000000..537341abf --- /dev/null +++ b/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..4fa23d759 --- /dev/null +++ b/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.mapbox.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2020 com.mapbox. All rights reserved. diff --git a/example/macos/Runner/Configs/Debug.xcconfig b/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Release.xcconfig b/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/example/macos/Runner/Configs/Warnings.xcconfig b/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000..dddb8a30c --- /dev/null +++ b/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist new file mode 100644 index 000000000..4789daa6a --- /dev/null +++ b/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..2722837ec --- /dev/null +++ b/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000..852fa1a47 --- /dev/null +++ b/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 95c648eb6..e79256f80 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -12,7 +12,14 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 - location: ^2.3.5 + location: ^2.5.3 + http: + +dependency_overrides: + mapbox_gl_platform_interface: + path: ../mapbox_gl_platform_interface + mapbox_gl_web: + path: ../mapbox_gl_web dev_dependencies: flutter_test: @@ -44,6 +51,7 @@ flutter: - assets/symbols/custom-icon.png - assets/symbols/2.0x/custom-icon.png - assets/symbols/3.0x/custom-icon.png + - assets/style.json # For details regarding adding assets from package dependencies, see # https://flutter.io/assets-and-images/#from-packages diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/example/web/favicon.png differ diff --git a/example/web/icons/Icon-192.png b/example/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/example/web/icons/Icon-192.png differ diff --git a/example/web/icons/Icon-512.png b/example/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/example/web/icons/Icon-512.png differ diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 000000000..0ece3caab --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 000000000..c63800102 --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "minimal-ui", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/ios/Classes/Convert.swift b/ios/Classes/Convert.swift index 84b6c4de8..e7dfd85bb 100644 --- a/ios/Classes/Convert.swift +++ b/ios/Classes/Convert.swift @@ -37,6 +37,9 @@ class Convert { if let myLocationTrackingMode = options["myLocationTrackingMode"] as? UInt, let trackingMode = MGLUserTrackingMode(rawValue: myLocationTrackingMode) { delegate.setMyLocationTrackingMode(myLocationTrackingMode: trackingMode) } + if let myLocationRenderMode = options["myLocationRenderMode"] as? Int, let renderMode = MyLocationRenderMode(rawValue: myLocationRenderMode) { + delegate.setMyLocationRenderMode(myLocationRenderMode: renderMode) + } if let logoViewMargins = options["logoViewMargins"] as? [Double] { delegate.setLogoViewMargins(x: logoViewMargins[0], y: logoViewMargins[1]) } diff --git a/ios/Classes/Enums.swift b/ios/Classes/Enums.swift new file mode 100644 index 000000000..6d621d344 --- /dev/null +++ b/ios/Classes/Enums.swift @@ -0,0 +1,3 @@ +enum MyLocationRenderMode: Int { + case Normal, Compass, Gps +} diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index f663cb5fc..6384ee51a 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -26,6 +26,11 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } init(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, registrar: FlutterPluginRegistrar) { + if let args = args as? [String: Any] { + if let token = args["accessToken"] as? NSString{ + MGLAccountManager.accessToken = token + } + } mapView = MGLMapView(frame: frame) mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.registrar = registrar @@ -43,6 +48,12 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } mapView.addGestureRecognizer(singleTap) + let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleMapLongPress(sender:))) + for recognizer in mapView.gestureRecognizers! where recognizer is UILongPressGestureRecognizer { + longPress.require(toFail: recognizer) + } + mapView.addGestureRecognizer(longPress) + if let args = args as? [String: Any] { Convert.interpretMapboxMapOptions(options: args["options"], delegate: self) if let initialCameraPosition = args["initialCameraPosition"] as? [String: Any], @@ -112,6 +123,34 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma style.localizeLabels(into: locale) } result(nil) + case "map#queryRenderedFeatures": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + let layerIds = arguments["layerIds"] as? Set + var filterExpression: NSPredicate? + if let filter = arguments["filter"] as? [Any] { + filterExpression = NSPredicate(mglJSONObject: filter) + } + var reply = [String: NSObject]() + var features:[MGLFeature] = [] + if let x = arguments["x"] as? Double, let y = arguments["y"] as? Double { + features = mapView.visibleFeatures(at: CGPoint(x: x, y: y), styleLayerIdentifiers: layerIds, predicate: filterExpression) + } + if let top = arguments["top"] as? Double, + let bottom = arguments["bottom"] as? Double, + let left = arguments["left"] as? Double, + let right = arguments["right"] as? Double { + features = mapView.visibleFeatures(in: CGRect(x: left, y: top, width: right, height: bottom), styleLayerIdentifiers: layerIds, predicate: filterExpression) + } + var featuresJson = [String]() + for feature in features { + let dictionary = feature.geoJSONDictionary() + if let theJSONData = try? JSONSerialization.data(withJSONObject: dictionary, options: []), + let theJSONText = String(data: theJSONData, encoding: .ascii) { + featuresJson.append(theJSONText) + } + } + reply["features"] = featuresJson as NSObject + result(reply) case "map#setTelemetryEnabled": guard let arguments = methodCall.arguments as? [String: Any] else { return } let telemetryEnabled = arguments["enabled"] as? Bool @@ -137,26 +176,30 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let cameraUpdate = arguments["cameraUpdate"] as? [Any] else { return } if let camera = Convert.parseCameraUpdate(cameraUpdate: cameraUpdate, mapView: mapView) { + if let duration = arguments["duration"] as? TimeInterval { + mapView.setCamera(camera, withDuration: TimeInterval(duration / 1000), + animationTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) + result(nil) + } mapView.setCamera(camera, animated: true) } result(nil) - case "symbol#add": + case "symbols#addAll": guard let symbolAnnotationController = symbolAnnotationController else { return } guard let arguments = methodCall.arguments as? [String: Any] else { return } - - // Parse geometry - if let options = arguments["options"] as? [String: Any], - let geometry = options["geometry"] as? [Double] { - // Convert geometry to coordinate and create symbol. - let coordinate = CLLocationCoordinate2DMake(geometry[0], geometry[1]) - let symbol = MGLSymbolStyleAnnotation(coordinate: coordinate) - Convert.interpretSymbolOptions(options: arguments["options"], delegate: symbol) - // Load icon image from asset if an icon name is supplied. - if let iconImage = options["iconImage"] as? String { - addIconImageToMap(iconImageName: iconImage) + + if let options = arguments["options"] as? [[String: Any]] { + var symbols: [MGLSymbolStyleAnnotation] = []; + for o in options { + if let symbol = getSymbolForOptions(options: o) { + symbols.append(symbol) + } } - symbolAnnotationController.addStyleAnnotation(symbol) - result(symbol.identifier) + if !symbols.isEmpty { + symbolAnnotationController.addStyleAnnotations(symbols) + } + + result(symbols.map { $0.identifier }) } else { result(nil) } @@ -178,18 +221,58 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } result(nil) - case "symbol#remove": + case "symbols#removeAll": + guard let symbolAnnotationController = symbolAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let symbolIds = arguments["symbols"] as? [String] else { return } + var symbols: [MGLSymbolStyleAnnotation] = []; + + for symbol in symbolAnnotationController.styleAnnotations(){ + if symbolIds.contains(symbol.identifier) { + symbols.append(symbol as! MGLSymbolStyleAnnotation) + } + } + symbolAnnotationController.removeStyleAnnotations(symbols) + result(nil) + case "symbol#getGeometry": guard let symbolAnnotationController = symbolAnnotationController else { return } guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let symbolId = arguments["symbol"] as? String else { return } + var reply: [String:Double]? = nil for symbol in symbolAnnotationController.styleAnnotations(){ if symbol.identifier == symbolId { - symbolAnnotationController.removeStyleAnnotation(symbol) + if let geometry = symbol.geoJSONDictionary["geometry"] as? [String: Any], + let coordinates = geometry["coordinates"] as? [Double] { + reply = ["latitude": coordinates[1], "longitude": coordinates[0]] + } break; } } + result(reply) + case "symbolManager#iconAllowOverlap": + guard let symbolAnnotationController = symbolAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let iconAllowOverlap = arguments["iconAllowOverlap"] as? Bool else { return } + + symbolAnnotationController.iconAllowsOverlap = iconAllowOverlap result(nil) + case "symbolManager#iconIgnorePlacement": + guard let symbolAnnotationController = symbolAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let iconIgnorePlacement = arguments["iconIgnorePlacement"] as? Bool else { return } + + symbolAnnotationController.iconIgnoresPlacement = iconIgnorePlacement + result(nil) + case "symbolManager#textAllowOverlap": + guard let symbolAnnotationController = symbolAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let textAllowOverlap = arguments["textAllowOverlap"] as? Bool else { return } + + symbolAnnotationController.textAllowsOverlap = textAllowOverlap + result(nil) + case "symbolManager#textIgnorePlacement": + result(FlutterMethodNotImplemented) case "circle#add": guard let circleAnnotationController = circleAnnotationController else { return } guard let arguments = methodCall.arguments as? [String: Any] else { return } @@ -273,11 +356,57 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } result(nil) + case "line#getGeometry": + guard let lineAnnotationController = lineAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let lineId = arguments["line"] as? String else { return } + + var reply: [Any]? = nil + for line in lineAnnotationController.styleAnnotations() { + if line.identifier == lineId { + if let geometry = line.geoJSONDictionary["geometry"] as? [String: Any], + let coordinates = geometry["coordinates"] as? [[Double]] { + reply = coordinates.map { [ "latitude": $0[1], "longitude": $0[0] ] } + } + break; + } + } + result(reply) + case "style#addImage": + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let name = arguments["name"] as? String else { return } + //guard let length = arguments["length"] as? NSNumber else { return } + guard let bytes = arguments["bytes"] as? FlutterStandardTypedData else { return } + guard let sdf = arguments["sdf"] as? Bool else { return } + guard let data = bytes.data as? Data else{ return } + guard let image = UIImage(data: data) else { return } + if (sdf) { + self.mapView.style?.setImage(image.withRenderingMode(.alwaysTemplate), forName: name) + } else { + self.mapView.style?.setImage(image, forName: name) + } + result(nil) default: result(FlutterMethodNotImplemented) } } + private func getSymbolForOptions(options: [String: Any]) -> MGLSymbolStyleAnnotation? { + // Parse geometry + if let geometry = options["geometry"] as? [Double] { + // Convert geometry to coordinate and create symbol. + let coordinate = CLLocationCoordinate2DMake(geometry[0], geometry[1]) + let symbol = MGLSymbolStyleAnnotation(coordinate: coordinate) + Convert.interpretSymbolOptions(options: options, delegate: symbol) + // Load icon image from asset if an icon name is supplied. + if let iconImage = options["iconImage"] as? String { + addIconImageToMap(iconImageName: iconImage) + } + return symbol + } + return nil + } + private func addIconImageToMap(iconImageName: String) { // Check if the image has already been added to the map. if self.mapView.style?.image(forName: iconImageName) == nil { @@ -320,6 +449,28 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma ]) } + /* + * UILongPressGestureRecognizer + * After a long press invoke the map#onMapLongClick callback. + */ + @objc @IBAction func handleMapLongPress(sender: UILongPressGestureRecognizer) { + //Fire when the long press starts + if (sender.state == .began) { + // Get the CGPoint where the user tapped. + let point = sender.location(in: mapView) + let coordinate = mapView.convert(point, toCoordinateFrom: mapView) + channel?.invokeMethod("map#onMapLongClick", arguments: [ + "x": point.x, + "y": point.y, + "lng": coordinate.longitude, + "lat": coordinate.latitude, + ]) + } + + } + + + /* * MGLAnnotationControllerDelegate */ @@ -427,7 +578,6 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma if let symbol = annotation as? Symbol { channel?.invokeMethod("symbol#onTap", arguments: ["symbol" : "\(symbol.id)"]) - } } @@ -493,6 +643,13 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } else if (styleString.hasPrefix("{") || styleString.hasPrefix("[")) { // Currently the iOS Mapbox SDK does not have a builder for json. NSLog("setStyleString - JSON style currently not supported") + } else if ( + !styleString.hasPrefix("http://") && + !styleString.hasPrefix("https://") && + !styleString.hasPrefix("mapbox://")) { + // We are assuming that the style will be loaded from an asset here. + let assetPath = registrar.lookupKey(forAsset: styleString) + mapView.styleURL = URL(string: assetPath, relativeTo: Bundle.main.resourceURL) } else { mapView.styleURL = URL(string: styleString) } @@ -522,6 +679,16 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma func setMyLocationTrackingMode(myLocationTrackingMode: MGLUserTrackingMode) { mapView.userTrackingMode = myLocationTrackingMode } + func setMyLocationRenderMode(myLocationRenderMode: MyLocationRenderMode) { + switch myLocationRenderMode { + case .Normal: + mapView.showsUserHeadingIndicator = false + case .Compass: + mapView.showsUserHeadingIndicator = true + case .Gps: + NSLog("RenderMode.GPS currently not supported") + } + } func setLogoViewMargins(x: Double, y: Double) { mapView.logoViewMargins = CGPoint(x: x, y: y) } diff --git a/ios/Classes/MapboxMapOptionsSink.swift b/ios/Classes/MapboxMapOptionsSink.swift index 6ce226314..2cd2cf746 100644 --- a/ios/Classes/MapboxMapOptionsSink.swift +++ b/ios/Classes/MapboxMapOptionsSink.swift @@ -13,6 +13,7 @@ protocol MapboxMapOptionsSink { func setZoomGesturesEnabled(zoomGesturesEnabled: Bool) func setMyLocationEnabled(myLocationEnabled: Bool) func setMyLocationTrackingMode(myLocationTrackingMode: MGLUserTrackingMode) + func setMyLocationRenderMode(myLocationRenderMode: MyLocationRenderMode) func setLogoViewMargins(x: Double, y: Double) func setCompassViewPosition(position: MGLOrnamentPosition) func setCompassViewMargins(x: Double, y: Double) diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index 83c0b15e5..b856b363d 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -6,21 +6,34 @@ library mapbox_gl; import 'dart:async'; import 'dart:math'; -import 'dart:ui'; +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart'; + +export 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart' + show + LatLng, + LatLngBounds, + CameraPosition, + CameraUpdate, + ArgumentCallbacks, + Symbol, + SymbolOptions, + CameraTargetBounds, + MinMaxZoomPreference, + MapboxStyles, + MyLocationTrackingMode, + MyLocationRenderMode, + Circle, + CircleOptions, + Line, + LineOptions; part 'src/bitmap.dart'; -part 'src/callbacks.dart'; -part 'src/camera.dart'; part 'src/controller.dart'; part 'src/mapbox_map.dart'; -part 'src/location.dart'; -part 'src/symbol.dart'; -part 'src/line.dart'; -part 'src/circle.dart'; -part 'src/ui.dart'; -part 'src/global.dart'; \ No newline at end of file +part 'src/global.dart'; diff --git a/lib/src/controller.dart b/lib/src/controller.dart index ccca05c46..83ed1f2a5 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -5,12 +5,15 @@ part of mapbox_gl; typedef void OnMapClickCallback(Point point, LatLng coordinates); +typedef void OnMapLongClickCallback(Point point, LatLng coordinates); typedef void OnStyleLoadedCallback(); typedef void OnCameraTrackingDismissedCallback(); typedef void OnCameraTrackingChangedCallback(MyLocationTrackingMode mode); +typedef void OnCameraIdleCallback(); + typedef void OnMapIdleCallback(); /// Controller for a single MapboxMap instance running on the host platform. @@ -29,48 +32,139 @@ typedef void OnMapIdleCallback(); /// Line tap events can be received by adding callbacks to [onLineTapped]. /// Circle tap events can be received by adding callbacks to [onCircleTapped]. class MapboxMapController extends ChangeNotifier { - MapboxMapController._( - this._id, MethodChannel channel, CameraPosition initialCameraPosition, + MapboxMapController._(this._id, CameraPosition initialCameraPosition, {this.onStyleLoadedCallback, this.onMapClick, + this.onMapLongClick, this.onCameraTrackingDismissed, this.onCameraTrackingChanged, + this.onCameraIdle, this.onMapIdle}) - : assert(_id != null), - assert(channel != null), - _channel = channel { + : assert(_id != null) { _cameraPosition = initialCameraPosition; - _channel.setMethodCallHandler(_handleMethodCall); + + MapboxGlPlatform.getInstance(_id) + .onInfoWindowTappedPlatform + .add((symbolId) { + final Symbol symbol = _symbols[symbolId]; + if (symbol != null) { + onInfoWindowTapped(symbol); + } + }); + + MapboxGlPlatform.getInstance(_id).onSymbolTappedPlatform.add((symbolId) { + final Symbol symbol = _symbols[symbolId]; + if (symbol != null) { + onSymbolTapped(symbol); + } + }); + + MapboxGlPlatform.getInstance(_id).onLineTappedPlatform.add((lineId) { + final Line line = _lines[lineId]; + if (line != null) { + onLineTapped(line); + } + }); + + MapboxGlPlatform.getInstance(_id).onCircleTappedPlatform.add((circleId) { + final Circle circle = _circles[circleId]; + if (circle != null) { + onCircleTapped(circle); + } + }); + + MapboxGlPlatform.getInstance(_id).onCameraMoveStartedPlatform.add((_) { + _isCameraMoving = true; + notifyListeners(); + }); + + MapboxGlPlatform.getInstance(_id) + .onCameraMovePlatform + .add((cameraPosition) { + _cameraPosition = cameraPosition; + notifyListeners(); + }); + + MapboxGlPlatform.getInstance(_id).onCameraIdlePlatform.add((_) { + _isCameraMoving = false; + if (onCameraIdle != null) { + onCameraIdle(); + } + notifyListeners(); + }); + + MapboxGlPlatform.getInstance(_id).onMapStyleLoadedPlatform.add((_) { + if (onStyleLoadedCallback != null) { + onStyleLoadedCallback(); + } + }); + + MapboxGlPlatform.getInstance(_id).onMapClickPlatform.add((dict) { + if (onMapClick != null) { + onMapClick(dict['point'], dict['latLng']); + } + }); + + MapboxGlPlatform.getInstance(_id).onMapLongClickPlatform.add((dict) { + if (onMapLongClick != null) { + onMapLongClick(dict['point'], dict['latLng']); + } + }); + + MapboxGlPlatform.getInstance(_id) + .onCameraTrackingChangedPlatform + .add((mode) { + if (onCameraTrackingChanged != null) { + onCameraTrackingChanged(mode); + } + }); + + MapboxGlPlatform.getInstance(_id) + .onCameraTrackingDismissedPlatform + .add((_) { + if (onCameraTrackingDismissed != null) { + onCameraTrackingDismissed(); + } + }); + + MapboxGlPlatform.getInstance(_id).onMapIdlePlatform.add((_) { + if (onMapIdle != null) { + onMapIdle(); + } + }); } static Future init( int id, CameraPosition initialCameraPosition, {OnStyleLoadedCallback onStyleLoadedCallback, OnMapClickCallback onMapClick, + OnMapLongClickCallback onMapLongClick, OnCameraTrackingDismissedCallback onCameraTrackingDismissed, OnCameraTrackingChangedCallback onCameraTrackingChanged, + OnCameraIdleCallback onCameraIdle, OnMapIdleCallback onMapIdle}) async { assert(id != null); - final MethodChannel channel = - MethodChannel('plugins.flutter.io/mapbox_maps_$id'); - await channel.invokeMethod('map#waitForMap'); - return MapboxMapController._(id, channel, initialCameraPosition, + await MapboxGlPlatform.getInstance(id).initPlatform(id); + return MapboxMapController._(id, initialCameraPosition, onStyleLoadedCallback: onStyleLoadedCallback, onMapClick: onMapClick, + onMapLongClick: onMapLongClick, onCameraTrackingDismissed: onCameraTrackingDismissed, onCameraTrackingChanged: onCameraTrackingChanged, + onCameraIdle: onCameraIdle, onMapIdle: onMapIdle); } - final MethodChannel _channel; - final OnStyleLoadedCallback onStyleLoadedCallback; final OnMapClickCallback onMapClick; + final OnMapLongClickCallback onMapLongClick; final OnCameraTrackingDismissedCallback onCameraTrackingDismissed; final OnCameraTrackingChangedCallback onCameraTrackingChanged; + final OnCameraIdleCallback onCameraIdle; + final OnMapIdleCallback onMapIdle; /// Callbacks to receive tap events for symbols placed on this map. @@ -115,81 +209,12 @@ class MapboxMapController extends ChangeNotifier { final int _id; //ignore: unused_field - Future _handleMethodCall(MethodCall call) async { - switch (call.method) { - case 'infoWindow#onTap': - final String symbolId = call.arguments['symbol']; - final Symbol symbol = _symbols[symbolId]; - if (symbol != null) { - onInfoWindowTapped(symbol); - } - break; - case 'symbol#onTap': - final String symbolId = call.arguments['symbol']; - final Symbol symbol = _symbols[symbolId]; - if (symbol != null) { - onSymbolTapped(symbol); - } - break; - case 'line#onTap': - final String lineId = call.arguments['line']; - final Line line = _lines[lineId]; - if (line != null) { - onLineTapped(line); - } - break; - case 'circle#onTap': - final String circleId = call.arguments['circle']; - final Circle circle = _circles[circleId]; - if (circle != null) { - onCircleTapped(circle); - } - break; - case 'camera#onMoveStarted': - _isCameraMoving = true; - notifyListeners(); - break; - case 'camera#onMove': - _cameraPosition = CameraPosition.fromMap(call.arguments['position']); - notifyListeners(); - break; - case 'camera#onIdle': - _isCameraMoving = false; - notifyListeners(); - break; - case 'map#onStyleLoaded': - if (onStyleLoadedCallback != null) { - onStyleLoadedCallback(); - } - break; - case 'map#onMapClick': - final double x = call.arguments['x']; - final double y = call.arguments['y']; - final double lng = call.arguments['lng']; - final double lat = call.arguments['lat']; - if (onMapClick != null) { - onMapClick(Point(x, y), LatLng(lat, lng)); - } - break; - case 'map#onCameraTrackingChanged': - if (onCameraTrackingChanged != null) { - final int mode = call.arguments['mode']; - onCameraTrackingChanged(MyLocationTrackingMode.values[mode]); - } - break; - case 'map#onCameraTrackingDismissed': - if (onCameraTrackingDismissed != null) { - onCameraTrackingDismissed(); - } - break; - case 'map#onIdle': - if (onMapIdle != null) { - onMapIdle(); - } - break; - default: - throw MissingPluginException(); - } + Widget buildView( + Map creationParams, + Function onPlatformViewCreated, + Set> gestureRecognizers) { + return MapboxGlPlatform.getInstance(_id) + .buildView(creationParams, onPlatformViewCreated, gestureRecognizers); } /// Updates configuration options of the map user interface. @@ -200,13 +225,8 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes after listeners have been notified. Future _updateMapOptions(Map optionsUpdate) async { assert(optionsUpdate != null); - final dynamic json = await _channel.invokeMethod( - 'map#update', - { - 'options': optionsUpdate, - }, - ); - _cameraPosition = CameraPosition.fromMap(json); + _cameraPosition = + await MapboxGlPlatform.getInstance(_id).updateMapOptions(optionsUpdate); notifyListeners(); } @@ -217,9 +237,8 @@ class MapboxMapController extends ChangeNotifier { /// It returns true if the camera was successfully moved and false if the movement was canceled. /// Note: this currently always returns immediately with a value of null on iOS Future animateCamera(CameraUpdate cameraUpdate) async { - return await _channel.invokeMethod('camera#animate', { - 'cameraUpdate': cameraUpdate._toJson(), - }); + assert(cameraUpdate != null); + return MapboxGlPlatform.getInstance(_id).animateCamera(cameraUpdate); } /// Instantaneously re-position the camera. @@ -230,9 +249,7 @@ class MapboxMapController extends ChangeNotifier { /// It returns true if the camera was successfully moved and false if the movement was canceled. /// Note: this currently always returns immediately with a value of null on iOS Future moveCamera(CameraUpdate cameraUpdate) async { - return await _channel.invokeMethod('camera#move', { - 'cameraUpdate': cameraUpdate._toJson(), - }); + return MapboxGlPlatform.getInstance(_id).moveCamera(cameraUpdate); } /// Updates user location tracking mode. @@ -241,20 +258,19 @@ class MapboxMapController extends ChangeNotifier { /// platform side. Future updateMyLocationTrackingMode( MyLocationTrackingMode myLocationTrackingMode) async { - await _channel.invokeMethod( - 'map#updateMyLocationTrackingMode', { - 'mode': myLocationTrackingMode.index, - }); + return MapboxGlPlatform.getInstance(_id) + .updateMyLocationTrackingMode(myLocationTrackingMode); } - + /// Updates the language of the map labels to match the device's language. /// /// The returned [Future] completes after the change has been made on the /// platform side. Future matchMapLanguageWithDeviceDefault() async { - await _channel.invokeMethod('map#matchMapLanguageWithDeviceDefault'); - } - + return MapboxGlPlatform.getInstance(_id) + .matchMapLanguageWithDeviceDefault(); + } + /// Updates the distance from the edges of the map view’s frame to the edges /// of the map view’s logical viewport, optionally animating the change. /// @@ -268,15 +284,8 @@ class MapboxMapController extends ChangeNotifier { /// platform side. Future updateContentInsets(EdgeInsets insets, [bool animated = false]) async { - await _channel.invokeMethod('map#updateContentInsets', { - 'bounds': { - 'top': insets.top, - 'left': insets.left, - 'bottom': insets.bottom, - 'right': insets.right, - }, - 'animated': animated, - }); + return MapboxGlPlatform.getInstance(_id) + .updateContentInsets(insets, animated); } /// Updates the language of the map labels to match the specified language. @@ -285,19 +294,15 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes after the change has been made on the /// platform side. Future setMapLanguage(String language) async { - await _channel.invokeMethod('map#setMapLanguage', { - 'language': language, - }); + return MapboxGlPlatform.getInstance(_id).setMapLanguage(language); } - + /// Enables or disables the collection of anonymized telemetry data. /// /// The returned [Future] completes after the change has been made on the /// platform side. Future setTelemetryEnabled(bool enabled) async { - await _channel.invokeMethod('map#setTelemetryEnabled', { - 'enabled': enabled, - }); + return MapboxGlPlatform.getInstance(_id).setTelemetryEnabled(enabled); } /// Retrieves whether collection of anonymized telemetry data is enabled. @@ -305,7 +310,7 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes after the query has been made on the /// platform side. Future getTelemetryEnabled() async { - return await _channel.invokeMethod('map#getTelemetryEnabled'); + return MapboxGlPlatform.getInstance(_id).getTelemetryEnabled(); } /// Adds a symbol to the map, configured using the specified custom [options]. @@ -316,18 +321,21 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes with the added symbol once listeners have /// been notified. Future addSymbol(SymbolOptions options, [Map data]) async { - final SymbolOptions effectiveOptions = - SymbolOptions.defaultOptions.copyWith(options); - final String symbolId = await _channel.invokeMethod( - 'symbol#add', - { - 'options': effectiveOptions._toJson(), - }, - ); - final Symbol symbol = Symbol(symbolId, effectiveOptions, data); - _symbols[symbolId] = symbol; + List result = await addSymbols([options], [data]); + + return result.first; + } + + + Future> addSymbols(List options, [List data]) async { + final List effectiveOptions = options.map( + (o) => SymbolOptions.defaultOptions.copyWith(o) + ).toList(); + + final symbols = await MapboxGlPlatform.getInstance(_id).addSymbols(effectiveOptions, data); + symbols.forEach((s) => _symbols[s.id] = s); notifyListeners(); - return symbol; + return symbols; } /// Updates the specified [symbol] with the given [changes]. The symbol must @@ -339,14 +347,23 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future updateSymbol(Symbol symbol, SymbolOptions changes) async { assert(symbol != null); - assert(_symbols[symbol._id] == symbol); + assert(_symbols[symbol.id] == symbol); assert(changes != null); - await _channel.invokeMethod('symbol#update', { - 'symbol': symbol._id, - 'options': changes._toJson(), - }); - symbol._options = symbol._options.copyWith(changes); + await MapboxGlPlatform.getInstance(_id).updateSymbol(symbol, changes); + symbol.options = symbol.options.copyWith(changes); + notifyListeners(); + } + + /// Retrieves the current position of the symbol. + /// This may be different from the value of `symbol.options.geometry` if the symbol is draggable. + /// In that case this method provides the symbol's actual position, and `symbol.options.geometry` the last programmatically set position. + Future getSymbolLatLng(Symbol symbol) async { + assert(symbol != null); + assert(_symbols[symbol.id] == symbol); + final symbolLatLng = + await MapboxGlPlatform.getInstance(_id).getSymbolLatLng(symbol); notifyListeners(); + return symbolLatLng; } /// Removes the specified [symbol] from the map. The symbol must be a current @@ -358,8 +375,18 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future removeSymbol(Symbol symbol) async { assert(symbol != null); - assert(_symbols[symbol._id] == symbol); - await _removeSymbol(symbol._id); + assert(_symbols[symbol.id] == symbol); + await _removeSymbols([symbol.id]); + notifyListeners(); + } + + Future removeSymbols(Iterable symbols) async { + assert(symbols.length > 0); + symbols.forEach((s) { + assert(_symbols[s.id] == s); + }); + + await _removeSymbols(symbols.map((s) => s.id)); notifyListeners(); } @@ -372,9 +399,7 @@ class MapboxMapController extends ChangeNotifier { Future clearSymbols() async { assert(_symbols != null); final List symbolIds = List.from(_symbols.keys); - for (String id in symbolIds) { - await _removeSymbol(id); - } + _removeSymbols(symbolIds); notifyListeners(); } @@ -383,11 +408,9 @@ class MapboxMapController extends ChangeNotifier { /// /// The returned [Future] completes once the symbol has been removed from /// [_symbols]. - Future _removeSymbol(String id) async { - await _channel.invokeMethod('symbol#remove', { - 'symbol': id, - }); - _symbols.remove(id); + Future _removeSymbols(Iterable ids) async { + await MapboxGlPlatform.getInstance(_id).removeSymbols(ids); + _symbols.removeWhere((k, s) => ids.contains(k)); } /// Adds a line to the map, configured using the specified custom [options]. @@ -400,14 +423,9 @@ class MapboxMapController extends ChangeNotifier { Future addLine(LineOptions options, [Map data]) async { final LineOptions effectiveOptions = LineOptions.defaultOptions.copyWith(options); - final String lineId = await _channel.invokeMethod( - 'line#add', - { - 'options': effectiveOptions._toJson(), - }, - ); - final Line line = Line(lineId, effectiveOptions, data); - _lines[lineId] = line; + final line = + await MapboxGlPlatform.getInstance(_id).addLine(effectiveOptions); + _lines[line.id] = line; notifyListeners(); return line; } @@ -421,14 +439,23 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future updateLine(Line line, LineOptions changes) async { assert(line != null); - assert(_lines[line._id] == line); + assert(_lines[line.id] == line); assert(changes != null); - await _channel.invokeMethod('line#update', { - 'line': line._id, - 'options': changes._toJson(), - }); - line._options = line._options.copyWith(changes); + await MapboxGlPlatform.getInstance(_id).updateLine(line, changes); + line.options = line.options.copyWith(changes); + notifyListeners(); + } + + /// Retrieves the current position of the line. + /// This may be different from the value of `line.options.geometry` if the line is draggable. + /// In that case this method provides the line's actual position, and `line.options.geometry` the last programmatically set position. + Future> getLineLatLngs(Line line) async { + assert(line != null); + assert(_lines[line.id] == line); + final lineLatLngs = + await MapboxGlPlatform.getInstance(_id).getLineLatLngs(line); notifyListeners(); + return lineLatLngs; } /// Removes the specified [line] from the map. The line must be a current @@ -440,8 +467,8 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future removeLine(Line line) async { assert(line != null); - assert(_lines[line._id] == line); - await _removeLine(line._id); + assert(_lines[line.id] == line); + await _removeLine(line.id); notifyListeners(); } @@ -466,9 +493,7 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once the line has been removed from /// [_lines]. Future _removeLine(String id) async { - await _channel.invokeMethod('line#remove', { - 'line': id, - }); + await MapboxGlPlatform.getInstance(_id).removeLine(id); _lines.remove(id); } @@ -482,14 +507,9 @@ class MapboxMapController extends ChangeNotifier { Future addCircle(CircleOptions options, [Map data]) async { final CircleOptions effectiveOptions = CircleOptions.defaultOptions.copyWith(options); - final String circleId = await _channel.invokeMethod( - 'circle#add', - { - 'options': effectiveOptions._toJson(), - }, - ); - final Circle circle = Circle(circleId, effectiveOptions, data); - _circles[circleId] = circle; + final circle = + await MapboxGlPlatform.getInstance(_id).addCircle(effectiveOptions); + _circles[circle.id] = circle; notifyListeners(); return circle; } @@ -503,28 +523,21 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future updateCircle(Circle circle, CircleOptions changes) async { assert(circle != null); - assert(_circles[circle._id] == circle); + assert(_circles[circle.id] == circle); assert(changes != null); - await _channel.invokeMethod('circle#update', { - 'circle': circle._id, - 'options': changes._toJson(), - }); - circle._options = circle._options.copyWith(changes); + await MapboxGlPlatform.getInstance(_id).updateCircle(circle, changes); + circle.options = circle.options.copyWith(changes); notifyListeners(); } - /// `circle.options.geometry` can't get real-time location.For example, when you - /// set circle `draggable` is true,and you dragged circle.At this time you - /// should use `getCircleLatLng()` + /// Retrieves the current position of the circle. + /// This may be different from the value of `circle.options.geometry` if the circle is draggable. + /// In that case this method provides the circle's actual position, and `circle.options.geometry` the last programmatically set position. Future getCircleLatLng(Circle circle) async { assert(circle != null); - assert(_circles[circle._id] == circle); - Map mapLatLng = - await _channel.invokeMethod('circle#getGeometry', { - 'circle': circle._id, - }); - LatLng circleLatLng = - new LatLng(mapLatLng['latitude'], mapLatLng['longitude']); + assert(_circles[circle.id] == circle); + final circleLatLng = + await MapboxGlPlatform.getInstance(_id).getCircleLatLng(circle); notifyListeners(); return circleLatLng; } @@ -538,8 +551,8 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once listeners have been notified. Future removeCircle(Circle circle) async { assert(circle != null); - assert(_circles[circle._id] == circle); - await _removeCircle(circle._id); + assert(_circles[circle.id] == circle); + await _removeCircle(circle.id); notifyListeners(); } @@ -564,96 +577,97 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once the circle has been removed from /// [_circles]. Future _removeCircle(String id) async { - await _channel.invokeMethod('circle#remove', { - 'circle': id, - }); + await MapboxGlPlatform.getInstance(_id).removeCircle(id); + _circles.remove(id); } Future queryRenderedFeatures( - Point point, List layerIds, String filter) async { - try { - final Map reply = await _channel.invokeMethod( - 'map#queryRenderedFeatures', - { - 'x': point.x, - 'y': point.y, - 'layerIds': layerIds, - 'filter': filter, - }, - ); - return reply['features']; - } on PlatformException catch (e) { - return new Future.error(e); - } + Point point, List layerIds, List filter) async { + return MapboxGlPlatform.getInstance(_id) + .queryRenderedFeatures(point, layerIds, filter); } Future queryRenderedFeaturesInRect( Rect rect, List layerIds, String filter) async { - try { - final Map reply = await _channel.invokeMethod( - 'map#queryRenderedFeatures', - { - 'left': rect.left, - 'top': rect.top, - 'right': rect.right, - 'bottom': rect.bottom, - 'layerIds': layerIds, - 'filter': filter, - }, - ); - return reply['features']; - } on PlatformException catch (e) { - return new Future.error(e); - } + return MapboxGlPlatform.getInstance(_id) + .queryRenderedFeaturesInRect(rect, layerIds, filter); } - Future invalidateAmbientCache() async { - try { - await _channel.invokeMethod('map#invalidateAmbientCache'); - return null; - } on PlatformException catch (e) { - return new Future.error(e); - } + return MapboxGlPlatform.getInstance(_id).invalidateAmbientCache(); } /// Get last my location /// /// Return last latlng, nullable - Future requestMyLocationLatLng() async { - try { - final Map reply = await _channel.invokeMethod('locationComponent#getLastLocation', null); - double latitude = 0.0, longitude = 0.0; - if (reply.containsKey("latitude") && reply["latitude"] != null) { - latitude = double.parse(reply["latitude"].toString()); - } - if (reply.containsKey("longitude") && reply["longitude"] != null) { - longitude = double.parse(reply["longitude"].toString()); - } - return LatLng(latitude, longitude); - } on PlatformException catch (e) { - return new Future.error(e); - } - } - - ///This method returns the boundaries of the region currently displayed in the map. - Future getVisibleRegion() async{ - try { - final Map reply = await _channel.invokeMethod('map#getVisibleRegion', null); - LatLng southwest, northeast; - if (reply.containsKey("sw")) { - List coordinates = reply["sw"]; - southwest = LatLng(coordinates[0], coordinates[1]); - } - if (reply.containsKey("ne")) { - List coordinates = reply["ne"]; - northeast = LatLng(coordinates[0], coordinates[1]); - } - return LatLngBounds(southwest: southwest, northeast: northeast); - } on PlatformException catch (e) { - return new Future.error(e); - } + return MapboxGlPlatform.getInstance(_id).requestMyLocationLatLng(); + } + + /// This method returns the boundaries of the region currently displayed in the map. + Future getVisibleRegion() async { + return MapboxGlPlatform.getInstance(_id).getVisibleRegion(); + } + + /// Adds an image to the style currently displayed in the map, so that it can later be referred to by the provided name. + /// + /// This allows you to add an image to the currently displayed style once, and from there on refer to it e.g. in the [Symbol.iconImage] anytime you add a [Symbol] later on. + /// Set [sdf] to true if the image you add is an SDF image. + /// Returns after the image has successfully been added to the style. + /// Note: This can only be called after OnStyleLoadedCallback has been invoked and any added images will have to be re-added if a new style is loaded. + /// + /// Example: Adding an asset image and using it in a new symbol: + /// ```dart + /// Future addImageFromAsset() async{ + /// final ByteData bytes = await rootBundle.load("assets/someAssetImage.jpg"); + /// final Uint8List list = bytes.buffer.asUint8List(); + /// await controller.addImage("assetImage", list); + /// controller.addSymbol( + /// SymbolOptions( + /// geometry: LatLng(0,0), + /// iconImage: "assetImage", + /// ), + /// ); + /// } + /// ``` + /// + /// Example: Adding a network image (with the http package) and using it in a new symbol: + /// ```dart + /// Future addImageFromUrl() async{ + /// var response = await get("https://example.com/image.png"); + /// await controller.addImage("testImage", response.bodyBytes); + /// controller.addSymbol( + /// SymbolOptions( + /// geometry: LatLng(0,0), + /// iconImage: "testImage", + /// ), + /// ); + /// } + /// ``` + Future addImage(String name, Uint8List bytes, [bool sdf = false]) { + return MapboxGlPlatform.getInstance(_id).addImage(name, bytes, sdf); + } + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setSymbolIconAllowOverlap(bool enable) async { + await MapboxGlPlatform.getInstance(_id).setSymbolIconAllowOverlap(enable); + } + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setSymbolIconIgnorePlacement(bool enable) async { + await MapboxGlPlatform.getInstance(_id) + .setSymbolIconIgnorePlacement(enable); + } + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setSymbolTextAllowOverlap(bool enable) async { + await MapboxGlPlatform.getInstance(_id).setSymbolTextAllowOverlap(enable); + } + + /// For more information on what this does, see https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-collision + Future setSymbolTextIgnorePlacement(bool enable) async { + await MapboxGlPlatform.getInstance(_id) + .setSymbolTextIgnorePlacement(enable); } } diff --git a/lib/src/mapbox_map.dart b/lib/src/mapbox_map.dart index 6cf587667..1a765a848 100644 --- a/lib/src/mapbox_map.dart +++ b/lib/src/mapbox_map.dart @@ -9,6 +9,7 @@ typedef void MapCreatedCallback(MapboxMapController controller); class MapboxMap extends StatefulWidget { const MapboxMap({ @required this.initialCameraPosition, + this.accessToken, this.onMapCreated, this.onStyleLoadedCallback, this.gestureRecognizers, @@ -22,19 +23,33 @@ class MapboxMap extends StatefulWidget { this.tiltGesturesEnabled = true, this.trackCameraPosition = false, this.myLocationEnabled = false, - this.myLocationTrackingMode = MyLocationTrackingMode.Tracking, + this.myLocationTrackingMode = MyLocationTrackingMode.None, this.myLocationRenderMode = MyLocationRenderMode.COMPASS, this.logoViewMargins, this.compassViewPosition, this.compassViewMargins, this.attributionButtonMargins, this.onMapClick, + this.onMapLongClick, this.onCameraTrackingDismissed, this.onCameraTrackingChanged, + this.onCameraIdle, this.onMapIdle, }) : assert(initialCameraPosition != null); + + /// If you want to use Mapbox hosted styles and map tiles, you need to provide a Mapbox access token. + /// Obtain a free access token on [your Mapbox account page](https://www.mapbox.com/account/access-tokens/). + /// The reccommended way is to use this parameter to set your access token, an alternative way to add your access tokens through external files is described in the plugin's wiki on Github. + /// + /// Note: You should not use this parameter AND set the access token through external files at the same time, and you should use the same token throughout the entire app. + final String accessToken; + + /// Please note: you should only add annotations (e.g. symbols or circles) after `onStyleLoadedCallback` has been called. final MapCreatedCallback onMapCreated; + + /// Called when the map style has been successfully loaded and the annotation managers have been enabled. + /// Please note: you should only add annotations (e.g. symbols or circles) after this callback has been called. final OnStyleLoadedCallback onStyleLoadedCallback; /// The initial position of the map's camera. @@ -68,7 +83,10 @@ class MapboxMap extends StatefulWidget { /// True if the map view should respond to tilt gestures. final bool tiltGesturesEnabled; - /// True if the map view should relay camera move events to Flutter. + /// True if you want to be notified of map camera movements by the MapboxMapController. Default is false. + /// + /// If this is set to true and the user pans/zooms/rotates the map, MapboxMapController (which is a ChangeNotifier) + /// will notify it's listeners and you can then get the new MapboxMapController.cameraPosition. final bool trackCameraPosition; /// True if a "My Location" layer should be shown on the map. @@ -96,7 +114,7 @@ class MapboxMap extends StatefulWidget { /// when the map tries to turn on the My Location layer. final bool myLocationEnabled; - /// The mode used to track the user location on the map + /// The mode used to let the map's camera follow the device's physical location final MyLocationTrackingMode myLocationTrackingMode; /// The mode to render the user location symbol @@ -126,11 +144,17 @@ class MapboxMap extends StatefulWidget { final Set> gestureRecognizers; final OnMapClickCallback onMapClick; + final OnMapClickCallback onMapLongClick; - /// Called when the location tracking mode changes, such as when the user moves the map + /// Called when the map's camera no longer follows the physical device location, e.g. because the user moved the map final OnCameraTrackingDismissedCallback onCameraTrackingDismissed; + + /// Called when the location tracking mode changes final OnCameraTrackingChangedCallback onCameraTrackingChanged; + // Called when camera movement has ended. + final OnCameraIdleCallback onCameraIdle; + /// Called when map view is entering an idle state, and no more drawing will /// be necessary until new data is loaded or there is some interaction with /// the map. @@ -148,33 +172,17 @@ class _MapboxMapState extends State { Completer(); _MapboxMapOptions _mapboxMapOptions; + final MapboxGlPlatform _mapboxGlPlatform = MapboxGlPlatform.createInstance(); @override Widget build(BuildContext context) { final Map creationParams = { - 'initialCameraPosition': widget.initialCameraPosition?._toMap(), + 'initialCameraPosition': widget.initialCameraPosition?.toMap(), 'options': _MapboxMapOptions.fromWidget(widget).toMap(), + 'accessToken': widget.accessToken, }; - if (defaultTargetPlatform == TargetPlatform.android) { - return AndroidView( - viewType: 'plugins.flutter.io/mapbox_gl', - onPlatformViewCreated: onPlatformViewCreated, - gestureRecognizers: widget.gestureRecognizers, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - ); - } else if (defaultTargetPlatform == TargetPlatform.iOS) { - return UiKitView( - viewType: 'plugins.flutter.io/mapbox_gl', - onPlatformViewCreated: onPlatformViewCreated, - gestureRecognizers: widget.gestureRecognizers, - creationParams: creationParams, - creationParamsCodec: const StandardMessageCodec(), - ); - } - - return Text( - '$defaultTargetPlatform is not yet supported by the maps plugin'); + return _mapboxGlPlatform.buildView( + creationParams, onPlatformViewCreated, widget.gestureRecognizers); } @override @@ -202,12 +210,15 @@ class _MapboxMapState extends State { } Future onPlatformViewCreated(int id) async { + MapboxGlPlatform.addInstance(id, _mapboxGlPlatform); final MapboxMapController controller = await MapboxMapController.init( id, widget.initialCameraPosition, onStyleLoadedCallback: widget.onStyleLoadedCallback, onMapClick: widget.onMapClick, + onMapLongClick: widget.onMapLongClick, onCameraTrackingDismissed: widget.onCameraTrackingDismissed, onCameraTrackingChanged: widget.onCameraTrackingChanged, + onCameraIdle: widget.onCameraIdle, onMapIdle: widget.onMapIdle); _controller.complete(controller); if (widget.onMapCreated != null) { @@ -257,7 +268,7 @@ class _MapboxMapOptions { logoViewMargins: map.logoViewMargins, compassViewPosition: map.compassViewPosition, compassViewMargins: map.compassViewMargins, - attributionButtonMargins: map.attributionButtonMargins + attributionButtonMargins: map.attributionButtonMargins, ); } @@ -311,9 +322,9 @@ class _MapboxMapOptions { } addIfNonNull('compassEnabled', compassEnabled); - addIfNonNull('cameraTargetBounds', cameraTargetBounds?._toJson()); + addIfNonNull('cameraTargetBounds', cameraTargetBounds?.toJson()); addIfNonNull('styleString', styleString); - addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?._toJson()); + addIfNonNull('minMaxZoomPreference', minMaxZoomPreference?.toJson()); addIfNonNull('rotateGesturesEnabled', rotateGesturesEnabled); addIfNonNull('scrollGesturesEnabled', scrollGesturesEnabled); addIfNonNull('tiltGesturesEnabled', tiltGesturesEnabled); @@ -325,12 +336,15 @@ class _MapboxMapOptions { addIfNonNull('logoViewMargins', pointToArray(logoViewMargins)); addIfNonNull('compassViewPosition', compassViewPosition?.index); addIfNonNull('compassViewMargins', pointToArray(compassViewMargins)); - addIfNonNull('attributionButtonMargins', pointToArray(attributionButtonMargins)); + addIfNonNull( + 'attributionButtonMargins', pointToArray(attributionButtonMargins)); return optionsMap; } Map updatesMap(_MapboxMapOptions newOptions) { final Map prevOptionsMap = toMap(); - return newOptions.toMap()..removeWhere((String key, dynamic value) => prevOptionsMap[key] == value); + return newOptions.toMap() + ..removeWhere( + (String key, dynamic value) => prevOptionsMap[key] == value); } } diff --git a/mapbox_gl_platform_interface/CHANGELOG.md b/mapbox_gl_platform_interface/CHANGELOG.md new file mode 100644 index 000000000..c15887ba4 --- /dev/null +++ b/mapbox_gl_platform_interface/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.7.0 +- Initial version \ No newline at end of file diff --git a/mapbox_gl_platform_interface/LICENSE b/mapbox_gl_platform_interface/LICENSE new file mode 100644 index 000000000..a4c4948f4 --- /dev/null +++ b/mapbox_gl_platform_interface/LICENSE @@ -0,0 +1,6 @@ +flutter-mapbox-gl copyright (c) 2018, Mapbox. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/mapbox_gl_platform_interface/README.md b/mapbox_gl_platform_interface/README.md new file mode 100644 index 000000000..f3e2910f0 --- /dev/null +++ b/mapbox_gl_platform_interface/README.md @@ -0,0 +1 @@ +Contains the web platform implementation for the [Flutter Mapbox GL plugin](https://github.com/tobrun/flutter-mapbox-gl). \ No newline at end of file diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart new file mode 100644 index 000000000..0abe3163a --- /dev/null +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -0,0 +1,19 @@ +library mapbox_gl_platform_interface; + +import 'dart:math'; +import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show required, visibleForTesting; + +part 'src/callbacks.dart'; +part 'src/camera.dart'; +part 'src/circle.dart'; +part 'src/line.dart'; +part 'src/location.dart'; +part 'src/method_channel_mapbox_gl.dart'; +part 'src/symbol.dart'; +part 'src/ui.dart'; +part 'src/mapbox_gl_platform_interface.dart'; diff --git a/lib/src/callbacks.dart b/mapbox_gl_platform_interface/lib/src/callbacks.dart similarity index 97% rename from lib/src/callbacks.dart rename to mapbox_gl_platform_interface/lib/src/callbacks.dart index 85d746a82..e375f2fef 100644 --- a/lib/src/callbacks.dart +++ b/mapbox_gl_platform_interface/lib/src/callbacks.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of mapbox_gl; +part of mapbox_gl_platform_interface; /// Callback function taking a single argument. typedef void ArgumentCallback(T argument); diff --git a/lib/src/camera.dart b/mapbox_gl_platform_interface/lib/src/camera.dart similarity index 94% rename from lib/src/camera.dart rename to mapbox_gl_platform_interface/lib/src/camera.dart index d6b407433..747086ddf 100644 --- a/lib/src/camera.dart +++ b/mapbox_gl_platform_interface/lib/src/camera.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of mapbox_gl; +part of mapbox_gl_platform_interface; /// The position of the map "camera", the view point from which the world is /// shown in the map view. Aggregates the camera's [target] geographical @@ -51,9 +51,9 @@ class CameraPosition { /// will be silently clamped to the supported range. final double zoom; - dynamic _toMap() => { + dynamic toMap() => { 'bearing': bearing, - 'target': target._toJson(), + 'target': target.toJson(), 'tilt': tilt, 'zoom': zoom, }; @@ -98,14 +98,14 @@ class CameraUpdate { /// Returns a camera update that moves the camera to the specified position. static CameraUpdate newCameraPosition(CameraPosition cameraPosition) { return CameraUpdate._( - ['newCameraPosition', cameraPosition._toMap()], + ['newCameraPosition', cameraPosition.toMap()], ); } /// Returns a camera update that moves the camera target to the specified /// geographical location. static CameraUpdate newLatLng(LatLng latLng) { - return CameraUpdate._(['newLatLng', latLng._toJson()]); + return CameraUpdate._(['newLatLng', latLng.toJson()]); } /// Returns a camera update that transforms the camera so that the specified @@ -115,7 +115,7 @@ class CameraUpdate { static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) { return CameraUpdate._([ 'newLatLngBounds', - bounds._toList(), + bounds.toList(), padding, ]); } @@ -124,7 +124,7 @@ class CameraUpdate { /// geographical location and zoom level. static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) { return CameraUpdate._( - ['newLatLngZoom', latLng._toJson(), zoom], + ['newLatLngZoom', latLng.toJson(), zoom], ); } @@ -180,7 +180,7 @@ class CameraUpdate { static CameraUpdate bearingTo(double bearing) { return CameraUpdate._(['bearingTo', bearing]); } - + /// Returns a camera update that sets the camera bearing. static CameraUpdate tiltTo(double tilt) { return CameraUpdate._(['tiltTo', tilt]); @@ -188,5 +188,5 @@ class CameraUpdate { final dynamic _json; - dynamic _toJson() => _json; + dynamic toJson() => _json; } diff --git a/lib/src/circle.dart b/mapbox_gl_platform_interface/lib/src/circle.dart similarity index 92% rename from lib/src/circle.dart rename to mapbox_gl_platform_interface/lib/src/circle.dart index 9d738f267..01815ae85 100644 --- a/lib/src/circle.dart +++ b/mapbox_gl_platform_interface/lib/src/circle.dart @@ -4,29 +4,26 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of mapbox_gl; +part of mapbox_gl_platform_interface; class Circle { - @visibleForTesting - Circle(this._id, this._options, [this._data]); + Circle(this._id, this.options, [this._data]); /// A unique identifier for this circle. /// /// The identifier is an arbitrary unique string. final String _id; String get id => _id; - - - CircleOptions _options; final Map _data; Map get data => _data; + /// The circle configuration options most recently applied programmatically /// via the map controller. /// /// The returned value does not reflect any changes made to the circle through /// touch events. Add listeners to the owning map controller to track those. - CircleOptions get options => _options; + CircleOptions options; } /// Configuration options for [Circle] instances. @@ -79,7 +76,7 @@ class CircleOptions { ); } - dynamic _toJson() { + dynamic toJson() { final Map json = {}; void addIfPresent(String fieldName, dynamic value) { @@ -95,7 +92,7 @@ class CircleOptions { addIfPresent('circleStrokeWidth', circleStrokeWidth); addIfPresent('circleStrokeColor', circleStrokeColor); addIfPresent('circleStrokeOpacity', circleStrokeOpacity); - addIfPresent('geometry', geometry?._toJson()); + addIfPresent('geometry', geometry?.toJson()); addIfPresent('draggable', draggable); return json; } diff --git a/lib/src/line.dart b/mapbox_gl_platform_interface/lib/src/line.dart similarity index 91% rename from lib/src/line.dart rename to mapbox_gl_platform_interface/lib/src/line.dart index 0832f9bd4..376ac1f78 100644 --- a/lib/src/line.dart +++ b/mapbox_gl_platform_interface/lib/src/line.dart @@ -4,11 +4,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of mapbox_gl; +part of mapbox_gl_platform_interface; class Line { - @visibleForTesting - Line(this._id, this._options, [this._data]); + Line(this._id, this.options, [this._data]); /// A unique identifier for this line. /// @@ -18,17 +17,15 @@ class Line { String get id => _id; final Map _data; - - Map get data => _data; - LineOptions _options; + Map get data => _data; /// The line configuration options most recently applied programmatically /// via the map controller. /// /// The returned value does not reflect any changes made to the line through /// touch events. Add listeners to the owning map controller to track those. - LineOptions get options => _options; + LineOptions options; } /// Configuration options for [Line] instances. @@ -84,7 +81,7 @@ class LineOptions { ); } - dynamic _toJson() { + dynamic toJson() { final Map json = {}; void addIfPresent(String fieldName, dynamic value) { @@ -101,7 +98,8 @@ class LineOptions { addIfPresent('lineOffset', lineOffset); addIfPresent('lineBlur', lineBlur); addIfPresent('linePattern', linePattern); - addIfPresent('geometry', geometry?.map((LatLng latLng) => latLng._toJson())?.toList()); + addIfPresent('geometry', + geometry?.map((LatLng latLng) => latLng.toJson())?.toList()); addIfPresent('draggable', draggable); return json; } diff --git a/lib/src/location.dart b/mapbox_gl_platform_interface/lib/src/location.dart similarity index 95% rename from lib/src/location.dart rename to mapbox_gl_platform_interface/lib/src/location.dart index 4f33b7b49..617943453 100644 --- a/lib/src/location.dart +++ b/mapbox_gl_platform_interface/lib/src/location.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of mapbox_gl; +part of mapbox_gl_platform_interface; /// A pair of latitude and longitude coordinates, stored as degrees. class LatLng { @@ -26,7 +26,7 @@ class LatLng { /// The longitude in degrees between -180.0 (inclusive) and 180.0 (exclusive). final double longitude; - dynamic _toJson() { + dynamic toJson() { return [latitude, longitude]; } @@ -73,8 +73,8 @@ class LatLngBounds { /// The northeast corner of the rectangle. final LatLng northeast; - dynamic _toList() { - return [southwest._toJson(), northeast._toJson()]; + dynamic toList() { + return [southwest.toJson(), northeast.toJson()]; } @visibleForTesting diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart new file mode 100644 index 000000000..da022c739 --- /dev/null +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -0,0 +1,213 @@ +// ignore_for_file: unnecessary_getters_setters + +part of mapbox_gl_platform_interface; + +abstract class MapboxGlPlatform { + /// The default instance of [MapboxGlPlatform] to use. + /// + /// Defaults to [MethodChannelMapboxGl]. + /// + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [MapboxGlPlatform] when they register themselves. + static MapboxGlPlatform Function() createInstance = + () => MethodChannelMapboxGl(); + + static Map _instances = {}; + + static void addInstance(int id, MapboxGlPlatform platform) { + _instances[id] = platform; + } + + static MapboxGlPlatform getInstance(int id) { + return _instances[id]; + } + + final ArgumentCallbacks onInfoWindowTappedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onSymbolTappedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onLineTappedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onCircleTappedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onCameraMoveStartedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onCameraMovePlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onCameraIdlePlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onMapStyleLoadedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks> onMapClickPlatform = + ArgumentCallbacks>(); + + final ArgumentCallbacks> onMapLongClickPlatform = + ArgumentCallbacks>(); + + final ArgumentCallbacks + onCameraTrackingChangedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onCameraTrackingDismissedPlatform = + ArgumentCallbacks(); + + final ArgumentCallbacks onMapIdlePlatform = ArgumentCallbacks(); + + Future initPlatform(int id) async { + throw UnimplementedError('initPlatform() has not been implemented.'); + } + + Widget buildView( + Map creationParams, + Function onPlatformViewCreated, + Set> gestureRecognizers) { + throw UnimplementedError('buildView() has not been implemented.'); + } + + Future updateMapOptions( + Map optionsUpdate) async { + throw UnimplementedError('updateMapOptions() has not been implemented.'); + } + + Future animateCamera(CameraUpdate cameraUpdate) async { + throw UnimplementedError('animateCamera() has not been implemented.'); + } + + Future moveCamera(CameraUpdate cameraUpdate) async { + throw UnimplementedError('moveCamera() has not been implemented.'); + } + + Future updateMyLocationTrackingMode( + MyLocationTrackingMode myLocationTrackingMode) async { + throw UnimplementedError( + 'updateMyLocationTrackingMode() has not been implemented.'); + } + + Future matchMapLanguageWithDeviceDefault() async { + throw UnimplementedError( + 'matchMapLanguageWithDeviceDefault() has not been implemented.'); + } + + Future updateContentInsets(EdgeInsets insets, bool animated) async { + throw UnimplementedError('updateContentInsets() has not been implemented.'); + } + + Future setMapLanguage(String language) async { + throw UnimplementedError('setMapLanguage() has not been implemented.'); + } + + Future setTelemetryEnabled(bool enabled) async { + throw UnimplementedError('setTelemetryEnabled() has not been implemented.'); + } + + Future getTelemetryEnabled() async { + throw UnimplementedError('getTelemetryEnabled() has not been implemented.'); + } + + Future> addSymbols(List options, [List data]) async { + throw UnimplementedError('addSymbols() has not been implemented.'); + } + + Future updateSymbol(Symbol symbol, SymbolOptions changes) async { + throw UnimplementedError('updateSymbol() has not been implemented.'); + } + + Future removeSymbols(Iterable symbolsIds) async { + throw UnimplementedError('removeSymbol() has not been implemented.'); + } + + Future addLine(LineOptions options, [Map data]) async { + throw UnimplementedError('addLine() has not been implemented.'); + } + + Future updateLine(Line line, LineOptions changes) async { + throw UnimplementedError('updateLine() has not been implemented.'); + } + + Future removeLine(String lineId) async { + throw UnimplementedError('removeLine() has not been implemented.'); + } + + Future addCircle(CircleOptions options, [Map data]) async { + throw UnimplementedError('addCircle() has not been implemented.'); + } + + Future updateCircle(Circle circle, CircleOptions changes) async { + throw UnimplementedError('updateCircle() has not been implemented.'); + } + + Future getCircleLatLng(Circle circle) async { + throw UnimplementedError('getCircleLatLng() has not been implemented.'); + } + + Future getSymbolLatLng(Symbol symbol) async { + throw UnimplementedError('getSymbolLatLng() has not been implemented.'); + } + + Future> getLineLatLngs(Line line) async { + throw UnimplementedError('getLineLatLngs() has not been implemented.'); + } + + Future removeCircle(String circleId) async { + throw UnimplementedError('removeCircle() has not been implemented.'); + } + + Future queryRenderedFeatures( + Point point, List layerIds, List filter) async { + throw UnimplementedError( + 'queryRenderedFeatures() has not been implemented.'); + } + + Future queryRenderedFeaturesInRect( + Rect rect, List layerIds, String filter) async { + throw UnimplementedError( + 'queryRenderedFeaturesInRect() has not been implemented.'); + } + + Future invalidateAmbientCache() async { + throw UnimplementedError( + 'invalidateAmbientCache() has not been implemented.'); + } + + Future requestMyLocationLatLng() async { + throw UnimplementedError( + 'requestMyLocationLatLng() has not been implemented.'); + } + + Future getVisibleRegion() async { + throw UnimplementedError('getVisibleRegion() has not been implemented.'); + } + + Future addImage(String name, Uint8List bytes, + [bool sdf = false]) async { + throw UnimplementedError('addImage() has not been implemented.'); + } + + Future setSymbolIconAllowOverlap(bool enable) async { + throw UnimplementedError( + 'setSymbolIconAllowOverlap() has not been implemented.'); + } + + Future setSymbolIconIgnorePlacement(bool enable) async { + throw UnimplementedError( + 'setSymbolIconIgnorePlacement() has not been implemented.'); + } + + Future setSymbolTextAllowOverlap(bool enable) async { + throw UnimplementedError( + 'setSymbolTextAllowOverlap() has not been implemented.'); + } + + Future setSymbolTextIgnorePlacement(bool enable) async { + throw UnimplementedError( + 'setSymbolTextIgnorePlacement() has not been implemented.'); + } +} diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart new file mode 100644 index 000000000..e1513daf7 --- /dev/null +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -0,0 +1,456 @@ +part of mapbox_gl_platform_interface; + +class MethodChannelMapboxGl extends MapboxGlPlatform { + MethodChannel _channel; + + Future _handleMethodCall(MethodCall call) async { + switch (call.method) { + case 'infoWindow#onTap': + final String symbolId = call.arguments['symbol']; + if (symbolId != null) { + onInfoWindowTappedPlatform(symbolId); + } + break; + case 'symbol#onTap': + final String symbolId = call.arguments['symbol']; + if (symbolId != null) { + onSymbolTappedPlatform(symbolId); + } + break; + case 'line#onTap': + final String lineId = call.arguments['line']; + if (lineId != null) { + onLineTappedPlatform(lineId); + } + break; + case 'circle#onTap': + final String circleId = call.arguments['circle']; + if (circleId != null) { + onCircleTappedPlatform(circleId); + } + break; + case 'camera#onMoveStarted': + onCameraMoveStartedPlatform(null); + break; + case 'camera#onMove': + final CameraPosition cameraPosition = + CameraPosition.fromMap(call.arguments['position']); + onCameraMovePlatform(cameraPosition); + break; + case 'camera#onIdle': + onCameraIdlePlatform(null); + break; + case 'map#onStyleLoaded': + onMapStyleLoadedPlatform(null); + break; + case 'map#onMapClick': + final double x = call.arguments['x']; + final double y = call.arguments['y']; + final double lng = call.arguments['lng']; + final double lat = call.arguments['lat']; + onMapClickPlatform( + {'point': Point(x, y), 'latLng': LatLng(lat, lng)}); + break; + case 'map#onMapLongClick': + final double x = call.arguments['x']; + final double y = call.arguments['y']; + final double lng = call.arguments['lng']; + final double lat = call.arguments['lat']; + onMapLongClickPlatform( + {'point': Point(x, y), 'latLng': LatLng(lat, lng)}); + + break; + case 'map#onCameraTrackingChanged': + final int mode = call.arguments['mode']; + onCameraTrackingChangedPlatform(MyLocationTrackingMode.values[mode]); + break; + case 'map#onCameraTrackingDismissed': + onCameraTrackingDismissedPlatform(null); + break; + case 'map#onIdle': + onMapIdlePlatform(null); + break; + default: + throw MissingPluginException(); + } + } + + @override + Future initPlatform(int id) async { + assert(id != null); + _channel = MethodChannel('plugins.flutter.io/mapbox_maps_$id'); + await _channel.invokeMethod('map#waitForMap'); + _channel.setMethodCallHandler(_handleMethodCall); + } + + @override + Widget buildView( + Map creationParams, + Function onPlatformViewCreated, + Set> gestureRecognizers) { + if (defaultTargetPlatform == TargetPlatform.android) { + return AndroidView( + viewType: 'plugins.flutter.io/mapbox_gl', + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + return UiKitView( + viewType: 'plugins.flutter.io/mapbox_gl', + onPlatformViewCreated: onPlatformViewCreated, + gestureRecognizers: gestureRecognizers, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } + return Text( + '$defaultTargetPlatform is not yet supported by the maps plugin'); + } + + @override + Future updateMapOptions( + Map optionsUpdate) async { + final dynamic json = await _channel.invokeMethod( + 'map#update', + { + 'options': optionsUpdate, + }, + ); + return CameraPosition.fromMap(json); + } + + @override + Future animateCamera(cameraUpdate) async { + return await _channel.invokeMethod('camera#animate', { + 'cameraUpdate': cameraUpdate.toJson(), + }); + } + + @override + Future moveCamera(CameraUpdate cameraUpdate) async { + return await _channel.invokeMethod('camera#move', { + 'cameraUpdate': cameraUpdate.toJson(), + }); + } + + @override + Future updateMyLocationTrackingMode( + MyLocationTrackingMode myLocationTrackingMode) async { + await _channel + .invokeMethod('map#updateMyLocationTrackingMode', { + 'mode': myLocationTrackingMode.index, + }); + } + + @override + Future matchMapLanguageWithDeviceDefault() async { + await _channel.invokeMethod('map#matchMapLanguageWithDeviceDefault'); + } + + @override + Future updateContentInsets(EdgeInsets insets, bool animated) async { + await _channel.invokeMethod('map#updateContentInsets', { + 'bounds': { + 'top': insets.top, + 'left': insets.left, + 'bottom': insets.bottom, + 'right': insets.right, + }, + 'animated': animated, + }); + } + + @override + Future setMapLanguage(String language) async { + await _channel.invokeMethod('map#setMapLanguage', { + 'language': language, + }); + } + + @override + Future setTelemetryEnabled(bool enabled) async { + await _channel.invokeMethod('map#setTelemetryEnabled', { + 'enabled': enabled, + }); + } + + @override + Future getTelemetryEnabled() async { + return await _channel.invokeMethod('map#getTelemetryEnabled'); + } + + @override + Future> addSymbols(List options, [List data]) async { + final List symbolIds = await _channel.invokeMethod( + 'symbols#addAll', + { + 'options': options.map((o) => o.toJson()).toList(), + }, + ); + final List symbols = symbolIds.asMap().map( + (i, id) => MapEntry( + i, + Symbol( + id, + options.elementAt(i), + data != null && data.length > i ? data.elementAt(i) : null + ) + ) + ).values.toList(); + + return symbols; + } + + @override + Future updateSymbol(Symbol symbol, SymbolOptions changes) async { + await _channel.invokeMethod('symbol#update', { + 'symbol': symbol.id, + 'options': changes.toJson(), + }); + } + + @override + Future getSymbolLatLng(Symbol symbol) async{ + Map mapLatLng = + await _channel.invokeMethod('symbol#getGeometry', { + 'symbol': symbol._id, + }); + LatLng symbolLatLng = + new LatLng(mapLatLng['latitude'], mapLatLng['longitude']); + return symbolLatLng; + } + + @override + Future removeSymbols(Iterable ids) async { + await _channel.invokeMethod('symbols#removeAll', { + 'symbols': ids.toList(), + }); + } + + @override + Future addLine(LineOptions options, [Map data]) async { + final String lineId = await _channel.invokeMethod( + 'line#add', + { + 'options': options.toJson(), + }, + ); + return Line(lineId, options, data); + } + + @override + Future updateLine(Line line, LineOptions changes) async { + await _channel.invokeMethod('line#update', { + 'line': line.id, + 'options': changes.toJson(), + }); + } + + @override + Future> getLineLatLngs(Line line) async{ + List latLngList = + await _channel.invokeMethod('line#getGeometry', { + 'line': line._id, + }); + List resultList = []; + for (var latLng in latLngList) { + resultList.add(LatLng(latLng['latitude'], latLng['longitude'])); + } + return resultList; + } + + @override + Future removeLine(String lineId) async { + await _channel.invokeMethod('line#remove', { + 'line': lineId, + }); + } + + @override + Future addCircle(CircleOptions options, [Map data]) async { + final String circleId = await _channel.invokeMethod( + 'circle#add', + { + 'options': options.toJson(), + }, + ); + return Circle(circleId, options, data); + } + + @override + Future updateCircle(Circle circle, CircleOptions changes) async { + await _channel.invokeMethod('circle#update', { + 'circle': circle.id, + 'options': changes.toJson(), + }); + } + + @override + Future getCircleLatLng(Circle circle) async { + Map mapLatLng = + await _channel.invokeMethod('circle#getGeometry', { + 'circle': circle.id, + }); + return LatLng(mapLatLng['latitude'], mapLatLng['longitude']); + } + + @override + Future removeCircle(String circleId) async { + await _channel.invokeMethod('circle#remove', { + 'circle': circleId, + }); + } + + @override + Future queryRenderedFeatures( + Point point, List layerIds, List filter) async { + try { + final Map reply = await _channel.invokeMethod( + 'map#queryRenderedFeatures', + { + 'x': point.x, + 'y': point.y, + 'layerIds': layerIds, + 'filter': filter, + }, + ); + return reply['features']; + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future queryRenderedFeaturesInRect( + Rect rect, List layerIds, String filter) async { + try { + final Map reply = await _channel.invokeMethod( + 'map#queryRenderedFeatures', + { + 'left': rect.left, + 'top': rect.top, + 'right': rect.right, + 'bottom': rect.bottom, + 'layerIds': layerIds, + 'filter': filter, + }, + ); + return reply['features']; + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future invalidateAmbientCache() async { + try { + await _channel.invokeMethod('map#invalidateAmbientCache'); + return null; + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future requestMyLocationLatLng() async { + try { + final Map reply = await _channel.invokeMethod( + 'locationComponent#getLastLocation', null); + double latitude = 0.0, longitude = 0.0; + if (reply.containsKey("latitude") && reply["latitude"] != null) { + latitude = double.parse(reply["latitude"].toString()); + } + if (reply.containsKey("longitude") && reply["longitude"] != null) { + longitude = double.parse(reply["longitude"].toString()); + } + return LatLng(latitude, longitude); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future getVisibleRegion() async { + try { + final Map reply = + await _channel.invokeMethod('map#getVisibleRegion', null); + LatLng southwest, northeast; + if (reply.containsKey("sw")) { + List coordinates = reply["sw"]; + southwest = LatLng(coordinates[0], coordinates[1]); + } + if (reply.containsKey("ne")) { + List coordinates = reply["ne"]; + northeast = LatLng(coordinates[0], coordinates[1]); + } + return LatLngBounds(southwest: southwest, northeast: northeast); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future addImage(String name, Uint8List bytes, + [bool sdf = false]) async { + try { + return await _channel.invokeMethod('style#addImage', { + "name": name, + "bytes": bytes, + "length": bytes.length, + "sdf": sdf + }); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future setSymbolIconAllowOverlap(bool enable) async { + try { + await _channel + .invokeMethod('symbolManager#iconAllowOverlap', { + 'iconAllowOverlap': enable, + }); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future setSymbolIconIgnorePlacement(bool enable) async { + try { + await _channel + .invokeMethod('symbolManager#iconIgnorePlacement', { + 'iconIgnorePlacement': enable, + }); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future setSymbolTextAllowOverlap(bool enable) async { + try { + await _channel + .invokeMethod('symbolManager#textAllowOverlap', { + 'textAllowOverlap': enable, + }); + } on PlatformException catch (e) { + return new Future.error(e); + } + } + + @override + Future setSymbolTextIgnorePlacement(bool enable) async { + try { + await _channel + .invokeMethod('symbolManager#textIgnorePlacement', { + 'textIgnorePlacement': enable, + }); + } on PlatformException catch (e) { + return new Future.error(e); + } + } +} diff --git a/lib/src/symbol.dart b/mapbox_gl_platform_interface/lib/src/symbol.dart similarity index 94% rename from lib/src/symbol.dart rename to mapbox_gl_platform_interface/lib/src/symbol.dart index 2540582dd..4f4ea50d4 100644 --- a/lib/src/symbol.dart +++ b/mapbox_gl_platform_interface/lib/src/symbol.dart @@ -4,11 +4,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of mapbox_gl; +part of mapbox_gl_platform_interface; class Symbol { - @visibleForTesting - Symbol(this._id, this._options, [this._data]); + Symbol(this._id, this.options, [this._data]); /// A unique identifier for this symbol. /// @@ -17,16 +16,15 @@ class Symbol { String get id => _id; - SymbolOptions _options; - final Map _data; Map get data => _data; + /// The symbol configuration options most recently applied programmatically /// via the map controller. /// /// The returned value does not reflect any changes made to the symbol through /// touch events. Add listeners to the owning map controller to track those. - SymbolOptions get options => _options; + SymbolOptions options; } dynamic _offsetToJson(Offset offset) { @@ -103,9 +101,7 @@ class SymbolOptions { final int zIndex; final bool draggable; - static const SymbolOptions defaultOptions = SymbolOptions( - - ); + static const SymbolOptions defaultOptions = SymbolOptions(); SymbolOptions copyWith(SymbolOptions changes) { if (changes == null) { @@ -142,7 +138,7 @@ class SymbolOptions { ); } - dynamic _toJson() { + dynamic toJson() { final Map json = {}; void addIfPresent(String fieldName, dynamic value) { @@ -175,10 +171,9 @@ class SymbolOptions { addIfPresent('textHaloColor', textHaloColor); addIfPresent('textHaloWidth', textHaloWidth); addIfPresent('textHaloBlur', textHaloBlur); - addIfPresent('geometry', geometry?._toJson()); + addIfPresent('geometry', geometry?.toJson()); addIfPresent('zIndex', zIndex); addIfPresent('draggable', draggable); return json; } - -} \ No newline at end of file +} diff --git a/lib/src/ui.dart b/mapbox_gl_platform_interface/lib/src/ui.dart similarity index 95% rename from lib/src/ui.dart rename to mapbox_gl_platform_interface/lib/src/ui.dart index 3e9aaa207..460189533 100644 --- a/lib/src/ui.dart +++ b/mapbox_gl_platform_interface/lib/src/ui.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -part of mapbox_gl; +part of mapbox_gl_platform_interface; class MapboxStyles { static const String MAPBOX_STREETS = "mapbox://styles/mapbox/streets-v11"; @@ -26,7 +26,8 @@ class MapboxStyles { /// Satellite Streets: Global satellite and aerial imagery with unobtrusive labels. Using this /// constant means your map style will always use the latest version and may change as we /// improve the style. - static const String SATELLITE_STREETS = "mapbox://styles/mapbox/satellite-streets-v11"; + static const String SATELLITE_STREETS = + "mapbox://styles/mapbox/satellite-streets-v11"; /// Traffic Day: Color-coded roads based on live traffic congestion data. Traffic data is currently /// available in @@ -83,7 +84,7 @@ class CameraTargetBounds { /// Unbounded camera target. static const CameraTargetBounds unbounded = CameraTargetBounds(null); - dynamic _toJson() => [bounds?._toList()]; + dynamic toJson() => [bounds?.toList()]; @override bool operator ==(dynamic other) { @@ -120,7 +121,7 @@ class MinMaxZoomPreference { static const MinMaxZoomPreference unbounded = MinMaxZoomPreference(null, null); - dynamic _toJson() => [minZoom, maxZoom]; + dynamic toJson() => [minZoom, maxZoom]; @override bool operator ==(dynamic other) { diff --git a/mapbox_gl_platform_interface/pubspec.yaml b/mapbox_gl_platform_interface/pubspec.yaml new file mode 100644 index 000000000..62d650818 --- /dev/null +++ b/mapbox_gl_platform_interface/pubspec.yaml @@ -0,0 +1,13 @@ +name: mapbox_gl_platform_interface +description: A common platform interface for the mapbox_gl plugin. +version: 0.7.0 +homepage: https://github.com/tobrun/flutter-mapbox-gl + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/mapbox_gl_web/CHANGELOG.md b/mapbox_gl_web/CHANGELOG.md new file mode 100644 index 000000000..c15887ba4 --- /dev/null +++ b/mapbox_gl_web/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.7.0 +- Initial version \ No newline at end of file diff --git a/mapbox_gl_web/LICENSE b/mapbox_gl_web/LICENSE new file mode 100644 index 000000000..a4c4948f4 --- /dev/null +++ b/mapbox_gl_web/LICENSE @@ -0,0 +1,6 @@ +flutter-mapbox-gl copyright (c) 2018, Mapbox. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/mapbox_gl_web/README.md b/mapbox_gl_web/README.md new file mode 100644 index 000000000..a6f0ab70d --- /dev/null +++ b/mapbox_gl_web/README.md @@ -0,0 +1 @@ +Contains the web interfaces for the [Flutter Mapbox GL plugin](https://github.com/tobrun/flutter-mapbox-gl). \ No newline at end of file diff --git a/mapbox_gl_web/ios/mapbox_gl_web.podspec b/mapbox_gl_web/ios/mapbox_gl_web.podspec new file mode 100644 index 000000000..ef2337f6a --- /dev/null +++ b/mapbox_gl_web/ios/mapbox_gl_web.podspec @@ -0,0 +1,18 @@ + +Pod::Spec.new do |s| + s.name = 'mapbox_gl_web' + s.version = '0.1.0' + s.summary = 'No-op implementation of mapbox_gl_web web plugin to avoid build issues on iOS' + s.description = <<-DESC + temp fake mapbox_gl_web plugin + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' +end \ No newline at end of file diff --git a/mapbox_gl_web/lib/mapbox_gl_web.dart b/mapbox_gl_web/lib/mapbox_gl_web.dart new file mode 100644 index 000000000..df5ca795f --- /dev/null +++ b/mapbox_gl_web/lib/mapbox_gl_web.dart @@ -0,0 +1,28 @@ +library mapbox_gl_web; + +import 'dart:async'; +// FIXED HERE: https://github.com/dart-lang/linter/pull/1985 +// ignore_for_file: avoid_web_libraries_in_flutter +import 'dart:html'; +import 'dart:js'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart'; +import 'package:mapbox_gl_dart/mapbox_gl_dart.dart' hide Point; +import 'package:mapbox_gl_dart/mapbox_gl_dart.dart' as mapbox show Point; +import 'package:image/image.dart' hide Point; + +part 'src/convert.dart'; +part 'src/mapbox_map_plugin.dart'; +part 'src/options_sink.dart'; +part 'src/feature_manager/feature_manager.dart'; +part 'src/feature_manager/symbol_manager.dart'; +part 'src/feature_manager/line_manager.dart'; +part 'src/feature_manager/circle_manager.dart'; +part 'src/mapbox_map_controller.dart'; diff --git a/mapbox_gl_web/lib/src/convert.dart b/mapbox_gl_web/lib/src/convert.dart new file mode 100644 index 000000000..376cdb1a0 --- /dev/null +++ b/mapbox_gl_web/lib/src/convert.dart @@ -0,0 +1,356 @@ +part of mapbox_gl_web; + +class Convert { + static void interpretMapboxMapOptions( + Map options, MapboxMapOptionsSink sink) { + if (options.containsKey('cameraTargetBounds')) { + final bounds = options['cameraTargetBounds'][0]; + if (bounds == null) { + sink.setCameraTargetBounds(null); + } else { + sink.setCameraTargetBounds( + LatLngBounds( + southwest: LatLng(bounds[0][0], bounds[0][1]), + northeast: LatLng(bounds[1][0], bounds[1][1]), + ), + ); + } + } + if (options.containsKey('compassEnabled')) { + sink.setCompassEnabled(options['compassEnabled']); + } + if (options.containsKey('styleString')) { + sink.setStyleString(options['styleString']); + } + if (options.containsKey('minMaxZoomPreference')) { + sink.setMinMaxZoomPreference(options['minMaxZoomPreference'][0], + options['minMaxZoomPreference'][1]); + } + if (options.containsKey('rotateGesturesEnabled')) { + sink.setRotateGesturesEnabled(options['rotateGesturesEnabled']); + } + if (options.containsKey('scrollGesturesEnabled')) { + sink.setScrollGesturesEnabled(options['scrollGesturesEnabled']); + } + if (options.containsKey('tiltGesturesEnabled')) { + sink.setTiltGesturesEnabled(options['tiltGesturesEnabled']); + } + if (options.containsKey('trackCameraPosition')) { + sink.setTrackCameraPosition(options['trackCameraPosition']); + } + if (options.containsKey('zoomGesturesEnabled')) { + sink.setZoomGesturesEnabled(options['zoomGesturesEnabled']); + } + if (options.containsKey('myLocationEnabled')) { + sink.setMyLocationEnabled(options['myLocationEnabled']); + } + if (options.containsKey('myLocationTrackingMode')) { + sink.setMyLocationTrackingMode(options['myLocationTrackingMode']); + } + if (options.containsKey('myLocationRenderMode')) { + sink.setMyLocationRenderMode(options['myLocationRenderMode']); + } + if (options.containsKey('logoViewMargins')) { + sink.setLogoViewMargins( + options['logoViewMargins'][0], options['logoViewMargins'][1]); + } + if (options.containsKey('compassViewPosition')) { + sink.setCompassGravity(options['compassViewPosition']); + } + if (options.containsKey('compassViewMargins')) { + sink.setCompassViewMargins( + options['compassViewMargins'][0], options['compassViewMargins'][1]); + } + if (options.containsKey('attributionButtonMargins')) { + sink.setAttributionButtonMargins(options['attributionButtonMargins'][0], + options['attributionButtonMargins'][1]); + } + } + + static CameraOptions toCameraOptions( + CameraUpdate cameraUpdate, MapboxMap mapboxMap) { + final List json = cameraUpdate.toJson(); + final type = json[0]; + switch (type) { + case 'newCameraPosition': + final camera = json[1]; + return CameraOptions( + center: LngLat(camera['target'][1], camera['target'][0]), + zoom: camera['zoom'], + pitch: camera['tilt'], + bearing: camera['bearing'], + ); + case 'newLatLng': + final target = json[1]; + return CameraOptions( + center: LngLat(target[1], target[0]), + zoom: mapboxMap.getZoom(), + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + case 'newLatLngBounds': + final bounds = json[1]; + final padding = json[2]; + final camera = mapboxMap.cameraForBounds( + LngLatBounds( + LngLat(bounds[0][1], bounds[0][0]), + LngLat(bounds[1][1], bounds[1][0]), + ), + { + 'padding': { + 'top': padding, + 'bottom': padding, + 'left': padding, + 'right': padding + } + }); + return camera; + case 'newLatLngZoom': + final target = json[1]; + final zoom = json[2]; + return CameraOptions( + center: LngLat(target[1], target[0]), + zoom: zoom, + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + case 'scrollBy': + final x = json[1]; + final y = json[2]; + final mapbox.Point point = mapboxMap.project(mapboxMap.getCenter()); + return CameraOptions( + center: mapboxMap.unproject(mapbox.Point(point.x + x, point.y + y)), + zoom: mapboxMap.getZoom(), + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + + case 'zoomBy': + final zoom = json[1]; + if (json.length == 2) { + return CameraOptions( + center: mapboxMap.getCenter(), + zoom: mapboxMap.getZoom() + zoom, + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + } + final point = json[2]; + return CameraOptions( + center: mapboxMap.unproject(mapbox.Point(point[0], point[1])), + zoom: mapboxMap.getZoom() + zoom, + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + case 'zoomIn': + return CameraOptions( + center: mapboxMap.getCenter(), + zoom: mapboxMap.getZoom() + 1, + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + case 'zoomOut': + return CameraOptions( + center: mapboxMap.getCenter(), + zoom: mapboxMap.getZoom() - 1, + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + case 'zoomTo': + final zoom = json[1]; + return CameraOptions( + center: mapboxMap.getCenter(), + zoom: zoom, + pitch: mapboxMap.getPitch(), + bearing: mapboxMap.getBearing(), + ); + case 'bearingTo': + final bearing = json[1]; + return CameraOptions( + center: mapboxMap.getCenter(), + zoom: mapboxMap.getZoom(), + pitch: mapboxMap.getPitch(), + bearing: bearing, + ); + case 'tiltTo': + final tilt = json[1]; + return CameraOptions( + center: mapboxMap.getCenter(), + zoom: mapboxMap.getZoom(), + pitch: tilt, + bearing: mapboxMap.getBearing(), + ); + default: + throw UnimplementedError('Cannot interpret $type as CameraUpdate'); + } + } + + static Feature interpretSymbolOptions( + SymbolOptions options, Feature feature) { + var properties = feature.properties; + var geometry = feature.geometry; + if (options.iconSize != null) { + properties['iconSize'] = options.iconSize; + } + if (options.iconImage != null) { + properties['iconImage'] = options.iconImage; + } + if (options.iconRotate != null) { + properties['iconRotate'] = options.iconRotate; + } + if (options.iconOffset != null) { + properties['iconOffset'] = [options.iconOffset.dx, options.iconOffset.dy]; + } + if (options.iconAnchor != null) { + properties['iconAnchor'] = options.iconAnchor; + } + if (options.textField != null) { + properties['textField'] = options.textField; + } + if (options.textSize != null) { + properties['textSize'] = options.textSize; + } + if (options.textMaxWidth != null) { + properties['textMaxWidth'] = options.textMaxWidth; + } + if (options.textLetterSpacing != null) { + properties['textLetterSpacing'] = options.textLetterSpacing; + } + if (options.textJustify != null) { + properties['textJustify'] = options.textJustify; + } + if (options.textAnchor != null) { + properties['textAnchor'] = options.textAnchor; + } + if (options.textRotate != null) { + properties['textRotate'] = options.textRotate; + } + if (options.textTransform != null) { + properties['textTransform'] = options.textTransform; + } + if (options.textOffset != null) { + properties['textOffset'] = [options.textOffset.dx, options.textOffset.dy]; + } + if (options.iconOpacity != null) { + properties['iconOpacity'] = options.iconOpacity; + } + if (options.iconColor != null) { + properties['iconColor'] = options.iconColor; + } + if (options.iconHaloColor != null) { + properties['iconHaloColor'] = options.iconHaloColor; + } + if (options.iconHaloWidth != null) { + properties['iconHaloWidth'] = options.iconHaloWidth; + } + if (options.iconHaloBlur != null) { + properties['iconHaloBlur'] = options.iconHaloBlur; + } + if (options.textOpacity != null) { + properties['textOpacity'] = options.textOpacity; + } + if (options.textColor != null) { + properties['textColor'] = options.textColor; + } + if (options.textHaloColor != null) { + properties['textHaloColor'] = options.textHaloColor; + } + if (options.textHaloWidth != null) { + properties['textHaloWidth'] = options.textHaloWidth; + } + if (options.textHaloBlur != null) { + properties['textHaloBlur'] = options.textHaloBlur; + } + if (options.geometry != null) { + geometry = Geometry( + type: geometry.type, + coordinates: [options.geometry.longitude, options.geometry.latitude], + ); + } + if (options.zIndex != null) { + properties['symbolSortKey'] = options.zIndex; + } + if (options.draggable != null) { + properties['draggable'] = options.draggable; + } + return feature.copyWith(properties: properties, geometry: geometry); + } + + static Feature interpretLineOptions(LineOptions options, Feature feature) { + var properties = feature.properties; + var geometry = feature.geometry; + if (options.lineJoin != null) { + properties['lineJoin'] = options.lineJoin; + } + if (options.lineOpacity != null) { + properties['lineOpacity'] = options.lineOpacity; + } + if (options.lineColor != null) { + properties['lineColor'] = options.lineColor; + } + if (options.lineWidth != null) { + properties['lineWidth'] = options.lineWidth; + } + if (options.lineGapWidth != null) { + properties['lineGapWidth'] = options.lineGapWidth; + } + if (options.lineOffset != null) { + properties['lineOffset'] = options.lineOffset; + } + if (options.lineBlur != null) { + properties['lineBlur'] = options.lineBlur; + } + if (options.linePattern != null) { + properties['linePattern'] = options.linePattern; + } + if (options.geometry != null) { + geometry = Geometry( + type: geometry.type, + coordinates: options.geometry + .map((latLng) => [latLng.longitude, latLng.latitude]) + .toList(), + ); + } + if (options.draggable != null) { + properties['draggable'] = options.draggable; + } + return feature.copyWith(properties: properties, geometry: geometry); + } + + static Feature interpretCircleOptions( + CircleOptions options, Feature feature) { + var properties = feature.properties; + var geometry = feature.geometry; + if (options.circleRadius != null) { + properties['circleRadius'] = options.circleRadius; + } + if (options.circleColor != null) { + properties['circleColor'] = options.circleColor; + } + if (options.circleBlur != null) { + properties['circleBlur'] = options.circleBlur; + } + if (options.circleOpacity != null) { + properties['circleOpacity'] = options.circleOpacity; + } + if (options.circleStrokeWidth != null) { + properties['circleStrokeWidth'] = options.circleStrokeWidth; + } + if (options.circleStrokeColor != null) { + properties['circleStrokeColor'] = options.circleStrokeColor; + } + if (options.circleStrokeOpacity != null) { + properties['circleStrokeOpacity'] = options.circleStrokeOpacity; + } + if (options.geometry != null) { + geometry = Geometry( + type: geometry.type, + coordinates: [options.geometry.longitude, options.geometry.latitude], + ); + } + if (options.draggable != null) { + properties['draggable'] = options.draggable; + } + return feature.copyWith(properties: properties, geometry: geometry); + } +} diff --git a/mapbox_gl_web/lib/src/feature_manager/circle_manager.dart b/mapbox_gl_web/lib/src/feature_manager/circle_manager.dart new file mode 100644 index 000000000..c55e3e1fd --- /dev/null +++ b/mapbox_gl_web/lib/src/feature_manager/circle_manager.dart @@ -0,0 +1,49 @@ +part of mapbox_gl_web; + +/// Signature for when a tap has occurred. +typedef CircleTapCallback = void Function(String id); + +class CircleManager extends FeatureManager { + final MapboxMap map; + final CircleTapCallback onTap; + + CircleManager({ + @required this.map, + this.onTap, + }) : super( + sourceId: 'circle_source', + layerId: 'circle_layer', + map: map, + onTap: onTap, + ); + + @override + void initLayer() { + map.addLayer({ + 'id': layerId, + 'type': 'circle', + 'source': sourceId, + 'paint': { + 'circle-radius': ['get', 'circleRadius'], + 'circle-color': ['get', 'circleColor'], + 'circle-blur': ['get', 'circleBlur'], + 'circle-opacity': ['get', 'circleOpacity'], + 'circle-stroke-width': ['get', 'circleStrokeWidth'], + 'circle-stroke-color': ['get', 'circleStrokeColor'], + 'circle-stroke-opacity': ['get', 'circleStrokeOpacity'], + } + }); + } + + @override + void onDrag(String featureId, LatLng latLng) { + update(featureId, CircleOptions(geometry: latLng)); + } + + @override + void update(String lineId, CircleOptions changes) { + Feature olfFeature = getFeature(lineId); + Feature newFeature = Convert.interpretCircleOptions(changes, olfFeature); + updateFeature(newFeature); + } +} diff --git a/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart b/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart new file mode 100644 index 000000000..34ebe17fe --- /dev/null +++ b/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart @@ -0,0 +1,115 @@ +part of mapbox_gl_web; + +/// Signature for when a tap has occurred. +typedef FeatureTapCallback = void Function(String id); + +abstract class FeatureManager { + final String sourceId; + final String layerId; + final MapboxMap map; + final FeatureTapCallback onTap; + + final Map _features = {}; + num featureCounter = 1; + String _draggableFeatureId; + + FeatureManager({ + @required this.sourceId, + @required this.layerId, + @required this.map, + this.onTap, + }) { + var featureSource = GeoJsonSource(data: FeatureCollection(features: [])); + map.addSource(sourceId, featureSource); + initLayer(); + _initClickHandler(); + _initDragHandler(); + } + + void initLayer(); + + void update(String featureId, T changes); + + void onDrag(String featureId, LatLng latLng); + + String add(Feature feature) { + feature.id = featureCounter++; + _features['${feature.id}'] = feature; + _updateSource(); + return '${feature.id}'; + } + + + void updateFeature(Feature feature) { + updateFeatures([feature]); + } + + + void updateFeatures(Iterable features) { + features.forEach( + (feature) => _features['${feature.id}'] = feature + ); + _updateSource(); + } + + void remove(String featureId) { + removeAll([featureId]); + } + + void removeAll(Iterable featuresIds) { + featuresIds.forEach( + (featureId) => _features.remove(featureId) + ); + _updateSource(); + } + + Feature getFeature(String featureId) { + return _features[featureId]; + } + + void _initClickHandler() { + map.on('click', layerId, (e) { + if (onTap != null) { + onTap('${e.features[0].id}'); + } + }); + + map.on('mouseenter', layerId, (_) { + map.getCanvas().style.cursor = 'pointer'; + }); + + map.on('mouseleave', layerId, (_) { + map.getCanvas().style.cursor = ''; + }); + } + + void _initDragHandler() { + map.on('mousedown', layerId, (e) { + var isDraggable = e.features[0].properties['draggable']; + if (isDraggable != null && isDraggable) { + // Prevent the default map drag behavior. + e.preventDefault(); + _draggableFeatureId = '${e.features[0].id}'; + map.getCanvas().style.cursor = 'grabbing'; + } + }); + + map.on('mousemove', (e) { + if (_draggableFeatureId != null) { + var coords = e.lngLat; + onDrag(_draggableFeatureId, LatLng(coords.lat, coords.lng)); + } + }); + + map.on('mouseup', (_) { + _draggableFeatureId = null; + map.getCanvas().style.cursor = ''; + }); + } + + void _updateSource() { + GeoJsonSource featureSource = map.getSource(sourceId); + featureSource + .setData(FeatureCollection(features: _features.values.toList())); + } +} diff --git a/mapbox_gl_web/lib/src/feature_manager/line_manager.dart b/mapbox_gl_web/lib/src/feature_manager/line_manager.dart new file mode 100644 index 000000000..27a668bc6 --- /dev/null +++ b/mapbox_gl_web/lib/src/feature_manager/line_manager.dart @@ -0,0 +1,53 @@ +part of mapbox_gl_web; + +/// Signature for when a tap has occurred. +typedef LineTapCallback = void Function(String id); + +class LineManager extends FeatureManager { + final MapboxMap map; + final LineTapCallback onTap; + + LineManager({ + @required this.map, + this.onTap, + }) : super( + sourceId: 'line_source', + layerId: 'line_layer', + map: map, + onTap: onTap, + ); + + @override + void initLayer() { + // NOTE: line-pattern disable line-color + map.addLayer({ + 'id': layerId, + 'type': 'line', + 'source': sourceId, + 'layout': { + 'line-join': ['get', 'lineJoin'], + }, + 'paint': { + 'line-opacity': ['get', 'lineOpacity'], + 'line-color': ['get', 'lineColor'], + 'line-width': ['get', 'lineWidth'], + 'line-gap-width': ['get', 'lineGapWidth'], + 'line-offset': ['get', 'lineOffset'], + 'line-blur': ['get', 'lineBlur'], + //'line-pattern': ['get', 'linePattern'], + } + }); + } + + void update(String lineId, LineOptions changes) { + Feature olfFeature = getFeature(lineId); + Feature newFeature = Convert.interpretLineOptions(changes, olfFeature); + updateFeature(newFeature); + } + + @override + void onDrag(String featureId, LatLng latLng) { + // TODO: implement onDrag + print('onDrag is not already implemented'); + } +} diff --git a/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart b/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart new file mode 100644 index 000000000..7ed68a210 --- /dev/null +++ b/mapbox_gl_web/lib/src/feature_manager/symbol_manager.dart @@ -0,0 +1,100 @@ +part of mapbox_gl_web; + +/// Signature for when a tap has occurred. +typedef SymbolTapCallback = void Function(String id); + +class SymbolManager extends FeatureManager { + final MapboxMap map; + final SymbolTapCallback onTap; + + SymbolManager({ + @required this.map, + this.onTap, + }) : super( + sourceId: 'symbol_source', + layerId: 'symbol_layer', + map: map, + onTap: onTap, + ); + + @override + void initLayer() { + map.addLayer({ + 'id': layerId, + 'type': 'symbol', + 'source': sourceId, + 'layout': { + 'icon-image': '{iconImage}', + 'icon-size': ['get', 'iconSize'], + 'icon-rotate': ['get', 'iconRotate'], + 'icon-offset': ['get', 'iconOffset'], + 'icon-anchor': ['get', 'iconAnchor'], + 'text-field': ['get', 'textField'], + 'text-size': ['get', 'textSize'], + 'text-max-width': ['get', 'textMaxWidth'], + 'text-letter-spacing': ['get', 'textLetterSpacing'], + 'text-justify': ['get', 'textJustify'], + 'text-anchor': ['get', 'textAnchor'], + 'text-rotate': ['get', 'textRotate'], + 'text-transform': ['get', 'textTransform'], + 'text-offset': ['get', 'textOffset'], + 'symbol-sort-key': ['get', 'symbolSortKey'], + 'icon-allow-overlap': true, + 'icon-ignore-placement': true, + 'text-allow-overlap': true, + 'text-ignore-placement': true, + }, + 'paint': { + 'icon-opacity': ['get', 'iconOpacity'], + 'icon-color': ['get', 'iconColor'], + 'icon-halo-color': ['get', 'iconHaloColor'], + 'icon-halo-width': ['get', 'iconHaloWidth'], + 'icon-halo-blur': ['get', 'iconHaloBlur'], + 'text-opacity': ['get', 'textOpacity'], + 'text-color': ['get', 'textColor'], + 'text-halo-color': ['get', 'textHaloColor'], + 'text-halo-width': ['get', 'textHaloWidth'], + 'text-halo-blur': ['get', 'textHaloBlur'], + } + }); + + map.on('styleimagemissing', (event) { + if (event.id == '') { + return; + } + var density = context['window'].devicePixelRatio ?? 1; + var imagePath = density == 1 + ? '/assets/assets/symbols/custom-icon.png' + : '/assets/assets/symbols/$density.0x/custom-icon.png'; + map.loadImage(imagePath, (error, image) { + if (error != null) throw error; + if (!map.hasImage(event.id)) + map.addImage(event.id, image, {'pixelRatio': density}); + }); + }); + } + + @override + void update(String lineId, SymbolOptions changes) { + updateAll({lineId: changes}); + } + + + void updateAll(Map changesById) { + List featuresWithUpdatedOptions = []; + changesById.forEach( + (id, options) => featuresWithUpdatedOptions.add( + Convert.interpretSymbolOptions( + options, + getFeature(id) + ) + ) + ); + updateFeatures(featuresWithUpdatedOptions); + } + + @override + void onDrag(String featureId, LatLng latLng) { + update(featureId, SymbolOptions(geometry: latLng)); + } +} diff --git a/mapbox_gl_web/lib/src/mapbox_map_controller.dart b/mapbox_gl_web/lib/src/mapbox_map_controller.dart new file mode 100644 index 000000000..7f970fcac --- /dev/null +++ b/mapbox_gl_web/lib/src/mapbox_map_controller.dart @@ -0,0 +1,614 @@ +part of mapbox_gl_web; + +class MapboxMapController extends MapboxGlPlatform + implements MapboxMapOptionsSink { + DivElement _mapElement; + + Map _creationParams; + MapboxMap _map; + + SymbolManager symbolManager; + LineManager lineManager; + CircleManager circleManager; + + bool _trackCameraPosition = false; + GeolocateControl _geolocateControl; + LatLng _myLastLocation; + + String _navigationControlPosition; + NavigationControl _navigationControl; + + @override + Widget buildView( + Map creationParams, + Function onPlatformViewCreated, + Set> gestureRecognizers) { + _creationParams = creationParams; + _registerViewFactory(onPlatformViewCreated, this.hashCode); + return HtmlElementView( + viewType: 'plugins.flutter.io/mapbox_gl_${this.hashCode}'); + } + + void _registerViewFactory(Function(int) callback, int identifier) { + // ignore: undefined_prefixed_name + ui.platformViewRegistry.registerViewFactory( + 'plugins.flutter.io/mapbox_gl_$identifier', (int viewId) { + _mapElement = DivElement(); + callback(viewId); + return _mapElement; + }); + } + + @override + Future initPlatform(int id) async { + await _addStylesheetToShadowRoot(); + if (_creationParams.containsKey('initialCameraPosition')) { + var camera = _creationParams['initialCameraPosition']; + if (_creationParams.containsKey('accessToken')) { + Mapbox.accessToken = _creationParams['accessToken']; + } + _map = MapboxMap( + MapOptions( + container: _mapElement, + style: 'mapbox://styles/mapbox/streets-v11', + center: LngLat(camera['target'][1], camera['target'][0]), + zoom: camera['zoom'], + bearing: camera['bearing'], + pitch: camera['tilt'], + ), + ); + _map.on('load', _onStyleLoaded); + } + Convert.interpretMapboxMapOptions(_creationParams['options'], this); + } + + Future _addStylesheetToShadowRoot() async { + int index = -1; + while (index == -1) { + index = document.getElementsByTagName('flt-platform-view').length - 1; + await Future.delayed(Duration(milliseconds: 10)); + } + HtmlElement e = document.getElementsByTagName('flt-platform-view')[index] + as HtmlElement; + + LinkElement link = LinkElement(); + link.href = + 'https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.1/mapbox-gl.css'; + link.rel = 'stylesheet'; + e.shadowRoot.append(link); + + await link.onLoad.first; + } + + @override + Future updateMapOptions( + Map optionsUpdate) async { + // FIX: why is called indefinitely? (map_ui page) + Convert.interpretMapboxMapOptions(optionsUpdate, this); + return _getCameraPosition(); + } + + @override + Future animateCamera(CameraUpdate cameraUpdate) async { + final cameraOptions = Convert.toCameraOptions(cameraUpdate, _map); + if (cameraOptions != null) { + _map.flyTo(cameraOptions); + } + return true; + } + + @override + Future moveCamera(CameraUpdate cameraUpdate) async { + final cameraOptions = Convert.toCameraOptions(cameraUpdate, _map); + if (cameraOptions != null) { + _map.jumpTo(cameraOptions); + } + return true; + } + + @override + Future updateMyLocationTrackingMode( + MyLocationTrackingMode myLocationTrackingMode) async { + setMyLocationTrackingMode(myLocationTrackingMode.index); + } + + @override + Future matchMapLanguageWithDeviceDefault() async { + setMapLanguage(ui.window.locale.languageCode); + } + + @override + Future setMapLanguage(String language) async { + _map.setLayoutProperty( + 'country-label', + 'text-field', + ['get', 'name_' + language], + ); + } + + @override + Future setTelemetryEnabled(bool enabled) async { + print('Telemetry not available in web'); + return; + } + + @override + Future getTelemetryEnabled() async { + print('Telemetry not available in web'); + return false; + } + + @override + Future> addSymbols(List options, [List data]) async { + Map optionsById = Map.fromIterable( + options, + key: (o) => symbolManager.add( + Feature( + geometry: Geometry( + type: 'Point', + coordinates: [o.geometry.longitude, o.geometry.latitude], + ), + ) + ), + value: (o) => o + ); + symbolManager.updateAll(optionsById); + + return optionsById.map( + (id, singleOptions) { + int dataIndex = options.indexOf(singleOptions); + Map singleData = data != null && data.length >= dataIndex + 1 ? data[dataIndex] : null; + return MapEntry( + id, + Symbol(id, singleOptions, singleData) + ); + } + ).values.toList(); + } + + @override + Future updateSymbol(Symbol symbol, SymbolOptions changes) async { + symbolManager.update(symbol.id, changes); + } + + @override + Future removeSymbols(Iterable symbolsIds) async { + symbolManager.removeAll(symbolsIds); + } + + @override + Future addLine(LineOptions options, [Map data]) async { + String lineId = lineManager.add(Feature( + geometry: Geometry( + type: 'LineString', + coordinates: options.geometry + .map((latLng) => [latLng.longitude, latLng.latitude]) + .toList(), + ), + )); + lineManager.update(lineId, options); + return Line(lineId, options, data); + } + + @override + Future updateLine(Line line, LineOptions changes) async { + lineManager.update(line.id, changes); + } + + @override + Future removeLine(String lineId) async { + lineManager.remove(lineId); + } + + @override + Future addCircle(CircleOptions options, [Map data]) async { + String circleId = circleManager.add(Feature( + geometry: Geometry( + type: 'Point', + coordinates: [options.geometry.longitude, options.geometry.latitude], + ), + )); + circleManager.update(circleId, options); + return Circle(circleId, options, data); + } + + @override + Future updateCircle(Circle circle, CircleOptions changes) async { + circleManager.update(circle.id, changes); + } + + @override + Future getCircleLatLng(Circle circle) async { + var coordinates = circleManager.getFeature(circle.id).geometry.coordinates; + return LatLng(coordinates[1], coordinates[0]); + } + + @override + Future removeCircle(String circleId) async { + circleManager.remove(circleId); + } + + @override + Future queryRenderedFeatures( + Point point, List layerIds, List filter) async { + Map options = {}; + if (layerIds.length > 0) { + options['layers'] = layerIds; + } + if (filter != null) { + options['filter'] = filter; + } + return _map.queryRenderedFeatures([point, point], options); + } + + @override + Future queryRenderedFeaturesInRect( + Rect rect, List layerIds, String filter) async { + Map options = {}; + if (layerIds.length > 0) { + options['layers'] = layerIds; + } + if (filter != null) { + options['filter'] = filter; + } + return _map.queryRenderedFeatures([ + Point(rect.left, rect.bottom), + Point(rect.right, rect.top), + ], options); + } + + @override + Future invalidateAmbientCache() async { + print('Offline storage not available in web'); + } + + @override + Future requestMyLocationLatLng() async { + return _myLastLocation; + } + + @override + Future getVisibleRegion() async { + final bounds = _map.getBounds(); + return LatLngBounds( + southwest: LatLng(bounds.getSouthWest().lat, bounds.getSouthWest().lng), + northeast: LatLng(bounds.getNorthEast().lat, bounds.getNorthEast().lng), + ); + } + + @override + Future addImage(String name, Uint8List bytes, + [bool sdf = false]) async { + final photo = decodeImage(bytes); + if (!_map.hasImage(name)) { + _map.addImage( + name, + { + 'width': photo.width, + 'height': photo.height, + 'data': photo.getBytes(), + }, + {'sdf': sdf}, + ); + } + } + + @override + Future setSymbolIconAllowOverlap(bool enable) async { + //TODO: to implement + print('setSymbolIconAllowOverlap not implemented yet'); + } + + @override + Future setSymbolIconIgnorePlacement(bool enable) async { + //TODO: to implement + print('setSymbolIconIgnorePlacement not implemented yet'); + } + + @override + Future setSymbolTextAllowOverlap(bool enable) async { + //TODO: to implement + print('setSymbolTextAllowOverlap not implemented yet'); + } + + @override + Future setSymbolTextIgnorePlacement(bool enable) async { + //TODO: to implement + print('setSymbolTextIgnorePlacement not implemented yet'); + } + + CameraPosition _getCameraPosition() { + if (_trackCameraPosition) { + final center = _map.getCenter(); + return CameraPosition( + bearing: _map.getBearing(), + target: LatLng(center.lat, center.lng), + tilt: _map.getPitch(), + zoom: _map.getZoom(), + ); + } + return null; + } + + void _onStyleLoaded(_) { + symbolManager = SymbolManager(map: _map, onTap: onSymbolTappedPlatform); + lineManager = LineManager(map: _map, onTap: onLineTappedPlatform); + circleManager = CircleManager(map: _map, onTap: onCircleTappedPlatform); + onMapStyleLoadedPlatform(null); + _map.on('click', _onMapClick); + // long click not available in web, so it is mapped to double click + _map.on('dblclick', _onMapLongClick); + _map.on('movestart', _onCameraMoveStarted); + _map.on('move', _onCameraMove); + _map.on('moveend', _onCameraIdle); + } + + void _onMapClick(e) { + onMapClickPlatform({ + 'point': Point(e.point.x, e.point.y), + 'latLng': LatLng(e.lngLat.lat, e.lngLat.lng), + }); + } + + void _onMapLongClick(e) { + onMapLongClickPlatform({ + 'point': Point(e.point.x, e.point.y), + 'latLng': LatLng(e.lngLat.lat, e.lngLat.lng), + }); + } + + void _onCameraMoveStarted(_) { + onCameraMoveStartedPlatform(null); + } + + void _onCameraMove(_) { + final center = _map.getCenter(); + var camera = CameraPosition( + bearing: _map.getBearing(), + target: LatLng(center.lat, center.lng), + tilt: _map.getPitch(), + zoom: _map.getZoom(), + ); + onCameraMovePlatform(camera); + } + + void _onCameraIdle(_) { + onCameraIdlePlatform(null); + } + + void _onCameraTrackingChanged(bool isTracking) { + if (isTracking) { + onCameraTrackingChangedPlatform(MyLocationTrackingMode.Tracking); + } else { + onCameraTrackingChangedPlatform(MyLocationTrackingMode.None); + } + } + + void _onCameraTrackingDismissed() { + onCameraTrackingDismissedPlatform(null); + } + + void _addGeolocateControl({bool trackUserLocation}) { + _removeGeolocateControl(); + _geolocateControl = GeolocateControl( + GeolocateControlOptions( + positionOptions: PositionOptions(enableHighAccuracy: true), + trackUserLocation: trackUserLocation ?? false, + showAccuracyCircle: true, + showUserLocation: true, + ), + ); + _geolocateControl.on('geolocate', (e) { + _myLastLocation = LatLng(e.coords.latitude, e.coords.longitude); + }); + _geolocateControl.on('trackuserlocationstart', (_) { + _onCameraTrackingChanged(true); + }); + _geolocateControl.on('trackuserlocationend', (_) { + _onCameraTrackingChanged(false); + _onCameraTrackingDismissed(); + }); + _map.addControl(_geolocateControl, 'bottom-right'); + } + + void _removeGeolocateControl() { + if (_geolocateControl != null) { + _map.removeControl(_geolocateControl); + _geolocateControl = null; + } + } + + void _updateNavigationControl({ + bool compassEnabled, + CompassViewPosition position, + }) { + bool prevShowCompass; + if (_navigationControl != null) { + prevShowCompass = _navigationControl.options.showCompass; + } + String prevPosition = _navigationControlPosition; + + String positionString; + switch (position) { + case CompassViewPosition.TopRight: + positionString = 'top-right'; + break; + case CompassViewPosition.TopLeft: + positionString = 'top-left'; + break; + case CompassViewPosition.BottomRight: + positionString = 'bottom-right'; + break; + case CompassViewPosition.BottomLeft: + positionString = 'bottom-left'; + break; + default: + positionString = null; + } + + bool newShowComapss = compassEnabled ?? prevShowCompass ?? false; + String newPosition = positionString ?? prevPosition ?? null; + + _removeNavigationControl(); + _navigationControl = NavigationControl(NavigationControlOptions( + showCompass: newShowComapss, + showZoom: false, + visualizePitch: false, + )); + + if (newPosition == null) { + _map.addControl(_navigationControl); + } else { + _map.addControl(_navigationControl, newPosition); + _navigationControlPosition = newPosition; + } + } + + void _removeNavigationControl() { + if (_navigationControl != null) { + _map.removeControl(_navigationControl); + _navigationControl = null; + } + } + + /* + * MapboxMapOptionsSink + */ + @override + void setAttributionButtonMargins(int x, int y) { + print('setAttributionButtonMargins not available in web'); + } + + @override + void setCameraTargetBounds(LatLngBounds bounds) { + if (bounds == null) { + _map.setMaxBounds(null); + } else { + _map.setMaxBounds( + LngLatBounds( + LngLat( + bounds.southwest.longitude, + bounds.southwest.latitude, + ), + LngLat( + bounds.northeast.longitude, + bounds.northeast.latitude, + ), + ), + ); + } + } + + @override + void setCompassEnabled(bool compassEnabled) { + _updateNavigationControl(compassEnabled: compassEnabled); + } + + @override + void setCompassGravity(int gravity) { + _updateNavigationControl(position: CompassViewPosition.values[gravity]); + } + + @override + void setCompassViewMargins(int x, int y) { + print('setCompassViewMargins not available in web'); + } + + @override + void setLogoViewMargins(int x, int y) { + print('setLogoViewMargins not available in web'); + } + + @override + void setMinMaxZoomPreference(num min, num max) { + // FIX: why is called indefinitely? (map_ui page) + _map.setMinZoom(min); + _map.setMaxZoom(max); + } + + @override + void setMyLocationEnabled(bool myLocationEnabled) { + if (myLocationEnabled) { + _addGeolocateControl(trackUserLocation: false); + } else { + _removeGeolocateControl(); + } + } + + @override + void setMyLocationRenderMode(int myLocationRenderMode) { + print('myLocationRenderMode not available in web'); + } + + @override + void setMyLocationTrackingMode(int myLocationTrackingMode) { + if (myLocationTrackingMode == 0) { + _addGeolocateControl(trackUserLocation: false); + } else { + print('Only one tracking mode available in web'); + _addGeolocateControl(trackUserLocation: true); + } + } + + @override + void setRotateGesturesEnabled(bool rotateGesturesEnabled) { + if (rotateGesturesEnabled) { + _map.dragRotate.enable(); + _map.touchZoomRotate.enableRotation(); + _map.keyboard.enable(); + } else { + _map.dragRotate.disable(); + _map.touchZoomRotate.disableRotation(); + _map.keyboard.disable(); + } + } + + @override + void setScrollGesturesEnabled(bool scrollGesturesEnabled) { + if (scrollGesturesEnabled) { + _map.dragPan.enable(); + _map.keyboard.enable(); + } else { + _map.dragPan.disable(); + _map.keyboard.disable(); + } + } + + @override + void setStyleString(String styleString) { + _map.setStyle(styleString); + } + + @override + void setTiltGesturesEnabled(bool tiltGesturesEnabled) { + if (tiltGesturesEnabled) { + _map.dragRotate.enable(); + _map.keyboard.enable(); + } else { + _map.dragRotate.disable(); + _map.keyboard.disable(); + } + } + + @override + void setTrackCameraPosition(bool trackCameraPosition) { + _trackCameraPosition = trackCameraPosition; + } + + @override + void setZoomGesturesEnabled(bool zoomGesturesEnabled) { + if (zoomGesturesEnabled) { + _map.doubleClickZoom.enable(); + _map.boxZoom.enable(); + _map.scrollZoom.enable(); + _map.touchZoomRotate.enable(); + _map.keyboard.enable(); + } else { + _map.doubleClickZoom.disable(); + _map.boxZoom.disable(); + _map.scrollZoom.disable(); + _map.touchZoomRotate.disable(); + _map.keyboard.disable(); + } + } +} diff --git a/mapbox_gl_web/lib/src/mapbox_map_plugin.dart b/mapbox_gl_web/lib/src/mapbox_map_plugin.dart new file mode 100644 index 000000000..dcc779d70 --- /dev/null +++ b/mapbox_gl_web/lib/src/mapbox_map_plugin.dart @@ -0,0 +1,8 @@ +part of mapbox_gl_web; + +class MapboxMapPlugin { + /// Registers this class as the default instance of [MapboxGlPlatform]. + static void registerWith(Registrar registrar) { + MapboxGlPlatform.createInstance = () => MapboxMapController(); + } +} diff --git a/mapbox_gl_web/lib/src/options_sink.dart b/mapbox_gl_web/lib/src/options_sink.dart new file mode 100644 index 000000000..7698f5b30 --- /dev/null +++ b/mapbox_gl_web/lib/src/options_sink.dart @@ -0,0 +1,37 @@ +part of mapbox_gl_web; + +abstract class MapboxMapOptionsSink { + // TODO: dddd replace with CameraPosition.Builder target + void setCameraTargetBounds(LatLngBounds bounds); + + void setCompassEnabled(bool compassEnabled); + + // TODO: styleString is not actually a part of options. consider moving + void setStyleString(String styleString); + + void setMinMaxZoomPreference(num min, num max); + + void setRotateGesturesEnabled(bool rotateGesturesEnabled); + + void setScrollGesturesEnabled(bool scrollGesturesEnabled); + + void setTiltGesturesEnabled(bool tiltGesturesEnabled); + + void setTrackCameraPosition(bool trackCameraPosition); + + void setZoomGesturesEnabled(bool zoomGesturesEnabled); + + void setMyLocationEnabled(bool myLocationEnabled); + + void setMyLocationTrackingMode(int myLocationTrackingMode); + + void setMyLocationRenderMode(int myLocationRenderMode); + + void setLogoViewMargins(int x, int y); + + void setCompassGravity(int gravity); + + void setCompassViewMargins(int x, int y); + + void setAttributionButtonMargins(int x, int y); +} diff --git a/mapbox_gl_web/pubspec.yaml b/mapbox_gl_web/pubspec.yaml new file mode 100644 index 000000000..d5b3ed38a --- /dev/null +++ b/mapbox_gl_web/pubspec.yaml @@ -0,0 +1,32 @@ +name: mapbox_gl_web +description: Web platform implementation of mapbox_gl +version: 0.7.0 +homepage: https://github.com/tobrun/flutter-mapbox-gl + +flutter: + plugin: + platforms: + web: + pluginClass: MapboxMapPlugin + fileName: mapbox_gl_web.dart + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + meta: ^1.1.7 + mapbox_gl_platform_interface: + git: + url: https://github.com/tobrun/flutter-mapbox-gl.git + path: mapbox_gl_platform_interface + mapbox_gl_dart: ^0.1.5 + image: ^2.1.12 + +dev_dependencies: + flutter_test: + sdk: flutter + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4 <2.0.0" \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 91e001e48..f15493608 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,25 +1,118 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.13" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+1" + mapbox_gl_dart: + dependency: transitive + description: + name: mapbox_gl_dart + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" + mapbox_gl_platform_interface: + dependency: "direct main" + description: + path: mapbox_gl_platform_interface + ref: HEAD + resolved-ref: fa9dac81b6d0f3a5f01688a6695ee938b71874e5 + url: "https://github.com/tobrun/flutter-mapbox-gl.git" + source: git + version: "0.7.0" + mapbox_gl_web: + dependency: "direct main" + description: + path: mapbox_gl_web + ref: HEAD + resolved-ref: fa9dac81b6d0f3a5f01688a6695ee938b71874e5 + url: "https://github.com/tobrun/flutter-mapbox-gl.git" + source: git + version: "0.7.0" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.1.8" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.4" sky_engine: dependency: transitive description: flutter @@ -39,5 +132,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.7.0" sdks: - dart: ">=2.0.0-dev.68.0 <3.0.0" + dart: ">=2.7.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0deb49875..bbbd88de0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,13 +1,20 @@ name: mapbox_gl -description: A Flutter plugin for integrating Mapbox Maps SDK inside a Flutter widget in iOS and Android applications. -version: 0.0.5 -author: Mapbox +description: A Flutter plugin for integrating Mapbox Maps inside a Flutter application on Android, iOS and web platfroms. +version: 0.7.0 homepage: https://github.com/tobrun/flutter-mapbox-gl - dependencies: flutter: sdk: flutter + mapbox_gl_platform_interface: + git: + url: https://github.com/tobrun/flutter-mapbox-gl.git + path: mapbox_gl_platform_interface + mapbox_gl_web: + git: + url: https://github.com/tobrun/flutter-mapbox-gl.git + path: mapbox_gl_web + flutter: plugin: platforms: @@ -16,6 +23,8 @@ flutter: pluginClass: MapboxMapsPlugin ios: pluginClass: MapboxMapsPlugin + web: + default_package: mapbox_gl_web environment: sdk: ">=2.1.0 <3.0.0"