diff --git a/CHANGELOG.md b/CHANGELOG.md index 01740cee..2d049671 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## unreleased + +- add `duration` parameter to `flyTo()` +- fix `jumpTo()` never returns + ## 0.0.1+1 - fix urls to website diff --git a/README.md b/README.md index effcb29e..3fda5e75 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ welcome. flutter pub global activate pigeon # only once dart run pigeon --input pigeons/pigeon.dart cp ios/Classes/Pigeon.g.swift macos/Classes/Pigeon.g.swift +dart format . ``` #### Test with WebAssembly diff --git a/android/build.gradle b/android/build.gradle index c269ff3c..77acfbdd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -55,7 +55,7 @@ android { // implementation 'org.maplibre.gl:android-plugin-offline-v9:3.0.0' // implementation 'com.squareup.okhttp3:okhttp:4.12.0' testImplementation("org.jetbrains.kotlin:kotlin-test") - testImplementation("org.mockito:mockito-core:5.0.0") + testImplementation("org.mockito:mockito-core:5.4.0") } testOptions { diff --git a/android/src/main/kotlin/com/github/josxha/maplibre/MapLibreMapController.kt b/android/src/main/kotlin/com/github/josxha/maplibre/MapLibreMapController.kt index 31cbbfba..aabad5de 100644 --- a/android/src/main/kotlin/com/github/josxha/maplibre/MapLibreMapController.kt +++ b/android/src/main/kotlin/com/github/josxha/maplibre/MapLibreMapController.kt @@ -24,6 +24,7 @@ import org.maplibre.android.maps.Style import org.maplibre.android.style.layers.CircleLayer import org.maplibre.android.style.layers.FillLayer import org.maplibre.android.style.sources.GeoJsonSource +import kotlin.coroutines.cancellation.CancellationException class MapLibreMapController( viewId: Int, @@ -72,9 +73,7 @@ class MapLibreMapController( } } - override fun getView(): View { - return mapViewContainer; - } + override fun getView(): View = mapViewContainer override fun onMapReady(mapLibreMap: MapLibreMap) { this.mapLibreMap = mapLibreMap @@ -88,31 +87,49 @@ class MapLibreMapController( } override fun dispose() { + // free any resources } override fun jumpTo( - center: LngLat, + center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, callback: (Result) -> Unit ) { - val latLng = LatLng(center.lat, center.lng); - val cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, zoom ?: 0.0) + val camera = CameraPosition.Builder() + if (center != null) camera.target(LatLng(center.lat, center.lng)) + if (zoom != null) camera.zoom(zoom) + if (pitch != null) camera.tilt(pitch) + if (bearing != null) camera.bearing(bearing) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(camera.build()) mapLibreMap.moveCamera(cameraUpdate) + callback(Result.success(Unit)); } override fun flyTo( - center: LngLat, + center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, + durationMs: Long, callback: (Result) -> Unit ) { - val latLng = LatLng(center.lat, center.lng); - val cameraUpdate = CameraUpdateFactory.newLatLngZoom(latLng, zoom ?: 0.0) - mapLibreMap.animateCamera(cameraUpdate) - callback(Result.success(Unit)); + val camera = CameraPosition.Builder() + if (center != null) camera.target(LatLng(center.lat, center.lng)) + if (zoom != null) camera.zoom(zoom) + if (pitch != null) camera.tilt(pitch) + if (bearing != null) camera.bearing(bearing) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(camera.build()) + mapLibreMap.animateCamera( + cameraUpdate, + durationMs.toInt(), + object : MapLibreMap.CancelableCallback { + override fun onCancel() = + callback(Result.failure(CancellationException("Animation cancelled."))) + + override fun onFinish() = callback(Result.success(Unit)) + }) } override fun toScreenLocation( diff --git a/android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt b/android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt index 5f55f843..fdc2e16c 100644 --- a/android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt +++ b/android/src/main/kotlin/com/github/josxha/maplibre/Pigeon.g.kt @@ -190,9 +190,9 @@ private open class PigeonPigeonCodec : StandardMessageCodec() { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface MapLibreHostApi { /** Move the viewport of the map to a new location without any animation. */ - fun jumpTo(center: LngLat, zoom: Double?, bearing: Double?, pitch: Double?, callback: (Result) -> Unit) + fun jumpTo(center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, callback: (Result) -> Unit) /** Animate the viewport of the map to a new location. */ - fun flyTo(center: LngLat, zoom: Double?, bearing: Double?, pitch: Double?, callback: (Result) -> Unit) + fun flyTo(center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, durationMs: Long, callback: (Result) -> Unit) /** Convert a coordinate to a location on the screen. */ fun toScreenLocation(lng: Double, lat: Double, callback: (Result) -> Unit) /** Convert a screen location to a coordinate. */ @@ -218,7 +218,7 @@ interface MapLibreHostApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val centerArg = args[0] as LngLat + val centerArg = args[0] as LngLat? val zoomArg = args[1] as Double? val bearingArg = args[2] as Double? val pitchArg = args[3] as Double? @@ -240,11 +240,12 @@ interface MapLibreHostApi { if (api != null) { channel.setMessageHandler { message, reply -> val args = message as List - val centerArg = args[0] as LngLat + val centerArg = args[0] as LngLat? val zoomArg = args[1] as Double? val bearingArg = args[2] as Double? val pitchArg = args[3] as Double? - api.flyTo(centerArg, zoomArg, bearingArg, pitchArg) { result: Result -> + val durationMsArg = args[4] as Long + api.flyTo(centerArg, zoomArg, bearingArg, pitchArg, durationMsArg) { result: Result -> val error = result.exceptionOrNull() if (error != null) { reply.reply(wrapError(error)) diff --git a/example/lib/controller_page.dart b/example/lib/controller_page.dart index 1bb3c8db..9eaffde8 100644 --- a/example/lib/controller_page.dart +++ b/example/lib/controller_page.dart @@ -1,6 +1,7 @@ // ignore_for_file: prefer_single_quotes, require_trailing_commas import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:maplibre/maplibre.dart'; @immutable @@ -29,37 +30,46 @@ class _ControllerPageState extends State { runSpacing: 8, children: [ OutlinedButton( - onPressed: () { - _controller.jumpTo( + onPressed: () async { + debugPrint('jumpTo start'); + await _controller.jumpTo( center: Position(172.4714, -42.4862), zoom: 4, + tilt: 0, + bearing: 0, ); + debugPrint('jumpTo end'); }, - child: const Text('Move to New Zealand'), + child: const Text('Jump to New Zealand'), ), OutlinedButton( - onPressed: () { - _controller.flyTo( - center: Position(-18.6874, 64.9445), - zoom: 5, - bearing: -50, - tilt: 60, - ); + onPressed: () async { + debugPrint('flyTo start'); + try { + await _controller.flyTo( + center: Position(-18.6874, 64.9445), + zoom: 5, + bearing: -50, + tilt: 60, + ); + debugPrint('flyTo end'); + } catch (error) { + final e = error as PlatformException; + debugPrint( + 'flyTo cancelled: code: ' + '"${e.code}", message: "${e.message}"', + ); + } }, - child: const Text('Animate to Iceland'), + child: const Text('Fly to Iceland'), ), ], ), ), Expanded( child: MapLibreMap( - options: MapOptions( - center: Position(9.17, 47.68), - ), + options: MapOptions(center: Position(9.17, 47.68), zoom: 3), onMapCreated: (controller) => _controller = controller, - onStyleLoaded: () async { - debugPrint('[MapLibreMap] onStyleLoadedCallback'); - }, ), ), ], diff --git a/ios/Classes/Pigeon.g.swift b/ios/Classes/Pigeon.g.swift index 6b82453a..bf0bbaa9 100644 --- a/ios/Classes/Pigeon.g.swift +++ b/ios/Classes/Pigeon.g.swift @@ -230,9 +230,9 @@ class PigeonPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol MapLibreHostApi { /// Move the viewport of the map to a new location without any animation. - func jumpTo(center: LngLat, zoom: Double?, bearing: Double?, pitch: Double?, completion: @escaping (Result) -> Void) + func jumpTo(center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, completion: @escaping (Result) -> Void) /// Animate the viewport of the map to a new location. - func flyTo(center: LngLat, zoom: Double?, bearing: Double?, pitch: Double?, completion: @escaping (Result) -> Void) + func flyTo(center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, durationMs: Int64, completion: @escaping (Result) -> Void) /// Convert a coordinate to a location on the screen. func toScreenLocation(lng: Double, lat: Double, completion: @escaping (Result) -> Void) /// Convert a screen location to a coordinate. @@ -256,7 +256,7 @@ class MapLibreHostApiSetup { if let api = api { jumpToChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let centerArg = args[0] as! LngLat + let centerArg: LngLat? = nilOrValue(args[0]) let zoomArg: Double? = nilOrValue(args[1]) let bearingArg: Double? = nilOrValue(args[2]) let pitchArg: Double? = nilOrValue(args[3]) @@ -277,11 +277,12 @@ class MapLibreHostApiSetup { if let api = api { flyToChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let centerArg = args[0] as! LngLat + let centerArg: LngLat? = nilOrValue(args[0]) let zoomArg: Double? = nilOrValue(args[1]) let bearingArg: Double? = nilOrValue(args[2]) let pitchArg: Double? = nilOrValue(args[3]) - api.flyTo(center: centerArg, zoom: zoomArg, bearing: bearingArg, pitch: pitchArg) { result in + let durationMsArg = args[4] as! Int64 + api.flyTo(center: centerArg, zoom: zoomArg, bearing: bearingArg, pitch: pitchArg, durationMs: durationMsArg) { result in switch result { case .success: reply(wrapResult(nil)) diff --git a/lib/src/map_controller.dart b/lib/src/map_controller.dart index 14663c37..4f6ad8d4 100644 --- a/lib/src/map_controller.dart +++ b/lib/src/map_controller.dart @@ -12,7 +12,7 @@ abstract interface class MapController { /// Instantly move the map camera to a new location. Future jumpTo({ - required Position center, + Position? center, double? zoom, double? bearing, double? tilt, @@ -20,10 +20,13 @@ abstract interface class MapController { /// Animate the map camera to a new location. Future flyTo({ - required Position center, - required double zoom, - required double bearing, - required double tilt, + Position? center, + double? zoom, + double? bearing, + double? tilt, + Duration nativeDuration = const Duration(seconds: 2), + double webSpeed = 1.2, + Duration? maxDuration, }); /// Add a [Marker] to the map. diff --git a/lib/src/native/pigeon.g.dart b/lib/src/native/pigeon.g.dart index b1e790b0..29fe3126 100644 --- a/lib/src/native/pigeon.g.dart +++ b/lib/src/native/pigeon.g.dart @@ -196,10 +196,10 @@ class MapLibreHostApi { /// Move the viewport of the map to a new location without any animation. Future jumpTo({ - required LngLat center, - double? zoom, - double? bearing, - double? pitch, + required LngLat? center, + required double? zoom, + required double? bearing, + required double? pitch, }) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.maplibre.MapLibreHostApi.jumpTo$pigeonVar_messageChannelSuffix'; @@ -226,10 +226,11 @@ class MapLibreHostApi { /// Animate the viewport of the map to a new location. Future flyTo({ - required LngLat center, - double? zoom, - double? bearing, - double? pitch, + required LngLat? center, + required double? zoom, + required double? bearing, + required double? pitch, + required int durationMs, }) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.maplibre.MapLibreHostApi.flyTo$pigeonVar_messageChannelSuffix'; @@ -240,7 +241,8 @@ class MapLibreHostApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = await pigeonVar_channel - .send([center, zoom, bearing, pitch]) as List?; + .send([center, zoom, bearing, pitch, durationMs]) + as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { diff --git a/lib/src/native/widget_state.dart b/lib/src/native/widget_state.dart index ea21a816..074dab1a 100644 --- a/lib/src/native/widget_state.dart +++ b/lib/src/native/widget_state.dart @@ -81,13 +81,13 @@ final class MapLibreMapStateNative extends State @override Future jumpTo({ - required Position center, + Position? center, double? zoom, double? bearing, double? tilt, }) => _hostApi.jumpTo( - center: center.toLngLat(), + center: center?.toLngLat(), zoom: zoom, bearing: bearing, pitch: tilt, @@ -95,16 +95,20 @@ final class MapLibreMapStateNative extends State @override Future flyTo({ - required Position center, + Position? center, double? zoom, double? bearing, double? tilt, + Duration nativeDuration = const Duration(seconds: 2), + double webSpeed = 1.2, + Duration? maxDuration, }) => _hostApi.flyTo( - center: center.toLngLat(), + center: center?.toLngLat(), zoom: zoom, bearing: bearing, pitch: tilt, + durationMs: nativeDuration.inMilliseconds, ); @override diff --git a/lib/src/web/widget_state.dart b/lib/src/web/widget_state.dart index 2cf5a09d..23cc4bfb 100644 --- a/lib/src/web/widget_state.dart +++ b/lib/src/web/widget_state.dart @@ -183,14 +183,14 @@ final class MapLibreMapStateWeb extends State @override Future jumpTo({ - required Position center, + Position? center, double? zoom, double? bearing, double? tilt, }) async => _map.jumpTo( interop.JumpToOptions( - center: center.toLngLat(), + center: center?.toLngLat(), zoom: zoom, bearing: bearing, pitch: tilt, @@ -199,17 +199,22 @@ final class MapLibreMapStateWeb extends State @override Future flyTo({ - required Position center, + Position? center, double? zoom, double? bearing, double? tilt, + Duration nativeDuration = const Duration(seconds: 2), + double webSpeed = 1.2, + Duration? maxDuration, }) async => _map.flyTo( interop.FlyToOptions( - center: center.toLngLat(), + center: center?.toLngLat(), zoom: zoom, bearing: bearing, pitch: tilt, + speed: webSpeed, + maxDuration: maxDuration?.inMilliseconds, ), ); diff --git a/linux/pigeon.g.cc b/linux/pigeon.g.cc index f6d355b4..fa2cf540 100644 --- a/linux/pigeon.g.cc +++ b/linux/pigeon.g.cc @@ -739,8 +739,10 @@ static void maplibre_map_libre_host_api_fly_to_cb(FlBasicMessageChannel* channel pitch_value = fl_value_get_float(value3); pitch = &pitch_value; } + FlValue* value4 = fl_value_get_list_value(message_, 4); + int64_t duration_ms = fl_value_get_int(value4); g_autoptr(MaplibreMapLibreHostApiResponseHandle) handle = maplibre_map_libre_host_api_response_handle_new(channel, response_handle); - self->vtable->fly_to(center, zoom, bearing, pitch, handle, self->user_data); + self->vtable->fly_to(center, zoom, bearing, pitch, duration_ms, handle, self->user_data); } static void maplibre_map_libre_host_api_to_screen_location_cb(FlBasicMessageChannel* channel, FlValue* message_, FlBasicMessageChannelResponseHandle* response_handle, gpointer user_data) { diff --git a/linux/pigeon.g.h b/linux/pigeon.g.h index bf518909..478311d4 100644 --- a/linux/pigeon.g.h +++ b/linux/pigeon.g.h @@ -189,7 +189,7 @@ G_DECLARE_FINAL_TYPE(MaplibreMapLibreHostApiResponseHandle, maplibre_map_libre_h */ typedef struct { void (*jump_to)(MaplibreLngLat* center, double* zoom, double* bearing, double* pitch, MaplibreMapLibreHostApiResponseHandle* response_handle, gpointer user_data); - void (*fly_to)(MaplibreLngLat* center, double* zoom, double* bearing, double* pitch, MaplibreMapLibreHostApiResponseHandle* response_handle, gpointer user_data); + void (*fly_to)(MaplibreLngLat* center, double* zoom, double* bearing, double* pitch, int64_t duration_ms, MaplibreMapLibreHostApiResponseHandle* response_handle, gpointer user_data); void (*to_screen_location)(double lng, double lat, MaplibreMapLibreHostApiResponseHandle* response_handle, gpointer user_data); void (*to_lng_lat)(double x, double y, MaplibreMapLibreHostApiResponseHandle* response_handle, gpointer user_data); void (*add_fill_layer)(const gchar* id, const gchar* source_id, MaplibreMapLibreHostApiResponseHandle* response_handle, gpointer user_data); diff --git a/macos/Classes/Pigeon.g.swift b/macos/Classes/Pigeon.g.swift index 6b82453a..bf0bbaa9 100644 --- a/macos/Classes/Pigeon.g.swift +++ b/macos/Classes/Pigeon.g.swift @@ -230,9 +230,9 @@ class PigeonPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol MapLibreHostApi { /// Move the viewport of the map to a new location without any animation. - func jumpTo(center: LngLat, zoom: Double?, bearing: Double?, pitch: Double?, completion: @escaping (Result) -> Void) + func jumpTo(center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, completion: @escaping (Result) -> Void) /// Animate the viewport of the map to a new location. - func flyTo(center: LngLat, zoom: Double?, bearing: Double?, pitch: Double?, completion: @escaping (Result) -> Void) + func flyTo(center: LngLat?, zoom: Double?, bearing: Double?, pitch: Double?, durationMs: Int64, completion: @escaping (Result) -> Void) /// Convert a coordinate to a location on the screen. func toScreenLocation(lng: Double, lat: Double, completion: @escaping (Result) -> Void) /// Convert a screen location to a coordinate. @@ -256,7 +256,7 @@ class MapLibreHostApiSetup { if let api = api { jumpToChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let centerArg = args[0] as! LngLat + let centerArg: LngLat? = nilOrValue(args[0]) let zoomArg: Double? = nilOrValue(args[1]) let bearingArg: Double? = nilOrValue(args[2]) let pitchArg: Double? = nilOrValue(args[3]) @@ -277,11 +277,12 @@ class MapLibreHostApiSetup { if let api = api { flyToChannel.setMessageHandler { message, reply in let args = message as! [Any?] - let centerArg = args[0] as! LngLat + let centerArg: LngLat? = nilOrValue(args[0]) let zoomArg: Double? = nilOrValue(args[1]) let bearingArg: Double? = nilOrValue(args[2]) let pitchArg: Double? = nilOrValue(args[3]) - api.flyTo(center: centerArg, zoom: zoomArg, bearing: bearingArg, pitch: pitchArg) { result in + let durationMsArg = args[4] as! Int64 + api.flyTo(center: centerArg, zoom: zoomArg, bearing: bearingArg, pitch: pitchArg, durationMs: durationMsArg) { result in switch result { case .success: reply(wrapResult(nil)) diff --git a/pigeons/pigeon.dart b/pigeons/pigeon.dart index cba4c5bc..733c1fe0 100644 --- a/pigeons/pigeon.dart +++ b/pigeons/pigeon.dart @@ -26,19 +26,20 @@ abstract interface class MapLibreHostApi { /// Move the viewport of the map to a new location without any animation. @async void jumpTo({ - required LngLat center, - double? zoom, - double? bearing, - double? pitch, + required LngLat? center, + required double? zoom, + required double? bearing, + required double? pitch, }); /// Animate the viewport of the map to a new location. @async void flyTo({ - required LngLat center, - double? zoom, - double? bearing, - double? pitch, + required LngLat? center, + required double? zoom, + required double? bearing, + required double? pitch, + required int durationMs, }); /// Convert a coordinate to a location on the screen. diff --git a/windows/runner/pigeon.g.cpp b/windows/runner/pigeon.g.cpp index ec9ef35b..28497538 100644 --- a/windows/runner/pigeon.g.cpp +++ b/windows/runner/pigeon.g.cpp @@ -325,11 +325,7 @@ void MapLibreHostApi::SetUp( try { const auto& args = std::get(message); const auto& encodable_center_arg = args.at(0); - if (encodable_center_arg.IsNull()) { - reply(WrapError("center_arg unexpectedly null.")); - return; - } - const auto& center_arg = std::any_cast(std::get(encodable_center_arg)); + const auto* center_arg = encodable_center_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_center_arg))); const auto& encodable_zoom_arg = args.at(1); const auto* zoom_arg = std::get_if(&encodable_zoom_arg); const auto& encodable_bearing_arg = args.at(2); @@ -360,18 +356,20 @@ void MapLibreHostApi::SetUp( try { const auto& args = std::get(message); const auto& encodable_center_arg = args.at(0); - if (encodable_center_arg.IsNull()) { - reply(WrapError("center_arg unexpectedly null.")); - return; - } - const auto& center_arg = std::any_cast(std::get(encodable_center_arg)); + const auto* center_arg = encodable_center_arg.IsNull() ? nullptr : &(std::any_cast(std::get(encodable_center_arg))); const auto& encodable_zoom_arg = args.at(1); const auto* zoom_arg = std::get_if(&encodable_zoom_arg); const auto& encodable_bearing_arg = args.at(2); const auto* bearing_arg = std::get_if(&encodable_bearing_arg); const auto& encodable_pitch_arg = args.at(3); const auto* pitch_arg = std::get_if(&encodable_pitch_arg); - api->FlyTo(center_arg, zoom_arg, bearing_arg, pitch_arg, [reply](std::optional&& output) { + const auto& encodable_duration_ms_arg = args.at(4); + if (encodable_duration_ms_arg.IsNull()) { + reply(WrapError("duration_ms_arg unexpectedly null.")); + return; + } + const int64_t duration_ms_arg = encodable_duration_ms_arg.LongValue(); + api->FlyTo(center_arg, zoom_arg, bearing_arg, pitch_arg, duration_ms_arg, [reply](std::optional&& output) { if (output.has_value()) { reply(WrapError(output.value())); return; diff --git a/windows/runner/pigeon.g.h b/windows/runner/pigeon.g.h index 2881eaf5..ba1abb36 100644 --- a/windows/runner/pigeon.g.h +++ b/windows/runner/pigeon.g.h @@ -224,17 +224,18 @@ class MapLibreHostApi { virtual ~MapLibreHostApi() {} // Move the viewport of the map to a new location without any animation. virtual void JumpTo( - const LngLat& center, + const LngLat* center, const double* zoom, const double* bearing, const double* pitch, std::function reply)> result) = 0; // Animate the viewport of the map to a new location. virtual void FlyTo( - const LngLat& center, + const LngLat* center, const double* zoom, const double* bearing, const double* pitch, + int64_t duration_ms, std::function reply)> result) = 0; // Convert a coordinate to a location on the screen. virtual void ToScreenLocation(