Skip to content

Commit bc592b1

Browse files
authored
Annotation tap and long press events (#977)
* Annotations tap listener as Cancelable * add long press listener as Cancelable
1 parent 94013c9 commit bc592b1

39 files changed

+906
-670
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
### main
22

33
* Introduce new experimental properties: `FillLayer.fillConstructBridgeGuardRail`, `FillLayer.fillBridgeGuardRailColor`, `FillLayer.fillTunnelStructureColor`, `CircleLayer.circleElevationReference`.
4+
* Introduce `tapEvents` and `longPressEvents` API to the Annotation Managers to handle tap and long press event callbacks for annotations:
5+
Example usage:
6+
```dart
7+
manager.tapEvents(
8+
onTap: (annotation) {
9+
print("Tapped annotation: ${annotation.id}");
10+
},
11+
);
12+
manager.longPressEvents(
13+
onLongPress: (annotation) {
14+
print("Long press annotation: ${annotation.id}");
15+
},
16+
);
17+
```
18+
19+
> [!NOTE]
20+
> As part of this change, `AnnotationOnClickListener` is now deprecated.
21+
> Tap events will now not propagate to annotations below the topmost one. If you tap on overlapping annotations, only the top annotation's tap event will be triggered.
422
523
### 2.9.0
624

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/annotation/AnnotationController.kt

Lines changed: 109 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@ package com.mapbox.maps.mapbox_maps.annotation
22

33
import com.mapbox.maps.MapView
44
import com.mapbox.maps.mapbox_maps.pigeons.*
5-
import com.mapbox.maps.mapbox_maps.pigeons.OnCircleAnnotationClickListener
6-
import com.mapbox.maps.mapbox_maps.pigeons.OnPointAnnotationClickListener
7-
import com.mapbox.maps.mapbox_maps.pigeons.OnPolygonAnnotationClickListener
8-
import com.mapbox.maps.mapbox_maps.pigeons.OnPolylineAnnotationClickListener
95
import com.mapbox.maps.mapbox_maps.pigeons.PointAnnotation
106
import com.mapbox.maps.plugin.annotation.Annotation
117
import com.mapbox.maps.plugin.annotation.AnnotationConfig
@@ -18,22 +14,19 @@ import com.mapbox.maps.plugin.annotation.generated.PolylineAnnotation
1814
import io.flutter.plugin.common.BinaryMessenger
1915
import io.flutter.plugin.common.MethodCall
2016
import io.flutter.plugin.common.MethodChannel
17+
import kotlin.collections.set
2118

2219
class AnnotationController(
2320
private val mapView: MapView,
2421
private val messenger: BinaryMessenger,
2522
private val channelSuffix: String
2623
) : ControllerDelegate {
2724
private val managerMap = mutableMapOf<String, AnnotationManager<*, *, *, *, *, *, *>>()
28-
private val streamSinkMap = mutableMapOf<String, PigeonEventSink<AnnotationInteractionContext>>()
25+
private val streamSinkMap = mutableMapOf<String, InteractionEventsHandler>()
2926
private val pointAnnotationController = PointAnnotationController(this)
3027
private val circleAnnotationController = CircleAnnotationController(this)
3128
private val polygonAnnotationController = PolygonAnnotationController(this)
3229
private val polylineAnnotationController = PolylineAnnotationController(this)
33-
private var onPointAnnotationClickListener: OnPointAnnotationClickListener? = null
34-
private var onPolygonAnnotationClickListener: OnPolygonAnnotationClickListener? = null
35-
private var onPolylineAnnotationClickListener: OnPolylineAnnotationClickListener? = null
36-
private var onCircleAnnotationClickListener: OnCircleAnnotationClickListener? = null
3730
private var index = 0
3831

3932
fun handleCreateManager(call: MethodCall, result: MethodChannel.Result) {
@@ -50,7 +43,13 @@ class AnnotationController(
5043
mapView.annotations.createCircleAnnotationManager(AnnotationConfig(belowLayerId, id, id)).apply {
5144
this.addClickListener(
5245
com.mapbox.maps.plugin.annotation.generated.OnCircleAnnotationClickListener { annotation ->
53-
onCircleAnnotationClickListener?.onCircleAnnotationClick(annotation.toFLTCircleAnnotation()) {}
46+
sendTapEvent(id, annotation)
47+
false
48+
}
49+
)
50+
this.addLongClickListener(
51+
OnCircleAnnotationLongClickListener { annotation ->
52+
sendLongPressEvent(id, annotation)
5453
false
5554
}
5655
)
@@ -74,7 +73,13 @@ class AnnotationController(
7473
mapView.annotations.createPointAnnotationManager(AnnotationConfig(belowLayerId, id, id)).apply {
7574
this.addClickListener(
7675
com.mapbox.maps.plugin.annotation.generated.OnPointAnnotationClickListener { annotation ->
77-
onPointAnnotationClickListener?.onPointAnnotationClick(annotation.toFLTPointAnnotation()) {}
76+
sendTapEvent(id, annotation)
77+
false
78+
}
79+
)
80+
this.addLongClickListener(
81+
OnPointAnnotationLongClickListener { annotation ->
82+
sendLongPressEvent(id, annotation)
7883
false
7984
}
8085
)
@@ -98,7 +103,13 @@ class AnnotationController(
98103
mapView.annotations.createPolygonAnnotationManager(AnnotationConfig(belowLayerId, id, id)).apply {
99104
this.addClickListener(
100105
com.mapbox.maps.plugin.annotation.generated.OnPolygonAnnotationClickListener { annotation ->
101-
onPolygonAnnotationClickListener?.onPolygonAnnotationClick(annotation.toFLTPolygonAnnotation()) {}
106+
sendTapEvent(id, annotation)
107+
false
108+
}
109+
)
110+
this.addLongClickListener(
111+
OnPolygonAnnotationLongClickListener { annotation ->
112+
sendLongPressEvent(id, annotation)
102113
false
103114
}
104115
)
@@ -122,7 +133,13 @@ class AnnotationController(
122133
mapView.annotations.createPolylineAnnotationManager(AnnotationConfig(belowLayerId, id, id)).apply {
123134
this.addClickListener(
124135
com.mapbox.maps.plugin.annotation.generated.OnPolylineAnnotationClickListener { annotation ->
125-
onPolylineAnnotationClickListener?.onPolylineAnnotationClick(annotation.toFLTPolylineAnnotation()) {}
136+
sendTapEvent(id, annotation)
137+
false
138+
}
139+
)
140+
this.addLongClickListener(
141+
OnPolylineAnnotationLongClickListener { annotation ->
142+
sendLongPressEvent(id, annotation)
126143
false
127144
}
128145
)
@@ -147,23 +164,13 @@ class AnnotationController(
147164
return
148165
}
149166
}
150-
151-
val interactionEvents = object : AnnotationDragEventsStreamHandler() {
152-
override fun onListen(p0: Any?, sink: PigeonEventSink<AnnotationInteractionContext>) {
153-
streamSinkMap[id] = sink
154-
}
155-
156-
override fun onCancel(p0: Any?) {
157-
streamSinkMap.remove(id)
158-
}
159-
}
160-
161-
AnnotationDragEventsStreamHandler.register(
167+
managerMap[id] = manager
168+
val eventsHandler = InteractionEventsHandler()
169+
eventsHandler.register(
162170
messenger,
163-
interactionEvents, "$channelSuffix/$id"
171+
"$channelSuffix/$id"
164172
)
165-
166-
managerMap[id] = manager
173+
streamSinkMap[id] = eventsHandler
167174
result.success(id)
168175
}
169176

@@ -176,10 +183,6 @@ class AnnotationController(
176183
}
177184

178185
fun setup() {
179-
onPointAnnotationClickListener = OnPointAnnotationClickListener(messenger, channelSuffix)
180-
onCircleAnnotationClickListener = OnCircleAnnotationClickListener(messenger, channelSuffix)
181-
onPolygonAnnotationClickListener = OnPolygonAnnotationClickListener(messenger, channelSuffix)
182-
onPolylineAnnotationClickListener = OnPolylineAnnotationClickListener(messenger, channelSuffix)
183186
_PointAnnotationMessenger.setUp(messenger, pointAnnotationController, channelSuffix)
184187
_CircleAnnotationMessenger.setUp(
185188
messenger,
@@ -200,10 +203,6 @@ class AnnotationController(
200203
_CircleAnnotationMessenger.setUp(messenger, null, channelSuffix)
201204
_PolylineAnnotationMessenger.setUp(messenger, null, channelSuffix)
202205
_PolygonAnnotationMessenger.setUp(messenger, null, channelSuffix)
203-
onPointAnnotationClickListener = null
204-
onCircleAnnotationClickListener = null
205-
onPolygonAnnotationClickListener = null
206-
onPolylineAnnotationClickListener = null
207206
}
208207

209208
fun sendDragEvent(managerId: String, annotation: Annotation<*>, gestureState: GestureState) {
@@ -219,7 +218,39 @@ class AnnotationController(
219218

220219
else -> throw IllegalArgumentException("$annotation is unsupported")
221220
}
222-
streamSinkMap[managerId]?.success(context)
221+
streamSinkMap[managerId]?.dragEventsSink?.success(context)
222+
}
223+
224+
fun sendTapEvent(managerId: String, annotation: Annotation<*>) {
225+
val context: AnnotationInteractionContext = when (annotation) {
226+
is com.mapbox.maps.plugin.annotation.generated.PointAnnotation ->
227+
PointAnnotationInteractionContext(annotation.toFLTPointAnnotation(), GestureState.ENDED)
228+
is CircleAnnotation ->
229+
CircleAnnotationInteractionContext(annotation.toFLTCircleAnnotation(), GestureState.ENDED)
230+
is PolygonAnnotation ->
231+
PolygonAnnotationInteractionContext(annotation.toFLTPolygonAnnotation(), GestureState.ENDED)
232+
is PolylineAnnotation ->
233+
PolylineAnnotationInteractionContext(annotation.toFLTPolylineAnnotation(), GestureState.ENDED)
234+
235+
else -> throw IllegalArgumentException("$annotation is unsupported")
236+
}
237+
streamSinkMap[managerId]?.tapEventsSink?.success(context)
238+
}
239+
240+
fun sendLongPressEvent(managerId: String, annotation: Annotation<*>) {
241+
val context: AnnotationInteractionContext = when (annotation) {
242+
is com.mapbox.maps.plugin.annotation.generated.PointAnnotation ->
243+
PointAnnotationInteractionContext(annotation.toFLTPointAnnotation(), GestureState.ENDED)
244+
is CircleAnnotation ->
245+
CircleAnnotationInteractionContext(annotation.toFLTCircleAnnotation(), GestureState.ENDED)
246+
is PolygonAnnotation ->
247+
PolygonAnnotationInteractionContext(annotation.toFLTPolygonAnnotation(), GestureState.ENDED)
248+
is PolylineAnnotation ->
249+
PolylineAnnotationInteractionContext(annotation.toFLTPolylineAnnotation(), GestureState.ENDED)
250+
251+
else -> throw IllegalArgumentException("$annotation is unsupported")
252+
}
253+
streamSinkMap[managerId]?.longPressEventsSink?.success(context)
223254
}
224255

225256
override fun getManager(managerId: String): AnnotationManager<*, *, *, *, *, *, *> {
@@ -228,4 +259,45 @@ class AnnotationController(
228259
}
229260
return managerMap[managerId]!!
230261
}
262+
263+
companion object {
264+
class InteractionEventsHandler {
265+
var tapEventsSink: PigeonEventSink<AnnotationInteractionContext>? = null
266+
var dragEventsSink: PigeonEventSink<AnnotationInteractionContext>? = null
267+
var longPressEventsSink: PigeonEventSink<AnnotationInteractionContext>? = null
268+
269+
fun register(messenger: BinaryMessenger, channelName: String) {
270+
val tapEvents = object : AnnotationInteractionEventsStreamHandler() {
271+
override fun onListen(p0: Any?, sink: PigeonEventSink<AnnotationInteractionContext>) {
272+
tapEventsSink = sink
273+
}
274+
275+
override fun onCancel(p0: Any?) {
276+
tapEventsSink = null
277+
}
278+
}
279+
val longPressEvents = object : AnnotationInteractionEventsStreamHandler() {
280+
override fun onListen(p0: Any?, sink: PigeonEventSink<AnnotationInteractionContext>) {
281+
longPressEventsSink = sink
282+
}
283+
284+
override fun onCancel(p0: Any?) {
285+
longPressEventsSink = null
286+
}
287+
}
288+
val dragEvents = object : AnnotationInteractionEventsStreamHandler() {
289+
override fun onListen(p0: Any?, sink: PigeonEventSink<AnnotationInteractionContext>) {
290+
dragEventsSink = sink
291+
}
292+
293+
override fun onCancel(p0: Any?) {
294+
dragEventsSink = null
295+
}
296+
}
297+
AnnotationInteractionEventsStreamHandler.register(messenger, tapEvents, "$channelName/tap")
298+
AnnotationInteractionEventsStreamHandler.register(messenger, tapEvents, "$channelName/drag")
299+
AnnotationInteractionEventsStreamHandler.register(messenger, longPressEvents, "$channelName/long_press")
300+
}
301+
}
302+
}
231303
}

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/CircleAnnotationMessenger.kt

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@ private fun wrapError(exception: Throwable): List<Any?> {
3434
}
3535
}
3636

37-
private fun createConnectionError(channelName: String): FlutterError {
38-
return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "")
39-
}
40-
4137
/**
4238
* Selects the base of circle-elevation. Some modes might require precomputed elevation data in the tileset.
4339
* Default value: "none".
@@ -376,31 +372,6 @@ private open class CircleAnnotationMessengerPigeonCodec : StandardMessageCodec()
376372
}
377373
}
378374

379-
/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
380-
class OnCircleAnnotationClickListener(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
381-
companion object {
382-
/** The codec used by OnCircleAnnotationClickListener. */
383-
val codec: MessageCodec<Any?> by lazy {
384-
CircleAnnotationMessengerPigeonCodec()
385-
}
386-
}
387-
fun onCircleAnnotationClick(annotationArg: CircleAnnotation, callback: (Result<Unit>) -> Unit) {
388-
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
389-
val channelName = "dev.flutter.pigeon.mapbox_maps_flutter.OnCircleAnnotationClickListener.onCircleAnnotationClick$separatedMessageChannelSuffix"
390-
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
391-
channel.send(listOf(annotationArg)) {
392-
if (it is List<*>) {
393-
if (it.size > 1) {
394-
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
395-
} else {
396-
callback(Result.success(Unit))
397-
}
398-
} else {
399-
callback(Result.failure(createConnectionError(channelName)))
400-
}
401-
}
402-
}
403-
}
404375
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
405376
interface _CircleAnnotationMessenger {
406377
fun create(managerId: String, annotationOption: CircleAnnotationOptions, callback: (Result<CircleAnnotation>) -> Unit)

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/GestureListeners.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,10 +463,10 @@ class PigeonEventSink<T>(private val sink: EventChannel.EventSink) {
463463
}
464464
}
465465

466-
abstract class AnnotationDragEventsStreamHandler : GestureListenersPigeonEventChannelWrapper<AnnotationInteractionContext> {
466+
abstract class AnnotationInteractionEventsStreamHandler : GestureListenersPigeonEventChannelWrapper<AnnotationInteractionContext> {
467467
companion object {
468-
fun register(messenger: BinaryMessenger, streamHandler: AnnotationDragEventsStreamHandler, instanceName: String = "") {
469-
var channelName: String = "dev.flutter.pigeon.mapbox_maps_flutter.AnnotationInteractions._annotationDragEvents"
468+
fun register(messenger: BinaryMessenger, streamHandler: AnnotationInteractionEventsStreamHandler, instanceName: String = "") {
469+
var channelName: String = "dev.flutter.pigeon.mapbox_maps_flutter.AnnotationInteractions._annotationInteractionEvents"
470470
if (instanceName.isNotEmpty()) {
471471
channelName += ".$instanceName"
472472
}

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/PointAnnotationMessenger.kt

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ private fun wrapError(exception: Throwable): List<Any?> {
3333
)
3434
}
3535
}
36-
37-
private fun createConnectionError(channelName: String): FlutterError {
38-
return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "")
39-
}
4036
private fun deepEqualsPointAnnotationMessenger(a: Any?, b: Any?): Boolean {
4137
if (a is ByteArray && b is ByteArray) {
4238
return a.contentEquals(b)
@@ -1219,31 +1215,6 @@ private open class PointAnnotationMessengerPigeonCodec : StandardMessageCodec()
12191215
}
12201216
}
12211217

1222-
/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */
1223-
class OnPointAnnotationClickListener(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") {
1224-
companion object {
1225-
/** The codec used by OnPointAnnotationClickListener. */
1226-
val codec: MessageCodec<Any?> by lazy {
1227-
PointAnnotationMessengerPigeonCodec()
1228-
}
1229-
}
1230-
fun onPointAnnotationClick(annotationArg: PointAnnotation, callback: (Result<Unit>) -> Unit) {
1231-
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
1232-
val channelName = "dev.flutter.pigeon.mapbox_maps_flutter.OnPointAnnotationClickListener.onPointAnnotationClick$separatedMessageChannelSuffix"
1233-
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
1234-
channel.send(listOf(annotationArg)) {
1235-
if (it is List<*>) {
1236-
if (it.size > 1) {
1237-
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
1238-
} else {
1239-
callback(Result.success(Unit))
1240-
}
1241-
} else {
1242-
callback(Result.failure(createConnectionError(channelName)))
1243-
}
1244-
}
1245-
}
1246-
}
12471218
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
12481219
interface _PointAnnotationMessenger {
12491220
fun create(managerId: String, annotationOption: PointAnnotationOptions, callback: (Result<PointAnnotation>) -> Unit)

0 commit comments

Comments
 (0)