@@ -9,6 +9,7 @@ import androidx.compose.foundation.Image
99import androidx.compose.foundation.background
1010import androidx.compose.foundation.clickable
1111import androidx.compose.foundation.interaction.MutableInteractionSource
12+ import androidx.compose.foundation.layout.Arrangement
1213import androidx.compose.foundation.layout.Box
1314import androidx.compose.foundation.layout.Column
1415import androidx.compose.foundation.layout.Row
@@ -24,6 +25,7 @@ import androidx.compose.material.FloatingActionButton
2425import androidx.compose.material.MaterialTheme
2526import androidx.compose.material.Text
2627import androidx.compose.runtime.Composable
28+ import androidx.compose.runtime.CompositionLocalProvider
2729import androidx.compose.runtime.getValue
2830import androidx.compose.runtime.key
2931import androidx.compose.runtime.mutableStateOf
@@ -44,6 +46,7 @@ import androidx.compose.ui.graphics.Outline
4446import androidx.compose.ui.graphics.Path
4547import androidx.compose.ui.graphics.Shape
4648import androidx.compose.ui.graphics.asAndroidPath
49+ import androidx.compose.ui.platform.LocalLayoutDirection
4750import androidx.compose.ui.res.painterResource
4851import androidx.compose.ui.text.style.TextAlign
4952import androidx.compose.ui.tooling.preview.Preview
@@ -191,59 +194,86 @@ public class DynamicViewAnnotationActivity : ComponentActivity() {
191194 mutableStateOf(true )
192195 }
193196
197+ // Toggles the MapView's own layoutDirection between LTR/RTL to verify Compose-based
198+ // view annotations remain visible and correctly positioned regardless of host layout
199+ // direction (see ViewAnnotationManagerImpl - annotation views default to
200+ // Gravity.START|TOP, which must resolve against a container pinned to LTR).
201+ var isLayoutDirectionRtl by remember {
202+ mutableStateOf(false )
203+ }
204+
194205 MapboxMapComposeTheme {
195206 ExampleScaffold (
196207 floatingActionButton = {
197- FloatingActionButton (
198- onClick = {
199- if ((viewportState.mapViewportStatus as ? ViewportStatus .State )?.state is FollowPuckViewportState ) {
200- viewportState.transitionToOverviewState(overviewViewportStateOptions)
201- } else {
202- viewportState.transitionToFollowPuckState(followPuckViewportStateOption)
203- }
204- },
205- shape = RoundedCornerShape (16 .dp),
208+ Column (
209+ horizontalAlignment = Alignment .End ,
210+ verticalArrangement = Arrangement .spacedBy(8 .dp)
206211 ) {
207- Text (
208- modifier = Modifier .padding(10 .dp),
209- text = if ((viewportState.mapViewportStatus as ? ViewportStatus .State )?.state is FollowPuckViewportState ) " Overview" else " Follow puck"
210- )
212+ FloatingActionButton (
213+ onClick = { isLayoutDirectionRtl = ! isLayoutDirectionRtl },
214+ shape = RoundedCornerShape (16 .dp),
215+ ) {
216+ Text (
217+ modifier = Modifier .padding(10 .dp),
218+ text = " Layout direction: ${if (isLayoutDirectionRtl) " RTL" else " LTR" } "
219+ )
220+ }
221+ FloatingActionButton (
222+ onClick = {
223+ if ((viewportState.mapViewportStatus as ? ViewportStatus .State )?.state is FollowPuckViewportState ) {
224+ viewportState.transitionToOverviewState(overviewViewportStateOptions)
225+ } else {
226+ viewportState.transitionToFollowPuckState(followPuckViewportStateOption)
227+ }
228+ },
229+ shape = RoundedCornerShape (16 .dp),
230+ ) {
231+ Text (
232+ modifier = Modifier .padding(10 .dp),
233+ text = if ((viewportState.mapViewportStatus as ? ViewportStatus .State )?.state is FollowPuckViewportState ) " Overview" else " Follow puck"
234+ )
235+ }
211236 }
212237 }
213238 ) {
214- MapboxMap (
215- Modifier .fillMaxSize(),
216- mapViewportState = viewportState,
217- style = {
218- NavigationStyle (isMainActive)
219- }
239+ CompositionLocalProvider (
240+ LocalLayoutDirection provides
241+ if (isLayoutDirectionRtl) LayoutDirection .Rtl else LayoutDirection .Ltr
220242 ) {
221- MapEffect (Unit ) { mapView ->
222- mapView.location.apply {
223- setLocationProvider(
224- SimulateRouteLocationProvider (
225- featureRouteMain.geometry() as LineString
226- )
227- )
228- locationPuck = LocationPuck2D (
229- bearingImage = ImageHolder .from(R .drawable.mapbox_user_puck_icon),
230- )
231- enabled = true
232- puckBearingEnabled = true
233- puckBearing = PuckBearing .COURSE
243+ MapboxMap (
244+ Modifier .fillMaxSize(),
245+ mapViewportState = viewportState,
246+ style = {
247+ NavigationStyle (isMainActive)
234248 }
235- // Only show view annotation and adjust location puck position after all the runtime layers
236- // are added.
237- mapView.mapboxMap.mapLoadedEvents.firstOrNull()?.let {
238- mapView.location.layerAbove = LAYER_CONSTRUCTION
239- showDynamicViewAnnotations = true
240- viewportState.transitionToOverviewState(overviewViewportStateOptions)
249+ ) {
250+ MapEffect (Unit ) { mapView ->
251+ mapView.location.apply {
252+ setLocationProvider(
253+ SimulateRouteLocationProvider (
254+ featureRouteMain.geometry() as LineString
255+ )
256+ )
257+ locationPuck = LocationPuck2D (
258+ bearingImage = ImageHolder .from(R .drawable.mapbox_user_puck_icon),
259+ )
260+ enabled = true
261+ puckBearingEnabled = true
262+ puckBearing = PuckBearing .COURSE
263+ }
264+ // Only show view annotation and adjust location puck position after all the runtime layers
265+ // are added.
266+ mapView.mapboxMap.mapLoadedEvents.firstOrNull()?.let {
267+ mapView.location.layerAbove = LAYER_CONSTRUCTION
268+ showDynamicViewAnnotations = true
269+ viewportState.transitionToOverviewState(overviewViewportStateOptions)
270+ }
241271 }
242- }
243272
244- if (showDynamicViewAnnotations) {
245- DynamicViewAnnotations (isMainActive) {
246- isMainActive = ! isMainActive
273+ if (showDynamicViewAnnotations) {
274+ DynamicViewAnnotations (isMainActive, isLayoutDirectionRtl) {
275+ isMainActive = ! isMainActive
276+ }
247277 }
248278 }
249279 }
@@ -314,7 +344,11 @@ public class DynamicViewAnnotationActivity : ComponentActivity() {
314344
315345 @Composable
316346 @MapboxMapComposable
317- private fun DynamicViewAnnotations (isMainActive : Boolean , toggleActiveRoute : () -> Unit ) {
347+ private fun DynamicViewAnnotations (
348+ isMainActive : Boolean ,
349+ isLayoutDirectionRtl : Boolean ,
350+ toggleActiveRoute : () -> Unit
351+ ) {
318352 var etaViewAnnotationAnchor by remember {
319353 mutableStateOf(ViewAnnotationAnchor .BOTTOM_LEFT )
320354 }
@@ -375,11 +409,22 @@ public class DynamicViewAnnotationActivity : ComponentActivity() {
375409 }
376410 }
377411 ) {
378- AlternativeDVAContent (
379- isInitial = ! isMainActive,
380- alternativeEtaViewAnnotationAnchor = alternativeEtaViewAnnotationAnchor,
381- onClick = toggleActiveRoute
382- )
412+ // ViewAnnotation hosts this content in its own ComposeView (see ViewAnnotation.kt), which
413+ // maps-sdk pins to LAYOUT_DIRECTION_LOCALE - the nearest LocalLayoutDirection provider to
414+ // this content, so it resolves against the real device locale regardless of any
415+ // CompositionLocalProvider wrapped further up around the outer MapboxMap composable.
416+ // Override LocalLayoutDirection directly here (Compose's nearest-provider-wins resolution)
417+ // to verify content mirroring without changing the device's actual locale.
418+ CompositionLocalProvider (
419+ LocalLayoutDirection provides
420+ if (isLayoutDirectionRtl) LayoutDirection .Rtl else LayoutDirection .Ltr
421+ ) {
422+ AlternativeDVAContent (
423+ isInitial = ! isMainActive,
424+ alternativeEtaViewAnnotationAnchor = alternativeEtaViewAnnotationAnchor,
425+ onClick = toggleActiveRoute
426+ )
427+ }
383428 }
384429
385430 // parking view annotation 1
0 commit comments