Skip to content

Commit 6bb4ed5

Browse files
committed
improve camera layout observer
1 parent cfb2465 commit 6bb4ed5

File tree

5 files changed

+80
-146
lines changed

5 files changed

+80
-146
lines changed

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/camera/CameraLayoutObserver.kt

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@ package com.mapbox.navigation.dropin.camera
22

33
import android.content.res.Configuration
44
import android.view.View
5+
import com.google.android.material.bottomsheet.BottomSheetBehavior
56
import com.mapbox.maps.EdgeInsets
67
import com.mapbox.navigation.core.MapboxNavigation
78
import com.mapbox.navigation.dropin.R
89
import com.mapbox.navigation.dropin.databinding.MapboxNavigationViewLayoutBinding
9-
import com.mapbox.navigation.ui.app.internal.Store
10+
import com.mapbox.navigation.dropin.navigationview.NavigationViewContext
1011
import com.mapbox.navigation.ui.app.internal.camera.CameraAction
11-
import com.mapbox.navigation.ui.app.internal.navigation.NavigationState
1212
import com.mapbox.navigation.ui.base.lifecycle.UIComponent
1313

1414
internal class CameraLayoutObserver(
15-
private val store: Store,
15+
private val context: NavigationViewContext,
1616
private val mapView: View,
1717
private val binding: MapboxNavigationViewLayoutBinding,
1818
) : UIComponent() {
@@ -21,63 +21,43 @@ internal class CameraLayoutObserver(
2121
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_v).toDouble()
2222
private val hPadding = binding.root.resources
2323
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_h).toDouble()
24-
private val vPaddingLandscape = binding.root.resources
25-
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_landscape_v).toDouble()
26-
private val hPaddingLandscape = binding.root.resources
27-
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_landscape_h).toDouble()
2824

2925
private val layoutListener = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
30-
val edgeInsets = when (deviceOrientation()) {
31-
Configuration.ORIENTATION_LANDSCAPE -> getLandscapePadding()
32-
else -> getPortraitPadding()
33-
}
34-
store.dispatch(CameraAction.UpdatePadding(edgeInsets))
26+
updateCameraPadding()
3527
}
3628

3729
override fun onAttached(mapboxNavigation: MapboxNavigation) {
3830
super.onAttached(mapboxNavigation)
3931
binding.coordinatorLayout.addOnLayoutChangeListener(layoutListener)
32+
context.behavior.infoPanelBehavior.bottomSheetState.observe { updateCameraPadding() }
4033
}
4134

4235
override fun onDetached(mapboxNavigation: MapboxNavigation) {
4336
super.onDetached(mapboxNavigation)
4437
binding.coordinatorLayout.removeOnLayoutChangeListener(layoutListener)
4538
}
4639

47-
private fun getPortraitPadding(): EdgeInsets {
48-
val top = binding.guidanceLayout.height.toDouble()
49-
val bottom = mapView.height.toDouble() - binding.roadNameLayout.top.toDouble()
50-
return when (store.state.value.navigation) {
51-
is NavigationState.DestinationPreview,
52-
is NavigationState.FreeDrive,
53-
is NavigationState.RoutePreview -> {
54-
EdgeInsets(vPadding, hPadding, bottom, hPadding)
55-
}
56-
is NavigationState.ActiveNavigation,
57-
is NavigationState.Arrival -> {
58-
EdgeInsets(vPadding + top, hPadding, bottom, hPadding)
59-
}
40+
private fun updateCameraPadding() {
41+
val edgeInsets = when (deviceOrientation()) {
42+
Configuration.ORIENTATION_LANDSCAPE -> getLandscapePadding()
43+
else -> getPortraitPadding()
6044
}
45+
context.store.dispatch(CameraAction.UpdatePadding(edgeInsets))
46+
}
47+
48+
private fun getPortraitPadding(): EdgeInsets {
49+
val top = binding.guidanceLayout.bottom
50+
val bottom = mapView.height - binding.roadNameLayout.top
51+
return EdgeInsets(vPadding + top, hPadding, vPadding + bottom, hPadding)
6152
}
6253

6354
private fun getLandscapePadding(): EdgeInsets {
64-
val bottom = mapView.height.toDouble() - binding.roadNameLayout.top.toDouble()
65-
return when (store.state.value.navigation) {
66-
is NavigationState.FreeDrive -> {
67-
EdgeInsets(vPaddingLandscape, hPaddingLandscape, bottom, hPaddingLandscape)
68-
}
69-
is NavigationState.DestinationPreview,
70-
is NavigationState.RoutePreview,
71-
is NavigationState.ActiveNavigation,
72-
is NavigationState.Arrival -> {
73-
EdgeInsets(
74-
vPaddingLandscape,
75-
hPaddingLandscape + binding.infoPanelLayout.right.toDouble(),
76-
bottom,
77-
hPaddingLandscape
78-
)
79-
}
80-
}
55+
val bottomSheetState = context.behavior.infoPanelBehavior.bottomSheetState.value
56+
val isBottomSheetVisible =
57+
bottomSheetState != null && bottomSheetState != BottomSheetBehavior.STATE_HIDDEN
58+
val bottomSheetWidth = if (isBottomSheetVisible) binding.infoPanelLayout.right else 0
59+
val bottom = mapView.height - binding.roadNameLayout.top
60+
return EdgeInsets(vPadding, hPadding + bottomSheetWidth, vPadding + bottom, hPadding)
8161
}
8262

8363
private fun deviceOrientation() = binding.root.resources.configuration.orientation

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/map/MapViewBinder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ abstract class MapViewBinder : UIBinder {
6767
val store = context.store
6868
val navigationState = store.select { it.navigation }
6969
return navigationListOf(
70-
CameraLayoutObserver(store, mapView, navigationViewBinding),
70+
CameraLayoutObserver(context, mapView, navigationViewBinding),
7171
LocationComponent(context.locationProvider),
7272
context.locationPuckComponent(mapView),
7373
LogoAttributionComponent(mapView, context.systemBarsInsets),
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<!-- Overview camera padding to ensure location puck and target pin are not clipped -->
4+
<dimen name="mapbox_camera_overview_padding_v">30dp</dimen>
5+
<dimen name="mapbox_camera_overview_padding_h">35dp</dimen>
6+
</resources>

libnavui-dropin/src/main/res/values/dimens.xml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@
2525
<dimen name="mapbox_maneuver_view_padding">4dp</dimen>
2626

2727
<!-- Overview camera padding to ensure location puck and target pin are not clipped -->
28-
<dimen name="mapbox_camera_overview_padding_v">50dp</dimen>
29-
<dimen name="mapbox_camera_overview_padding_landscape_v">35dp</dimen>
28+
<dimen name="mapbox_camera_overview_padding_v">30dp</dimen>
3029
<dimen name="mapbox_camera_overview_padding_h">50dp</dimen>
31-
<dimen name="mapbox_camera_overview_padding_landscape_h">35dp</dimen>
3230

3331
<dimen name="mapbox_extendable_button_width">72dp</dimen>
3432
<dimen name="mapbox_extendable_button_height">52dp</dimen>
Lines changed: 50 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
1-
@file:Suppress("PrivatePropertyName", "MaxLineLength")
2-
31
package com.mapbox.navigation.dropin.camera
42

53
import android.app.Service
64
import android.content.Context
75
import android.view.LayoutInflater
86
import android.view.View
9-
import android.view.View.OnLayoutChangeListener
107
import android.widget.FrameLayout
8+
import androidx.core.view.doOnNextLayout
119
import androidx.test.core.app.ApplicationProvider
10+
import com.google.android.material.bottomsheet.BottomSheetBehavior
1211
import com.mapbox.navigation.dropin.R
1312
import com.mapbox.navigation.dropin.databinding.MapboxNavigationViewLayoutBinding
13+
import com.mapbox.navigation.dropin.infopanel.InfoPanelBehavior
14+
import com.mapbox.navigation.dropin.navigationview.NavigationViewContext
15+
import com.mapbox.navigation.dropin.navigationview.NavigationViewModel
16+
import com.mapbox.navigation.dropin.testutil.TestLifecycleOwner
1417
import com.mapbox.navigation.dropin.util.TestStore
18+
import com.mapbox.navigation.testing.LoggingFrontendTestRule
1519
import com.mapbox.navigation.testing.MainCoroutineRule
1620
import com.mapbox.navigation.ui.app.internal.camera.CameraAction
17-
import com.mapbox.navigation.ui.app.internal.navigation.NavigationState
1821
import io.mockk.mockk
1922
import kotlinx.coroutines.ExperimentalCoroutinesApi
2023
import kotlinx.coroutines.coroutineScope
2124
import kotlinx.coroutines.launch
2225
import kotlinx.coroutines.suspendCancellableCoroutine
2326
import org.junit.Assert.assertEquals
24-
import org.junit.Assert.assertNotEquals
2527
import org.junit.Before
2628
import org.junit.Rule
2729
import org.junit.Test
@@ -37,9 +39,13 @@ class CameraLayoutObserverTest {
3739
@get:Rule
3840
val coroutineRule = MainCoroutineRule()
3941

42+
@get:Rule
43+
val loggerRule = LoggingFrontendTestRule()
44+
4045
private lateinit var store: TestStore
4146
private lateinit var binding: MapboxNavigationViewLayoutBinding
4247
private lateinit var mapView: View
48+
private lateinit var infoPanelBehavior: InfoPanelBehavior
4349
private lateinit var sut: CameraLayoutObserver
4450

4551
@Before
@@ -51,103 +57,68 @@ class CameraLayoutObserverTest {
5157
).apply {
5258
// slightly modifying default layout to simulate layout changes
5359
guidanceLayout.bottom = 100
54-
roadNameLayout.top = 100
55-
actionListLayout.left = 100
56-
infoPanelLayout.right = 100
60+
roadNameLayout.top = 900
61+
infoPanelLayout.right = 500
62+
}
63+
mapView = View(context).apply {
64+
bottom = 1000
5765
}
58-
mapView = View(context)
5966
store = TestStore()
60-
sut = CameraLayoutObserver(store, mapView, binding)
67+
val navContext = NavigationViewContext(
68+
context,
69+
TestLifecycleOwner(),
70+
NavigationViewModel(),
71+
) { store }
72+
infoPanelBehavior = navContext.behavior.infoPanelBehavior
73+
sut = CameraLayoutObserver(navContext, mapView, binding)
6174
}
6275

6376
@Test
6477
@Config(qualifiers = "port")
65-
fun `portrait - should update bottom padding for DestinationPreview, FreeDrive and RoutePreview state`() =
78+
fun `portrait - should update camera paddings`() =
6679
coroutineRule.runBlockingTest {
6780
sut.onAttached(mockk())
6881

69-
triggerLayoutChangesAndVerifyDispatchedActions(
70-
givenNavigationStates = listOf(
71-
NavigationState.DestinationPreview,
72-
NavigationState.FreeDrive,
73-
NavigationState.RoutePreview,
74-
)
75-
) { state, action ->
76-
assertEquals("$state|top", action.padding.top, PADDING_V_PORT, 0.001)
77-
assertNotEquals("$state|bottom", action.padding.bottom, PADDING_V_PORT, 0.001)
78-
assertEquals("$state|left", action.padding.left, PADDING_H_PORT, 0.001)
79-
assertEquals("$state|right", action.padding.right, PADDING_H_PORT, 0.001)
80-
}
81-
}
82-
83-
@Test
84-
@Config(qualifiers = "port")
85-
fun `portrait - should update top and bottom padding for ActiveNavigation and Arrival state`() =
86-
coroutineRule.runBlockingTest {
87-
sut.onAttached(mockk())
82+
binding.coordinatorLayout.triggerLayoutChange()
8883

89-
triggerLayoutChangesAndVerifyDispatchedActions(
90-
givenNavigationStates = listOf(
91-
NavigationState.ActiveNavigation,
92-
NavigationState.Arrival,
93-
)
94-
) { s, action ->
95-
assertNotEquals("$s|top", action.padding.top, PADDING_V_PORT, 0.001)
96-
assertNotEquals("$s|bottom", action.padding.bottom, PADDING_V_PORT, 0.001)
97-
assertEquals("$s|left", action.padding.left, PADDING_H_PORT, 0.001)
98-
assertEquals("$s|right", action.padding.right, PADDING_H_PORT, 0.001)
99-
}
84+
val action = store.actions.last() as CameraAction.UpdatePadding
85+
assertEquals("left", paddingH, action.padding.left, 0.001)
86+
assertEquals("top", 100 + paddingV, action.padding.top, 0.001)
87+
assertEquals("right", paddingH, action.padding.right, 0.001)
88+
assertEquals("bottom", 100 + paddingV, action.padding.bottom, 0.001)
10089
}
10190

10291
@Test
10392
@Config(qualifiers = "land")
104-
fun `landscape - should update bottom padding for FreeDrive state`() =
93+
fun `landscape - should update camera paddings when bottom sheet is collapsed`() =
10594
coroutineRule.runBlockingTest {
10695
sut.onAttached(mockk())
10796

108-
triggerLayoutChangesAndVerifyDispatchedActions(
109-
givenNavigationStates = listOf(
110-
NavigationState.FreeDrive
111-
)
112-
) { s, action ->
113-
assertEquals("$s|top", action.padding.top, PADDING_V_LAND, 0.001)
114-
assertNotEquals("$s|bottom", action.padding.bottom, PADDING_V_LAND, 0.001)
115-
assertEquals("$s|left", action.padding.left, PADDING_H_LAND, 0.001)
116-
assertEquals("$s|right", action.padding.right, PADDING_H_LAND, 0.001)
117-
}
97+
infoPanelBehavior.updateBottomSheetState(BottomSheetBehavior.STATE_COLLAPSED)
98+
binding.coordinatorLayout.triggerLayoutChange()
99+
100+
val action = store.actions.last() as CameraAction.UpdatePadding
101+
assertEquals("left", 500 + paddingH, action.padding.left, 0.001)
102+
assertEquals("top", paddingV, action.padding.top, 0.001)
103+
assertEquals("right", paddingH, action.padding.right, 0.001)
104+
assertEquals("bottom", 100 + paddingV, action.padding.bottom, 0.001)
118105
}
119106

120107
@Test
121108
@Config(qualifiers = "land")
122-
fun `landscape - should update left and bottom padding for all states except FreeDrive`() =
109+
fun `landscape - should update camera paddings when bottom sheet is hidden`() =
123110
coroutineRule.runBlockingTest {
124111
sut.onAttached(mockk())
125112

126-
triggerLayoutChangesAndVerifyDispatchedActions(
127-
givenNavigationStates = listOf(
128-
NavigationState.DestinationPreview,
129-
NavigationState.RoutePreview,
130-
NavigationState.ActiveNavigation,
131-
NavigationState.Arrival,
132-
)
133-
) { s, action ->
134-
assertEquals("$s|top", action.padding.top, PADDING_V_LAND, 0.001)
135-
assertNotEquals("$s|bottom", action.padding.bottom, PADDING_V_LAND, 0.001)
136-
assertNotEquals("$s|left", action.padding.left, PADDING_H_LAND, 0.001)
137-
assertEquals("$s|right", action.padding.right, PADDING_H_LAND, 0.001)
138-
}
139-
}
140-
141-
private suspend fun triggerLayoutChangesAndVerifyDispatchedActions(
142-
givenNavigationStates: List<NavigationState>,
143-
assertAction: (state: NavigationState, action: CameraAction.UpdatePadding) -> Unit
144-
) {
145-
givenNavigationStates.forEachIndexed { index, navState ->
146-
store.updateState { it.copy(navigation = navState) }
113+
infoPanelBehavior.updateBottomSheetState(BottomSheetBehavior.STATE_HIDDEN)
147114
binding.coordinatorLayout.triggerLayoutChange()
148-
assertAction(navState, store.actions[index] as CameraAction.UpdatePadding)
115+
116+
val action = store.actions.last() as CameraAction.UpdatePadding
117+
assertEquals("left", paddingH, action.padding.left, 0.001)
118+
assertEquals("top", paddingV, action.padding.top, 0.001)
119+
assertEquals("right", paddingH, action.padding.right, 0.001)
120+
assertEquals("bottom", 100 + paddingV, action.padding.bottom, 0.001)
149121
}
150-
}
151122

152123
private suspend fun View.triggerLayoutChange() = coroutineScope {
153124
launch {
@@ -158,34 +129,13 @@ class CameraLayoutObserverTest {
158129
}
159130

160131
private suspend fun View.waitForLayoutChange() = suspendCancellableCoroutine<Unit> { cont ->
161-
addOnLayoutChangeListener(object : OnLayoutChangeListener {
162-
override fun onLayoutChange(
163-
v: View?,
164-
left: Int,
165-
top: Int,
166-
right: Int,
167-
bottom: Int,
168-
oldLeft: Int,
169-
oldTop: Int,
170-
oldRight: Int,
171-
oldBottom: Int
172-
) {
173-
cont.resume(Unit)
174-
removeOnLayoutChangeListener(this)
175-
}
176-
})
132+
doOnNextLayout { cont.resume(Unit) }
177133
}
178134

179-
private val PADDING_V_PORT: Double
135+
private val paddingV: Double
180136
get() = binding.root.resources
181137
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_v).toDouble()
182-
private val PADDING_H_PORT: Double
138+
private val paddingH: Double
183139
get() = binding.root.resources
184140
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_h).toDouble()
185-
private val PADDING_V_LAND: Double
186-
get() = binding.root.resources
187-
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_landscape_v).toDouble()
188-
private val PADDING_H_LAND: Double
189-
get() = binding.root.resources
190-
.getDimensionPixelSize(R.dimen.mapbox_camera_overview_padding_landscape_h).toDouble()
191141
}

0 commit comments

Comments
 (0)