1+ package com.mapbox.maps.testapp.annotation
2+
3+ import android.graphics.Color
4+ import androidx.test.espresso.Espresso
5+ import androidx.test.espresso.matcher.ViewMatchers
6+ import com.mapbox.android.gestures.MoveGestureDetector
7+ import com.mapbox.geojson.Point
8+ import com.mapbox.maps.R
9+ import com.mapbox.maps.dsl.cameraOptions
10+ import com.mapbox.maps.plugin.annotation.annotations
11+ import com.mapbox.maps.plugin.annotation.generated.CircleAnnotation
12+ import com.mapbox.maps.plugin.annotation.generated.CircleAnnotationOptions
13+ import com.mapbox.maps.plugin.annotation.generated.createCircleAnnotationManager
14+ import com.mapbox.maps.plugin.gestures.OnMoveListener
15+ import com.mapbox.maps.plugin.gestures.TopPriorityOnMoveListener
16+ import com.mapbox.maps.plugin.gestures.gestures
17+ import com.mapbox.maps.testapp.BaseMapTest
18+ import com.mapbox.maps.testapp.gestures.GesturesUiTestUtils
19+ import org.junit.Assert
20+ import org.junit.Test
21+ import java.util.concurrent.CountDownLatch
22+ import java.util.concurrent.TimeUnit
23+
24+ class AnnotationOnMoveTest : BaseMapTest () {
25+
26+ private lateinit var circleAnnotation: CircleAnnotation
27+
28+ override fun loadMap () {
29+ super .loadMap()
30+ rule.scenario.onActivity {
31+ mapboxMap.setCamera(INITIAL_CAMERA )
32+ }
33+ }
34+
35+ /* *
36+ * Assuming we perform async QRF in annotation manager's `onMoveBegin` we have to be sure that:
37+ * a) map is not moved until QRF is executed (this is verified in gesture plugin implementation)
38+ * b) no user explicit `OnMoveListener.onMove`s are called until QRF is executed
39+ * or after QRF showed that we tapped on draggable annotation.
40+ */
41+ @Test
42+ fun annotationsOnMoveTest () {
43+ var userDefinedMoveBeginCalled = false
44+ val latch = CountDownLatch (1 )
45+ rule.scenario.onActivity {
46+ mapView.annotations.createCircleAnnotationManager().apply {
47+ val circleAnnotationOptions: CircleAnnotationOptions = CircleAnnotationOptions ()
48+ .withPoint(INITIAL_CAMERA .center!! )
49+ .withCircleColor(Color .YELLOW )
50+ .withCircleRadius(12.0 )
51+ .withDraggable(true )
52+ circleAnnotation = create(circleAnnotationOptions)
53+ }
54+ mapView.getMapboxMap().addOnMapIdleListener {
55+ latch.countDown()
56+ }
57+ mapView.gestures.addOnMoveListener(object : OnMoveListener {
58+ override fun onMoveBegin (detector : MoveGestureDetector ) {
59+ userDefinedMoveBeginCalled = true
60+
61+ // simulate situation that gestures plugin received explicit `onMove` during async QRF;
62+ // otherwise QRF in reality works too fast and finishes async before first `onMove` is called by our gestures library
63+ val gesturesImpl = Class .forName(" com.mapbox.maps.plugin.gestures.GesturesPluginImpl" )
64+ val internalHandleMove = gesturesImpl.getDeclaredMethod(
65+ " handleMove\$ plugin_gestures_publicDebug" ,
66+ MoveGestureDetector ::class .java,
67+ Float ::class .java,
68+ Float ::class .java
69+ )
70+ internalHandleMove.isAccessible = true
71+ internalHandleMove.invoke(
72+ mapView.gestures,
73+ detector,
74+ 10.0f ,
75+ 0f
76+ )
77+ }
78+
79+ override fun onMove (detector : MoveGestureDetector ): Boolean {
80+ // Make sure this user defined `onMove` is not called due to the
81+ // `circleAnnotation` consuming all the `onMove` events, either
82+ // because the QRF is being done or because the circle annotation is being dragged
83+ throw RuntimeException (" User defined onMove must not be called!" )
84+ }
85+
86+ override fun onMoveEnd (detector : MoveGestureDetector ) {
87+ }
88+ })
89+ }
90+ Assert .assertTrue(latch.await(10_000 , TimeUnit .MILLISECONDS ))
91+ // simulate 1-finger pan gesture starting from the center of the MapView
92+ // to make sure we click the annotation
93+ val shiftX = 10f * pixelRatio
94+ Espresso
95+ .onView(ViewMatchers .withId(R .id.mapView))
96+ .perform(
97+ GesturesUiTestUtils .move(
98+ shiftX,
99+ 0f
100+ )
101+ )
102+ Assert .assertTrue(userDefinedMoveBeginCalled)
103+ Assert .assertEquals(
104+ 24.938827583733797 ,
105+ circleAnnotation.point.longitude(),
106+ EPS
107+ )
108+ Assert .assertEquals(
109+ INITIAL_CAMERA .center!! .latitude(),
110+ circleAnnotation.point.latitude(),
111+ EPS
112+ )
113+ }
114+
115+ @Test
116+ fun addOnMoveListenerOrderingOneTest () {
117+ val listOfMoveBeginEvents = mutableListOf<String >()
118+ rule.scenario.onActivity {
119+ mapView.gestures.addOnMoveListener(object : OnMoveListener {
120+ override fun onMoveBegin (detector : MoveGestureDetector ) {
121+ listOfMoveBeginEvents.add(" 1_normal" )
122+ }
123+
124+ override fun onMove (detector : MoveGestureDetector ): Boolean { return false }
125+
126+ override fun onMoveEnd (detector : MoveGestureDetector ) { }
127+ })
128+ mapView.gestures.addOnMoveListener(object : OnMoveListener {
129+ override fun onMoveBegin (detector : MoveGestureDetector ) {
130+ listOfMoveBeginEvents.add(" 2_normal" )
131+ }
132+
133+ override fun onMove (detector : MoveGestureDetector ): Boolean { return false }
134+
135+ override fun onMoveEnd (detector : MoveGestureDetector ) { }
136+ })
137+ mapView.gestures.addOnMoveListener(object : TopPriorityOnMoveListener {
138+ override fun onMoveBegin (detector : MoveGestureDetector ) {
139+ listOfMoveBeginEvents.add(" 1_priority" )
140+ }
141+
142+ override fun onMove (detector : MoveGestureDetector ): Boolean { return false }
143+
144+ override fun onMoveEnd (detector : MoveGestureDetector ) { }
145+ })
146+ mapView.gestures.addOnMoveListener(object : TopPriorityOnMoveListener {
147+ override fun onMoveBegin (detector : MoveGestureDetector ) {
148+ listOfMoveBeginEvents.add(" 2_priority" )
149+ }
150+
151+ override fun onMove (detector : MoveGestureDetector ): Boolean { return false }
152+
153+ override fun onMoveEnd (detector : MoveGestureDetector ) { }
154+ })
155+ }
156+ Espresso
157+ .onView(ViewMatchers .withId(R .id.mapView))
158+ .perform(
159+ GesturesUiTestUtils .move(
160+ 200f ,
161+ 0f
162+ )
163+ )
164+ Assert .assertArrayEquals(
165+ arrayOf(" 1_priority" , " 2_priority" , " 1_normal" , " 2_normal" ),
166+ listOfMoveBeginEvents.toTypedArray()
167+ )
168+ }
169+
170+ private companion object {
171+ private val INITIAL_CAMERA = cameraOptions {
172+ center(Point .fromLngLat(24.9384 , 60.1699 ))
173+ zoom(14.0 )
174+ pitch(0.0 )
175+ bearing(0.0 )
176+ }
177+ private const val EPS = 0.0001
178+ }
179+ }
0 commit comments