From 95b7d7b75759eef5031bc0e9dad6ef1d3ca0a1bd Mon Sep 17 00:00:00 2001 From: Tobrun Date: Sat, 24 Oct 2020 13:07:31 +0200 Subject: [PATCH] Add Fill API support (#49) * [flutter] [android] - add fill support * Resolved merge conflict. * A first working version for ios (after some extensive rebasing). * Minor cleanup * Minor cleanup. * Fix broken build Android. * A working version for Android. * Minor cleanup. * Added fill pattern example. Works on Android not on iOS. Seems to break consecutive fills though. * For the first queried feature (when filter is set) create a fill. * Fix lint issue (unused method). * Updated code formatting. * Added interior polygon to iOS. * [docs] update readme support table * fixup Co-authored-by: Timothy Sealy --- README.md | 2 +- .../java/com/mapbox/mapboxgl/Convert.java | 58 +++- .../java/com/mapbox/mapboxgl/FillBuilder.java | 58 ++++ .../com/mapbox/mapboxgl/FillController.java | 74 +++++ .../com/mapbox/mapboxgl/FillOptionsSink.java | 27 ++ .../mapbox/mapboxgl/MapboxMapController.java | 87 +++++- .../mapbox/mapboxgl/OnFillTappedListener.java | 7 + .../assets/fill/cat_silhouette_pattern.png | Bin 0 -> 972 bytes example/ios/Podfile | 83 +++--- example/lib/main.dart | 2 + example/lib/map_ui.dart | 46 +++- example/lib/place_fill.dart | 252 ++++++++++++++++++ example/pubspec.yaml | 2 + ios/Classes/Convert.swift | 32 +++ ios/Classes/Extensions.swift | 7 + ios/Classes/MapboxMapController.swift | 52 ++++ lib/mapbox_gl.dart | 4 +- lib/src/controller.dart | 75 +++++- .../lib/mapbox_gl_platform_interface.dart | 2 + .../lib/src/fill.dart | 89 +++++++ .../lib/src/mapbox_gl_platform_interface.dart | 15 ++ .../lib/src/method_channel_mapbox_gl.dart | 32 +++ mapbox_gl_platform_interface/pubspec.yaml | 2 +- 23 files changed, 956 insertions(+), 52 deletions(-) create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillController.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java create mode 100644 example/assets/fill/cat_silhouette_pattern.png create mode 100644 example/lib/place_fill.dart create mode 100644 mapbox_gl_platform_interface/lib/src/fill.dart diff --git a/README.md b/README.md index fcf1f7e8c..5bf1fa5c7 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ This project is available on [pub.dev](https://pub.dev/packages/mapbox_gl), foll | 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 | | | | +| Fill | :white_check_mark: | :white_check_mark: | | ## Map Styles diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index dd8225dda..ede8283ee 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -5,7 +5,7 @@ package com.mapbox.mapboxgl; import android.graphics.Point; - +import com.mapbox.geojson.Polygon; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; @@ -100,7 +100,7 @@ static CameraUpdate toCameraUpdate(Object o, MapboxMap mapboxMap, float density) case "bearingTo": return CameraUpdateFactory.bearingTo(toFloat(data.get(1))); case "tiltTo": - return CameraUpdateFactory.tiltTo(toFloat(data.get(1))); + return CameraUpdateFactory.tiltTo(toFloat(data.get(1))); default: throw new IllegalArgumentException("Cannot interpret " + o + " as CameraUpdate"); } @@ -168,6 +168,31 @@ private static List toLatLngList(Object o) { return latLngList; } + private static List> toLatLngListList(Object o) { + if (o == null) { + return null; + } + final List data = toList(o); + List> latLngListList = new ArrayList<>(); + for (int i = 0; i < data.size(); i++) { + List latLngList = toLatLngList(data.get(i)); + latLngListList.add(latLngList); + } + return latLngListList; + } + + static Polygon interpretListLatLng(List> geometry) { + List> points = new ArrayList<>(geometry.size()); + for (List innerGeometry : geometry) { + List innerPoints = new ArrayList<>(innerGeometry.size()); + for (LatLng latLng : innerGeometry) { + innerPoints.add(com.mapbox.geojson.Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())); + } + points.add(innerPoints); + } + return Polygon.fromLngLats(points); + } + private static List toList(Object o) { return (List) o; } @@ -423,7 +448,6 @@ static void interpretCircleOptions(Object o, CircleOptionsSink sink) { sink.setDraggable(toBoolean(draggable)); } } - static void interpretLineOptions(Object o, LineOptionsSink sink) { final Map data = toMap(o); final Object lineJoin = data.get("lineJoin"); @@ -477,4 +501,32 @@ static void interpretLineOptions(Object o, LineOptionsSink sink) { sink.setDraggable(toBoolean(draggable)); } } + + static void interpretFillOptions(Object o, FillOptionsSink sink) { + final Map data = toMap(o); + final Object fillOpacity = data.get("fillOpacity"); + if (fillOpacity != null) { + sink.setFillOpacity(toFloat(fillOpacity)); + } + final Object fillColor = data.get("fillColor"); + if (fillColor != null) { + sink.setFillColor(toString(fillColor)); + } + final Object fillOutlineColor = data.get("fillOutlineColor"); + if (fillOutlineColor != null) { + sink.setFillOutlineColor(toString(fillOutlineColor)); + } + final Object fillPattern = data.get("fillPattern"); + if (fillPattern != null) { + sink.setFillPattern(toString(fillPattern)); + } + final Object geometry = data.get("geometry"); + if (geometry != null) { + sink.setGeometry(toLatLngListList(geometry)); + } + final Object draggable = data.get("draggable"); + if (draggable != null) { + sink.setDraggable(toBoolean(draggable)); + } + } } \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java new file mode 100644 index 000000000..09adcd90a --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java @@ -0,0 +1,58 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; +import com.mapbox.mapboxsdk.plugins.annotation.FillOptions; + +import java.util.List; + +class FillBuilder implements FillOptionsSink { + private final FillManager fillManager; + private final FillOptions fillOptions; + + FillBuilder(FillManager fillManager) { + this.fillManager = fillManager; + this.fillOptions = new FillOptions(); + } + + Fill build() { + return fillManager.create(fillOptions); + } + + @Override + public void setFillOpacity(float fillOpacity) { + fillOptions.withFillOpacity(fillOpacity); + } + + @Override + public void setFillColor(String fillColor) { + fillOptions.withFillColor(fillColor); + } + + @Override + public void setFillOutlineColor(String fillOutlineColor) { + fillOptions.withFillOutlineColor(fillOutlineColor); + } + + @Override + public void setFillPattern(String fillPattern) { + fillOptions.withFillPattern(fillPattern); + } + + @Override + public void setGeometry(List> geometry) { + fillOptions.withGeometry(Convert.interpretListLatLng(geometry)); + } + + @Override + public void setDraggable(boolean draggable) { + fillOptions.withDraggable(draggable); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillController.java b/android/src/main/java/com/mapbox/mapboxgl/FillController.java new file mode 100644 index 000000000..200b13f76 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillController.java @@ -0,0 +1,74 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import android.graphics.Color; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; + +import java.util.List; + +/** + * Controller of a single Fill on the map. + */ +class FillController implements FillOptionsSink { + private final Fill fill; + private final OnFillTappedListener onTappedListener; + private boolean consumeTapEvents; + + FillController(Fill fill, boolean consumeTapEvents, OnFillTappedListener onTappedListener) { + this.fill = fill; + this.consumeTapEvents = consumeTapEvents; + this.onTappedListener = onTappedListener; + } + + boolean onTap() { + if (onTappedListener != null) { + onTappedListener.onFillTapped(fill); + } + return consumeTapEvents; + } + + void remove(FillManager fillManager) { + fillManager.delete(fill); + } + + @Override + public void setFillOpacity(float fillOpacity) { + fill.setFillOpacity(fillOpacity); + } + + @Override + public void setFillColor(String fillColor) { + fill.setFillColor(Color.parseColor(fillColor)); + } + + @Override + public void setFillOutlineColor(String fillOutlineColor) { + fill.setFillOutlineColor(Color.parseColor(fillOutlineColor)); + } + + @Override + public void setFillPattern(String fillPattern) { + fill.setFillPattern(fillPattern); + } + + @Override + public void setGeometry(List> geometry) { + fill.setGeometry(Convert.interpretListLatLng(geometry)); + } + + @Override + public void setDraggable(boolean draggable) { + fill.setDraggable(draggable); + } + + public void update(FillManager fillManager) { + fillManager.update(fill); + } +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java new file mode 100644 index 000000000..849788103 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java @@ -0,0 +1,27 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +import java.util.List; + +/** Receiver of Fill configuration options. */ +interface FillOptionsSink { + + void setFillOpacity(float fillOpacity); + + void setFillColor(String fillColor); + + void setFillOutlineColor(String fillOutlineColor); + + void setFillPattern(String fillPattern); + + void setGeometry(List> geometry); + + void setDraggable(boolean draggable); +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index ffcc319be..ef6e801f4 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -53,10 +53,13 @@ import com.mapbox.mapboxsdk.maps.MapboxMapOptions; import com.mapbox.mapboxsdk.maps.Projection; import com.mapbox.mapboxsdk.offline.OfflineManager; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; import com.mapbox.mapboxsdk.plugins.annotation.Annotation; import com.mapbox.mapboxsdk.plugins.annotation.Circle; import com.mapbox.mapboxsdk.plugins.annotation.CircleManager; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; import com.mapbox.mapboxsdk.plugins.annotation.OnAnnotationClickListener; import com.mapbox.mapboxsdk.plugins.annotation.Symbol; import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager; @@ -97,11 +100,12 @@ final class MapboxMapController MapboxMap.OnMapLongClickListener, MapboxMapOptionsSink, MethodChannel.MethodCallHandler, - com.mapbox.mapboxsdk.maps.OnMapReadyCallback, + OnMapReadyCallback, OnCameraTrackingChangedListener, OnSymbolTappedListener, OnLineTappedListener, OnCircleTappedListener, + OnFillTappedListener, PlatformView { private static final String TAG = "MapboxMapController"; private final int id; @@ -113,9 +117,11 @@ final class MapboxMapController private final Map symbols; private final Map lines; private final Map circles; + private final Map fills; private SymbolManager symbolManager; private LineManager lineManager; private CircleManager circleManager; + private FillManager fillManager; private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private int myLocationTrackingMode = 0; @@ -149,6 +155,7 @@ final class MapboxMapController this.symbols = new HashMap<>(); this.lines = new HashMap<>(); this.circles = new HashMap<>(); + this.fills = new HashMap<>(); this.density = context.getResources().getDisplayMetrics().density; methodChannel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/mapbox_maps_" + id); @@ -272,11 +279,30 @@ private void removeCircle(String circleId) { private CircleController circle(String circleId) { final CircleController circle = circles.get(circleId); if (circle == null) { - throw new IllegalArgumentException("Unknown symbol: " + circleId); + throw new IllegalArgumentException("Unknown circle: " + circleId); } return circle; } + private FillBuilder newFillBuilder() { + return new FillBuilder(fillManager); + } + + private void removeFill(String fillId) { + final FillController fillController = fills.remove(fillId); + if (fillController != null) { + fillController.remove(fillManager); + } + } + + private FillController fill(String fillId) { + final FillController fill = fills.get(fillId); + if (fill == null) { + throw new IllegalArgumentException("Unknown fill: " + fillId); + } + return fill; + } + @Override public void onMapReady(MapboxMap mapboxMap) { this.mapboxMap = mapboxMap; @@ -327,6 +353,7 @@ public void onStyleLoaded(@NonNull Style style) { enableLineManager(style); enableSymbolManager(style); enableCircleManager(style); + enableFillManager(style); if (myLocationEnabled) { enableLocationComponent(style); } @@ -391,6 +418,13 @@ private void enableCircleManager(@NonNull Style style) { } } + private void enableFillManager(@NonNull Style style) { + if (fillManager == null) { + fillManager = new FillManager(mapView, mapboxMap, style); + fillManager.addClickListener(MapboxMapController.this::onAnnotationClick); + } + } + @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { @@ -543,12 +577,12 @@ public void onCancel() { result.success(reply); break; } - case "map#setTelemetryEnabled": { + case "map#setTelemetryEnabled": { final boolean enabled = call.argument("enabled"); Mapbox.getTelemetry().setUserTelemetryRequestState(enabled); result.success(null); break; - } + } case "map#getTelemetryEnabled": { final TelemetryEnabler.State telemetryState = TelemetryEnabler.retrieveTelemetryStateFromPreferences(); result.success(telemetryState == TelemetryEnabler.State.ENABLED); @@ -723,6 +757,30 @@ public void onError(@NonNull String message) { result.success(hashMapLatLng); break; } + case "fill#add": { + final FillBuilder fillBuilder = newFillBuilder(); + Convert.interpretFillOptions(call.argument("options"), fillBuilder); + final Fill fill = fillBuilder.build(); + final String fillId = String.valueOf(fill.getId()); + fills.put(fillId, new FillController(fill, true, this)); + result.success(fillId); + break; + } + case "fill#remove": { + final String fillId = call.argument("fill"); + removeFill(fillId); + result.success(null); + break; + } + case "fill#update": { + Log.e(TAG, "update fill"); + final String fillId = call.argument("fill"); + final FillController fill = fill(fillId); + Convert.interpretFillOptions(call.argument("options"), fill); + fill.update(fillManager); + result.success(null); + break; + } case "locationComponent#getLastLocation": { Log.e(TAG, "location component: getLastLocation"); if (this.myLocationEnabled && locationComponent != null && locationEngine != null) { @@ -749,8 +807,8 @@ public void onFailure(@NonNull Exception exception) { } break; } - case "style#addImage":{ - if(style==null){ + 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")); @@ -823,6 +881,13 @@ public boolean onAnnotationClick(Annotation annotation) { return true; } } + if (annotation instanceof Fill) { + final FillController fillController = fills.get(String.valueOf(annotation.getId())); + if (fillController != null) { + fillController.onTap(); + return true; + } + } return false; } @@ -847,6 +912,13 @@ public void onCircleTapped(Circle circle) { methodChannel.invokeMethod("circle#onTap", arguments); } + @Override + public void onFillTapped(Fill fill) { + final Map arguments = new HashMap<>(2); + arguments.put("fill", String.valueOf(fill.getId())); + methodChannel.invokeMethod("fill#onTap", arguments); + } + @Override public boolean onMapClick(@NonNull LatLng point) { PointF pointf = mapboxMap.getProjection().toScreenLocation(point); @@ -889,6 +961,9 @@ public void dispose() { if (circleManager != null) { circleManager.onDestroy(); } + if (fillManager != null) { + fillManager.onDestroy(); + } mapView.onDestroy(); registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); diff --git a/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java b/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java new file mode 100644 index 000000000..27eb86425 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java @@ -0,0 +1,7 @@ +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.plugins.annotation.Fill; + +public interface OnFillTappedListener { + void onFillTapped(Fill fill); +} diff --git a/example/assets/fill/cat_silhouette_pattern.png b/example/assets/fill/cat_silhouette_pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..6f5ece826b8e11c01e88c837810e83fe0099efd0 GIT binary patch literal 972 zcmeAS@N?(olHy`uVBq!ia0vp^4nSNn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W> zdx@v7EBiwZ4iRC7c_+N51Ld!Hx;TbJ9DX~)H$&J_qHX@a8Bxmr61mwNXKS!|2d)xN zb`4qOy=#X9;_qS($pF4MM{yoVb*Vi5i-E!2&UHeYL?GEF%^Smn< z?rt$S`^dWALBZk$TVohYlVa3IrUy^k`1CW1n-6d@`z@ZnyhpP1VC?I~OZx;Q4F6TG z^zI6cTz#}p;Kg}?=tsvyL_S~ra%pn+`Jd(j%e9@iT&rXfI4AIVw;`uoosS`7;k8|p zzsvbJF1Wk)NVS&kpYA8E@oyv=zsx?TyC_dEV*a(?mNy@|sOcn&w%?d(7WPo4ab^q0 z#-^-4k|A!ob6>f~oXV5?Xzcv%cl5m#?Doet=)Q32TyZra`^9-io4{wQo9&%c(kl)s zg?_lMa)8w^R{3eJ`qBriwOJ3OJH3T&EM>Zvez9Wbl;0vN6)xy<{+}eN^`PA25WhkA z*+2UyHT_@a)sr_>rhfCPtN#PI>H@AV3s;=_o2C7#YP{r+id9?_LS1-+w%C7g$%s?|E$oR^x})X z!Hc)=SWEC2X=M^yi`LpHDA)!55|SyXD>f3Gy|~ z7X!Clnt0{Yws-Rt6zV@%xL+wdx883j_r)(ataonRFz31D+^TsOj>^9BwcR3d@%m1# zPYQF7EGgUXDK_iZ&a5B)xA&*q_Um`8$ZolLIOx@jjjji4%ma=@?p<&E@<34Pr?hpO z&xE{R++5xBdaKa3hbOIC(rdh|oaNH_swL0b{aUnWvio literal 0 HcmV?d00001 diff --git a/example/ios/Podfile b/example/ios/Podfile index a563f857a..17ffc678f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -15,58 +15,71 @@ def parse_KV_file(file, separator='=') if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} 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 + File.foreach(file_abs_path) do |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) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do + # Flutter Pod use_frameworks! - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) end - } + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' - config.build_settings['SWIFT_VERSION'] = '4.2' end end end diff --git a/example/lib/main.dart b/example/lib/main.dart index 081c97712..db213a8d3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -15,6 +15,7 @@ import 'move_camera.dart'; import 'page.dart'; import 'place_circle.dart'; import 'place_symbol.dart'; +import 'place_fill.dart'; import 'scrolling_map.dart'; final List _allPages = [ @@ -25,6 +26,7 @@ final List _allPages = [ PlaceSymbolPage(), LinePage(), PlaceCirclePage(), + PlaceFillPage(), ScrollingMapPage(), ]; diff --git a/example/lib/map_ui.dart b/example/lib/map_ui.dart index 44378f62d..25f98f1d4 100644 --- a/example/lib/map_ui.dart +++ b/example/lib/map_ui.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 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -56,8 +57,9 @@ class MapUiBodyState extends State { bool _zoomGesturesEnabled = true; bool _myLocationEnabled = true; bool _telemetryEnabled = true; - MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.Tracking; + MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.None; List _featureQueryFilter; + Fill _selectedFill; @override void initState() { @@ -240,6 +242,38 @@ class MapUiBodyState extends State { ); } + _clearFill() { + if (_selectedFill != null) { + mapController.removeFill(_selectedFill); + setState(() { + _selectedFill = null; + }); + } + } + + _drawFill(features) async { + Map feature = jsonDecode(features[0]); + if (feature['geometry']['type'] == 'Polygon') { + var coordinates = feature['geometry']['coordinates']; + List> geometry = coordinates.map( + (ll) => ll.map( + (l) => LatLng(l[1],l[0]) + ).toList().cast() + ).toList().cast>(); + Fill fill = await mapController.addFill( + FillOptions( + geometry: geometry, + fillColor: "#FF0000", + fillOutlineColor: "#FF0000", + fillOpacity: 0.6, + ) + ); + setState(() { + _selectedFill = fill; + }); + } + } + @override Widget build(BuildContext context) { final MapboxMap mapboxMap = MapboxMap( @@ -262,9 +296,13 @@ class MapUiBodyState extends State { 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]); - } + print('# features: ${features.length}'); + _clearFill(); + if (features.length == 0 && _featureQueryFilter != null) { + Scaffold.of(context).showSnackBar(SnackBar(content: Text('QueryRenderedFeatures: No features found!'))); + } else { + _drawFill(features); + } }, onMapLongClick: (point, latLng) async { print("Map long press: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart new file mode 100644 index 000000000..480c7dde2 --- /dev/null +++ b/example/lib/place_fill.dart @@ -0,0 +1,252 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:mapbox_gl/mapbox_gl.dart'; +import 'package:mapbox_gl_example/main.dart'; + +import 'page.dart'; + +class PlaceFillPage extends ExamplePage { + PlaceFillPage() : super(const Icon(Icons.check_circle), 'Place fill'); + + @override + Widget build(BuildContext context) { + return const PlaceFillBody(); + } +} + +class PlaceFillBody extends StatefulWidget { + const PlaceFillBody(); + + @override + State createState() => PlaceFillBodyState(); +} + +class PlaceFillBodyState extends State { + PlaceFillBodyState(); + + static final LatLng center = const LatLng(-33.86711, 151.1947171); + final String _fillPatternImage = "assets/fill/cat_silhouette_pattern.png"; + + MapboxMapController controller; + int _fillCount = 0; + Fill _selectedFill; + + void _onMapCreated(MapboxMapController controller) { + this.controller = controller; + controller.onFillTapped.add(_onFillTapped); + } + + void _onStyleLoaded() { + addImageFromAsset("assetImage", _fillPatternImage); + } + + /// 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); + } + + @override + void dispose() { + controller?.onFillTapped?.remove(_onFillTapped); + super.dispose(); + } + + void _onFillTapped(Fill fill) { + setState(() { + _selectedFill = fill; + }); + } + + void _updateSelectedFill(FillOptions changes) { + controller.updateFill(_selectedFill, changes); + } + + void _add() { + controller.addFill( + FillOptions(geometry: [ + [ + LatLng(-32.81711, 151.1447171), + LatLng(-32.81711, 152.2447171), + LatLng(-33.91711, 152.2447171), + LatLng(-33.91711, 151.1447171), + ], + [ + LatLng(-32.86711, 152.1947171), + LatLng(-33.86711, 151.1947171), + LatLng(-32.86711, 151.1947171), + LatLng(-33.86711, 152.1947171), + ] + ], + fillColor: "#FF0000", + fillOutlineColor: "#FF0000"), + ); + setState(() { + _fillCount += 1; + }); + } + + void _remove() { + controller.removeFill(_selectedFill); + setState(() { + _selectedFill = null; + _fillCount -= 1; + }); + } + + void _changePosition() { + //TODO: Implement change position. + } + + void _changeDraggable() { + bool draggable = _selectedFill.options.draggable; + if (draggable == null) { + // default value + draggable = false; + } + _updateSelectedFill( + FillOptions(draggable: !draggable), + ); + } + + Future _changeFillOpacity() async { + double current = _selectedFill.options.fillOpacity; + if (current == null) { + // default value + current = 1.0; + } + + _updateSelectedFill( + FillOptions(fillOpacity: current < 0.1 ? 1.0 : current * 0.75), + ); + } + + Future _changeFillColor() async { + String current = _selectedFill.options.fillColor; + if (current == null) { + // default value + current = "#FF0000"; + } + + _updateSelectedFill( + FillOptions(fillColor: "#FFFF00"), + ); + } + + Future _changeFillOutlineColor() async { + String current = _selectedFill.options.fillOutlineColor; + if (current == null) { + // default value + current = "#FF0000"; + } + + _updateSelectedFill( + FillOptions(fillOutlineColor: "#FFFF00"), + ); + } + + Future _changeFillPattern() async { + String current = _selectedFill.options.fillPattern == null ? "assetImage" : null; + _updateSelectedFill( + FillOptions(fillPattern: current), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: SizedBox( + 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: 7.0, + ), + ), + ), + ), + Expanded( + child: SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + children: [ + Column( + children: [ + FlatButton( + child: const Text('add'), + onPressed: (_fillCount == 12) ? null : _add, + ), + FlatButton( + child: const Text('remove'), + onPressed: (_selectedFill == null) ? null : _remove, + ), + ], + ), + Column( + children: [ + FlatButton( + child: const Text('change fill-opacity'), + onPressed: (_selectedFill == null) + ? null + : _changeFillOpacity, + ), + FlatButton( + child: const Text('change fill-color'), + onPressed: (_selectedFill == null) + ? null + : _changeFillColor, + ), + FlatButton( + child: const Text('change fill-outline-color'), + onPressed: (_selectedFill == null) + ? null + : _changeFillOutlineColor, + ), + FlatButton( + child: const Text('change fill-pattern'), + onPressed: (_selectedFill == null) + ? null + : _changeFillPattern, + ), + FlatButton( + child: const Text('change position'), + onPressed: (_selectedFill == null) + ? null + : _changePosition, + ), + FlatButton( + child: const Text('toggle draggable'), + onPressed: (_selectedFill == null) + ? null + : _changeDraggable, + ), + ], + ), + ], + ) + ], + ), + ), + ), + ], + ); + } +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e79256f80..ad13abbb2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,7 @@ name: mapbox_gl_example description: Demonstrates how to use the mapbox_gl plugin. publish_to: 'none' +version: 1.0.0+1 environment: sdk: ">=2.0.0-dev.68.0 <3.0.0" @@ -48,6 +49,7 @@ flutter: # https://flutter.io/assets-and-images/#resolution-aware. assets: + - assets/fill/cat_silhouette_pattern.png - assets/symbols/custom-icon.png - assets/symbols/2.0x/custom-icon.png - assets/symbols/3.0x/custom-icon.png diff --git a/ios/Classes/Convert.swift b/ios/Classes/Convert.swift index 122fdb2ac..570f43dcf 100644 --- a/ios/Classes/Convert.swift +++ b/ios/Classes/Convert.swift @@ -320,4 +320,36 @@ class Convert { delegate.isDraggable = draggable } } + + class func interpretFillOptions(options: Any?, delegate: MGLPolygonStyleAnnotation) { + guard let options = options as? [String: Any] else { return } + if let fillOpacity = options["fillOpacity"] as? CGFloat { + delegate.fillOpacity = fillOpacity + } + if let fillColor = options["fillColor"] as? String { + delegate.fillColor = UIColor(hexString: fillColor) ?? UIColor.black + } + if let fillOutlineColor = options["fillOutlineColor"] as? String { + delegate.fillOutlineColor = UIColor(hexString: fillOutlineColor) ?? UIColor.black + } + if let fillPattern = options["fillPattern"] as? String { + delegate.fillPattern = fillPattern + } + if let draggable = options["draggable"] as? Bool { + delegate.isDraggable = draggable + } + } + + class func toPolygons(geometry: [[[Double]]]) -> [MGLPolygonFeature] { + var polygons:[MGLPolygonFeature] = [] + for lineString in geometry { + var linearRing: [CLLocationCoordinate2D] = [] + for coordinate in lineString { + linearRing.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) + } + let polygon = MGLPolygonFeature(coordinates: linearRing, count: UInt(linearRing.count)) + polygons.append(polygon) + } + return polygons + } } diff --git a/ios/Classes/Extensions.swift b/ios/Classes/Extensions.swift index 1b5039d99..800e32dcb 100644 --- a/ios/Classes/Extensions.swift +++ b/ios/Classes/Extensions.swift @@ -89,3 +89,10 @@ extension UIColor { return nil } } + + +extension Array { + var tail: Array { + return Array(self.dropFirst()) + } +} diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index 5a66efd53..170921e7b 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -20,6 +20,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma private var symbolAnnotationController: MGLSymbolAnnotationController? private var circleAnnotationController: MGLCircleAnnotationController? private var lineAnnotationController: MGLLineAnnotationController? + private var fillAnnotationController: MGLPolygonAnnotationController? func view() -> UIView { return mapView @@ -392,6 +393,51 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } result(reply) + case "fill#add": + guard let fillAnnotationController = fillAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + // Parse geometry + var identifier: String? = nil + if let options = arguments["options"] as? [String: Any], + let geometry = options["geometry"] as? [[[Double]]] { + guard geometry.count > 0 else { break } + // Convert geometry to coordinate and interior polygonc. + var fillCoordinates: [CLLocationCoordinate2D] = [] + for coordinate in geometry[0] { + fillCoordinates.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) + } + let polygons = Convert.toPolygons(geometry: geometry.tail) + let fill = MGLPolygonStyleAnnotation(coordinates: fillCoordinates, count: UInt(fillCoordinates.count), interiorPolygons: polygons) + Convert.interpretFillOptions(options: arguments["options"], delegate: fill) + fillAnnotationController.addStyleAnnotation(fill) + identifier = fill.identifier + } + result(identifier) + case "fill#update": + guard let fillAnnotationController = fillAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let fillId = arguments["fill"] as? String else { return } + + for fill in fillAnnotationController.styleAnnotations() { + if fill.identifier == fillId { + Convert.interpretFillOptions(options: arguments["options"], delegate: fill as! MGLPolygonStyleAnnotation) + fillAnnotationController.updateStyleAnnotation(fill) + break; + } + } + result(nil) + case "fill#remove": + guard let fillAnnotationController = fillAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let fillId = arguments["fill"] as? String else { return } + + for fill in fillAnnotationController.styleAnnotations() { + if fill.identifier == fillId { + fillAnnotationController.removeStyleAnnotation(fill) + break; + } + } + result(nil) case "style#addImage": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let name = arguments["name"] as? String else { return } @@ -505,6 +551,8 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma channel.invokeMethod("circle#onTap", arguments: ["circle" : "\(circle.identifier)"]) } else if let line = styleAnnotation as? MGLLineStyleAnnotation { channel.invokeMethod("line#onTap", arguments: ["line" : "\(line.identifier)"]) + } else if let fill = styleAnnotation as? MGLPolygonStyleAnnotation { + channel.invokeMethod("fill#onTap", arguments: ["fill" : "\(fill.identifier)"]) } } @@ -541,6 +589,10 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma circleAnnotationController!.annotationsInteractionEnabled = true circleAnnotationController?.delegate = self + fillAnnotationController = MGLPolygonAnnotationController(mapView: self.mapView) + fillAnnotationController!.annotationsInteractionEnabled = true + fillAnnotationController?.delegate = self + mapReadyResult?(nil) if let channel = channel { channel.invokeMethod("map#onStyleLoaded", arguments: nil) diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index 9113dd6b2..763b53a1b 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -32,7 +32,9 @@ export 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart' Circle, CircleOptions, Line, - LineOptions; + LineOptions, + Fill, + FillOptions; part 'src/controller.dart'; diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 13e226e50..d5d34db94 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -73,6 +73,13 @@ class MapboxMapController extends ChangeNotifier { } }); + MapboxGlPlatform.getInstance(_id).onFillTappedPlatform.add((fillId) { + final Fill fill = _fills[fillId]; + if (fill != null) { + onFillTapped(fill); + } + }); + MapboxGlPlatform.getInstance(_id).onCameraMoveStartedPlatform.add((_) { _isCameraMoving = true; notifyListeners(); @@ -173,6 +180,9 @@ class MapboxMapController extends ChangeNotifier { /// Callbacks to receive tap events for symbols placed on this map. final ArgumentCallbacks onCircleTapped = ArgumentCallbacks(); + /// Callbacks to receive tap events for fills placed on this map. + final ArgumentCallbacks onFillTapped = ArgumentCallbacks(); + /// Callbacks to receive tap events for info windows on symbols final ArgumentCallbacks onInfoWindowTapped = ArgumentCallbacks(); @@ -194,10 +204,16 @@ class MapboxMapController extends ChangeNotifier { /// The current set of circles on this map. /// - /// The returned set will be a detached snapshot of the symbols collection. + /// The returned set will be a detached snapshot of the circles collection. Set get circles => Set.from(_circles.values); final Map _circles = {}; + /// The current set of fills on this map. + /// + /// The returned set will be a detached snapshot of the fills collection. + Set get fills => Set.from(_fills.values); + final Map _fills = {}; + /// True if the map camera is currently moving. bool get isCameraMoving => _isCameraMoving; bool _isCameraMoving = false; @@ -582,6 +598,63 @@ class MapboxMapController extends ChangeNotifier { _circles.remove(id); } + /// Adds a fill to the map, configured using the specified custom [options]. + /// + /// Change listeners are notified once the fill has been added on the + /// platform side. + /// + /// The returned [Future] completes with the added fill once listeners have + /// been notified. + Future addFill(FillOptions options, [Map data]) async { + final FillOptions effectiveOptions = + FillOptions.defaultOptions.copyWith(options); + final fill = await MapboxGlPlatform.getInstance(_id).addFill(effectiveOptions); + _fills[fill.id] = fill; + notifyListeners(); + return fill; + } + + /// Updates the specified [fill] with the given [changes]. The fill must + /// be a current member of the [fills] set. + /// + /// Change listeners are notified once the fill has been updated on the + /// platform side. + /// + /// The returned [Future] completes once listeners have been notified. + Future updateFill(Fill fill, FillOptions changes) async { + assert(fill != null); + assert(_fills[fill.id] == fill); + assert(changes != null); + await MapboxGlPlatform.getInstance(_id).updateFill(fill, changes); + fill.options = fill.options.copyWith(changes); + notifyListeners(); + } + + /// Removes the specified [fill] from the map. The fill must be a current + /// member of the [fills] set. + /// + /// Change listeners are notified once the fill has been removed on the + /// platform side. + /// + /// The returned [Future] completes once listeners have been notified. + Future removeFill(Fill fill) async { + assert(fill != null); + assert(_fills[fill.id] == fill); + await _removeFill(fill.id); + notifyListeners(); + } + + /// Helper method to remove a single fill from the map. Consumed by + /// [removeFill] and [clearFills]. + /// + /// The returned [Future] completes once the fill has been removed from + /// [_fills]. + Future _removeFill(String id) async { + await MapboxGlPlatform.getInstance(_id).removeFill(id); + + _fills.remove(id); + } + Future queryRenderedFeatures( Point point, List layerIds, List filter) async { return MapboxGlPlatform.getInstance(_id) diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart index 0abe3163a..f5c1ed938 100644 --- a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -15,5 +15,7 @@ part 'src/line.dart'; part 'src/location.dart'; part 'src/method_channel_mapbox_gl.dart'; part 'src/symbol.dart'; +part 'src/fill.dart'; part 'src/ui.dart'; part 'src/mapbox_gl_platform_interface.dart'; + diff --git a/mapbox_gl_platform_interface/lib/src/fill.dart b/mapbox_gl_platform_interface/lib/src/fill.dart new file mode 100644 index 000000000..01cc709ce --- /dev/null +++ b/mapbox_gl_platform_interface/lib/src/fill.dart @@ -0,0 +1,89 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of mapbox_gl_platform_interface; + +class Fill { + @visibleForTesting + Fill(this._id, this.options, [this._data]); + + /// A unique identifier for this fill. + /// + /// The identifier is an arbitrary unique string. + final String _id; + String get id => _id; + + final Map _data; + Map get data => _data; + + /// The fill configuration options most recently applied programmatically + /// via the map controller. + /// + /// The returned value does not reflect any changes made to the fill through + /// touch events. Add listeners to the owning map controller to track those. + FillOptions options; +} + +/// Configuration options for [Fill] instances. +/// +/// When used to change configuration, null values will be interpreted as +/// "do not change this configuration option". +class FillOptions { + /// Creates a set of fill configuration options. + /// + /// By default, every non-specified field is null, meaning no desire to change + /// fill defaults or current configuration. + const FillOptions({ + this.fillOpacity, + this.fillColor, + this.fillOutlineColor, + this.fillPattern, + this.geometry, + this.draggable + }); + + final double fillOpacity; + final String fillColor; + final String fillOutlineColor; + final String fillPattern; + final List> geometry; + final bool draggable; + + static const FillOptions defaultOptions = FillOptions(); + + FillOptions copyWith(FillOptions changes) { + if (changes == null) { + return this; + } + return FillOptions( + fillOpacity: changes.fillOpacity ?? fillOpacity, + fillColor: changes.fillColor ?? fillColor, + fillOutlineColor: changes.fillOutlineColor ?? fillOutlineColor, + fillPattern: changes.fillPattern ?? fillPattern, + geometry: changes.geometry ?? geometry, + draggable: changes.draggable ?? draggable, + ); + } + + dynamic toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('fillOpacity', fillOpacity); + addIfPresent('fillColor', fillColor); + addIfPresent('fillOutlineColor', fillOutlineColor); + addIfPresent('fillPattern', fillPattern); + addIfPresent('geometry', + geometry?.map((List latLngList) => latLngList.map((LatLng latLng) => latLng.toJson())?.toList())?.toList()); + addIfPresent('draggable', draggable); + return json; + } +} \ No newline at end of file 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 index b53873293..16bd88da9 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -34,6 +34,9 @@ abstract class MapboxGlPlatform { final ArgumentCallbacks onCircleTappedPlatform = ArgumentCallbacks(); + final ArgumentCallbacks onFillTappedPlatform = + ArgumentCallbacks(); + final ArgumentCallbacks onCameraMoveStartedPlatform = ArgumentCallbacks(); @@ -160,6 +163,18 @@ abstract class MapboxGlPlatform { throw UnimplementedError('removeCircle() has not been implemented.'); } + Future addFill(FillOptions options, [Map data]) async { + throw UnimplementedError('addFill() has not been implemented.'); + } + + FutureupdateFill(Fill fill, FillOptions changes) async { + throw UnimplementedError('updateFill() has not been implemented.'); + } + + Future removeFill(String fillId) async { + throw UnimplementedError('removeFill() has not been implemented.'); + } + Future queryRenderedFeatures( Point point, List layerIds, List filter) async { throw UnimplementedError( 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 index 1f512e27c..e6c0032cc 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -29,6 +29,12 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { onCircleTappedPlatform(circleId); } break; + case 'fill#onTap': + final String fillId = call.arguments['fill']; + if (fillId != null) { + onFillTappedPlatform(fillId); + } + break; case 'camera#onMoveStarted': onCameraMoveStartedPlatform(null); break; @@ -302,6 +308,32 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }); } + @override + Future addFill(FillOptions options, [Map data]) async { + final String fillId = await _channel.invokeMethod( + 'fill#add', + { + 'options': options.toJson(), + }, + ); + return Fill(fillId, options, data); + } + + @override + Future updateFill(Fill fill, FillOptions changes) async { + await _channel.invokeMethod('fill#update', { + 'fill': fill.id, + 'options': changes.toJson(), + }); + } + + @override + Future removeFill(String fillId) async { + await _channel.invokeMethod('fill#remove', { + 'fill': fillId, + }); + } + @override Future queryRenderedFeatures( Point point, List layerIds, List filter) async { diff --git a/mapbox_gl_platform_interface/pubspec.yaml b/mapbox_gl_platform_interface/pubspec.yaml index 67a25be83..b67a5a19a 100644 --- a/mapbox_gl_platform_interface/pubspec.yaml +++ b/mapbox_gl_platform_interface/pubspec.yaml @@ -10,4 +10,4 @@ dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.9.1+hotfix.4 <2.0.0" + flutter: ">=1.10.0 <2.0.0"