Skip to content

Commit 24cb06d

Browse files
[Drop-In UI] Fix of multiple infoPanel issues (#6361)
* Fixed info panel layout issues when in fullscreen mode. Exposed BottomSheet slideOffset value via NavigationViewListener. Updated QA-Test-App custom info panel layout example. Fixed road name label position when updating bottom sheet state Added examples for custom info panel layouts with fixed height Fixed incorrect RoadNameLabel placement when bottom sheet is visible in landscape mode. Tests fix. CHANGELOG entry Fixed info panel > small content size support. Fixed InfoPanelCoordinator resetSlideOffset logic. NavigationViewListener KDoc updates. * Unit tests fix
1 parent c39dee4 commit 24cb06d

25 files changed

Lines changed: 761 additions & 195 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Mapbox welcomes participation and contributions from everyone.
99
- Added `ComponentInstaller` for the `LocationComponent` that offers simplified integration of map LocationPuck. [#6206](https://github.com/mapbox/mapbox-navigation-android/pull/6206)
1010
- Added `ViewStyleCustomization.locationPuck` that allows to define a custom location puck using `NavigationView`. [#6365](https://github.com/mapbox/mapbox-navigation-android/pull/6365)
1111
- Added _Experimental_ `RouteRefreshStatesObserver` that can be used to observe route refresh states. To subscribe and unsubscribe on updates corresponding use `MapboxNavigation#registerRouteRefreshStateObserver` and `MapboxNavigation#unregisterRouteRefreshStateObserver`. [#6345](https://github.com/mapbox/mapbox-navigation-android/pull/6345)
12+
- Introduced `NavigationViewListener.onInfoPanelSlide` callback that allows observation of `NavigationView`'s Info Panel slide offset. [#6361](https://github.com/mapbox/mapbox-navigation-android/pull/6361)
1213
#### Bug fixes and improvements
1314
- Marked `PredictiveCacheController`, `MapboxBuildingView`, `ViewportDataSourceUpdateObserver`, `NavigationScaleGestureHandler`, `NavigationCameraStateChangedObserver`, `NavigationCameraStateTransition`, `NavigationCameraTransition`, `TransitionEndListener`, `MapboxRecenterButton`, `MapboxRouteOverviewButton`, `MapboxJunctionView`, `MapboxSignboardView`, `MapboxRoadNameLabelView`, `MapboxRoadNameView`, `MapboxRouteArrowView`, `MapboxRouteLineView`, `MapboxCameraModeButton` methods and `View.capture` extension with `@UiThread` annotation. [#6235](https://github.com/mapbox/mapbox-navigation-android/pull/6235)
1415
- Marked `Binder`, `MapboxExtendableButton` methods and `MapboxNavigation#installComponents` methods with `@UiThread` annotation. [#6268](https://github.com/mapbox/mapbox-navigation-android/pull/6268)
@@ -19,6 +20,8 @@ Mapbox welcomes participation and contributions from everyone.
1920
- Fixed crash caused by `ConstantVelocityInterpolator` creating `PathInterpolator` with an invalid path. [#6367](https://github.com/mapbox/mapbox-navigation-android/pull/6367)
2021
- Improved alternatives id robustness by adding new alternatives to existing instead of replacing them during `MapboxNavigation#requestAlternativeRoutes`. [#6373](https://github.com/mapbox/mapbox-navigation-android/pull/6373)
2122
- Improved stop detector for auto profile. [#6373](https://github.com/mapbox/mapbox-navigation-android/pull/6373)
23+
- Fixed `RoadNameLabel` position issues by making sure that it updates when expanding and collapsing the info panel. [#6361](https://github.com/mapbox/mapbox-navigation-android/pull/6361)
24+
- Fixed `InfoPanel` overlapping the `RoadNameLabel` in free drive state with the device being in landscape orientation. [#6361](https://github.com/mapbox/mapbox-navigation-android/pull/6361)
2225

2326
## Mapbox Navigation SDK 2.9.0-alpha.2 - 16 September, 2022
2427
### Changelog

libnavui-dropin/api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,10 @@ package com.mapbox.navigation.dropin {
100100
method public void onInfoPanelExpanded();
101101
method public void onInfoPanelHidden();
102102
method public void onInfoPanelSettling();
103+
method public void onInfoPanelSlide(float slideOffset);
103104
method public void onManeuverCollapsed();
104105
method public void onManeuverExpanded();
106+
method public void onMapClicked(com.mapbox.geojson.Point point);
105107
method public void onOverviewCameraMode();
106108
method public void onRouteFetchCanceled(com.mapbox.api.directions.v5.models.RouteOptions routeOptions, com.mapbox.navigation.base.route.RouterOrigin routerOrigin);
107109
method public void onRouteFetchFailed(java.util.List<com.mapbox.navigation.base.route.RouterFailure> reasons, com.mapbox.api.directions.v5.models.RouteOptions routeOptions);

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,16 @@ abstract class NavigationViewListener {
131131
*/
132132
open fun onInfoPanelSettling() = Unit
133133

134+
/**
135+
* Called when the info panel is being dragged.
136+
*
137+
* @param slideOffset The new offset of info panel within [-1,1] range. Offset increases
138+
* as info panel is moving upward and decreases in the opposite direction.
139+
* Range 0 to 1: Denotes the panel is transitioning from collapsed to expanded state.
140+
* Range -1 to 0: Denotes the panel is transitioning from hidden to collapsed state.
141+
*/
142+
open fun onInfoPanelSlide(slideOffset: Float) = Unit
143+
134144
/**
135145
* Called when hardware back button has been pressed.
136146
*

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ internal class NavigationViewListenerRegistry(
110110
}
111111
launch {
112112
infoPanelSubscriber
113-
.infoPanelBehavior
113+
.bottomSheetState
114114
.filterNotNull()
115115
.collect { behavior ->
116116
when (behavior) {
@@ -122,7 +122,9 @@ internal class NavigationViewListenerRegistry(
122122
}
123123
}
124124
}
125-
125+
launch {
126+
infoPanelSubscriber.slideOffset.collect(listener::onInfoPanelSlide)
127+
}
126128
launch {
127129
maneuverSubscriber
128130
.maneuverBehavior

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/binder/infopanel/InfoPanelBinder.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Context
44
import android.view.LayoutInflater
55
import android.view.ViewGroup
66
import androidx.core.graphics.Insets
7+
import androidx.core.view.updatePadding
78
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
89
import com.mapbox.navigation.core.internal.extensions.navigationListOf
910
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
@@ -124,12 +125,8 @@ internal class MapboxInfoPanelBinder : InfoPanelBinder() {
124125
layout.findViewById(R.id.infoPanelContent)
125126

126127
override fun applySystemBarsInsets(layout: ViewGroup, insets: Insets) {
127-
layout.setPadding(
128-
layout.paddingLeft,
129-
layout.paddingTop,
130-
layout.paddingRight,
131-
insets.bottom
132-
)
128+
layout.updatePadding(bottom = insets.bottom)
129+
// top, left and right insets are applied by InfoPanelComponent
133130
}
134131

135132
override fun bind(viewGroup: ViewGroup): MapboxNavigationObserver {

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/component/infopanel/InfoPanelBehavior.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@ import kotlinx.coroutines.flow.asStateFlow
55

66
internal class InfoPanelBehavior {
77

8-
private val _infoPanelBehavior = MutableStateFlow<Int?>(null)
9-
val infoPanelBehavior = _infoPanelBehavior.asStateFlow()
8+
private val _bottomSheetState = MutableStateFlow<Int?>(null)
9+
val bottomSheetState = _bottomSheetState.asStateFlow()
1010

11-
fun updateBehavior(newState: Int) {
12-
_infoPanelBehavior.value = newState
11+
private val _slideOffset = MutableStateFlow<Float>(-1f)
12+
val slideOffset = _slideOffset.asStateFlow()
13+
14+
fun updateBottomSheetState(newState: Int) {
15+
_bottomSheetState.value = newState
16+
}
17+
18+
fun updateSlideOffset(slideOffset: Float) {
19+
if (!slideOffset.isNaN()) {
20+
_slideOffset.value = slideOffset.coerceIn(-1f, 1f)
21+
}
1322
}
1423
}
Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.mapbox.navigation.dropin.component.infopanel
22

33
import android.view.ViewGroup
4+
import androidx.core.view.updatePadding
45
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
56
import com.mapbox.navigation.core.MapboxNavigation
7+
import com.mapbox.navigation.dropin.NavigationView
68
import com.mapbox.navigation.dropin.NavigationViewContext
9+
import com.mapbox.navigation.dropin.internal.extensions.updateMargins
710
import com.mapbox.navigation.ui.base.lifecycle.UIComponent
811
import kotlinx.coroutines.flow.combine
912

@@ -13,21 +16,55 @@ internal class InfoPanelComponent(
1316
private val context: NavigationViewContext
1417
) : UIComponent() {
1518

19+
private companion object {
20+
const val APPLY_TOP_PADDING_THRESHOLD = 0.8f
21+
}
22+
1623
override fun onAttached(mapboxNavigation: MapboxNavigation) {
1724
super.onAttached(mapboxNavigation)
18-
stylesFlow().observe { (background, marginLeft, marginRight) ->
19-
layout.layoutParams = (layout.layoutParams as? ViewGroup.MarginLayoutParams)?.apply {
20-
setMargins(marginLeft, topMargin, marginRight, bottomMargin)
25+
26+
val navigationView = findNavigationView()
27+
combine(
28+
context.infoPanelBehavior.slideOffset,
29+
context.systemBarsInsets
30+
) { slideOffset, insets ->
31+
slideOffset to insets
32+
}.observe { (slideOffset, insets) ->
33+
val isFullHeightLayout = layout.height == navigationView?.height
34+
if (isFullHeightLayout && APPLY_TOP_PADDING_THRESHOLD < slideOffset) {
35+
val f = 1.0f - (1.0f - slideOffset) / (1.0f - APPLY_TOP_PADDING_THRESHOLD)
36+
val top = insets.top * f
37+
layout.updatePadding(top = top.toInt(), bottom = insets.bottom)
38+
} else {
39+
layout.updatePadding(top = 0, bottom = insets.bottom)
2140
}
41+
}
42+
43+
stylesFlow().observe { (background, marginLeft, marginRight) ->
44+
layout.updateMargins(
45+
left = marginLeft,
46+
right = marginRight
47+
)
2248
layout.setBackgroundResource(background)
2349
}
2450
}
2551

52+
private fun findNavigationView(): NavigationView? {
53+
var v = layout.parent
54+
while (v != null && v !is NavigationView) {
55+
v = v.parent
56+
}
57+
return v as? NavigationView
58+
}
59+
2660
private fun stylesFlow() = context.styles.let { styles ->
2761
combine(
2862
styles.infoPanelBackground,
2963
styles.infoPanelMarginStart,
30-
styles.infoPanelMarginEnd
31-
) { background, marginStart, marginEnd -> Triple(background, marginStart, marginEnd) }
64+
styles.infoPanelMarginEnd,
65+
context.systemBarsInsets
66+
) { background, marginStart, marginEnd, insets ->
67+
Triple(background, marginStart + insets.left, marginEnd + insets.right)
68+
}
3269
}
3370
}

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/coordinator/InfoPanelCoordinator.kt

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ internal class InfoPanelCoordinator(
3434

3535
private val updateGuideline = object : BottomSheetBehavior.BottomSheetCallback() {
3636
override fun onStateChanged(bottomSheet: View, newState: Int) {
37-
context.infoPanelBehavior.updateBehavior(newState)
38-
setGuidelinePosition(bottomSheet, context.systemBarsInsets.value)
37+
context.infoPanelBehavior.updateBottomSheetState(newState)
38+
updateGuidelinePosition()
3939
}
4040

4141
override fun onSlide(bottomSheet: View, slideOffset: Float) {
42-
setGuidelinePosition(bottomSheet, context.systemBarsInsets.value)
42+
context.infoPanelBehavior.updateSlideOffset(slideOffset)
43+
updateGuidelinePosition()
4344
}
4445
}
4546

@@ -55,18 +56,19 @@ internal class InfoPanelCoordinator(
5556
behavior.addBottomSheetCallback(updateGuideline)
5657
coroutineScope.launch {
5758
bottomSheetState().collect { state ->
59+
val prevState = behavior.state
5860
when (state) {
5961
BottomSheetBehavior.STATE_HIDDEN -> behavior.hide()
6062
BottomSheetBehavior.STATE_COLLAPSED,
6163
BottomSheetBehavior.STATE_HALF_EXPANDED,
6264
BottomSheetBehavior.STATE_EXPANDED -> behavior.show(state)
6365
}
66+
resetSlideOffset(prevState, state)
67+
updateGuidelinePosition()
6468
}
6569
}
6670
coroutineScope.launch {
67-
context.systemBarsInsets.collect {
68-
setGuidelinePosition(infoPanel, it)
69-
}
71+
context.systemBarsInsets.collect(this@InfoPanelCoordinator::updateGuidelinePosition)
7072
}
7173
coroutineScope.launch {
7274
context.options.isInfoPanelHideable.collect { hideable ->
@@ -107,11 +109,12 @@ internal class InfoPanelCoordinator(
107109
}
108110

109111
private fun bottomSheetState() = combine(
112+
context.uiBinders.infoPanelContentBinder,
110113
store.select { it.destination?.point },
111114
store.select { it.navigation },
112115
context.options.showInfoPanelInFreeDrive,
113-
context.options.infoPanelForcedState
114-
) { _, navigationState, showInfoPanelInFreeDrive, infoPanelForcedState ->
116+
context.options.infoPanelForcedState,
117+
) { _, _, navigationState, showInfoPanelInFreeDrive, infoPanelForcedState ->
115118
if (infoPanelForcedState != 0) {
116119
infoPanelForcedState
117120
} else if (showInfoPanelInFreeDrive || navigationState != NavigationState.FreeDrive) {
@@ -138,9 +141,27 @@ internal class InfoPanelCoordinator(
138141
state = BottomSheetBehavior.STATE_HIDDEN
139142
}
140143

141-
private fun setGuidelinePosition(bottomSheet: View, systemBarsInsets: Insets) {
142-
val parentHeight = (bottomSheet.parent as ViewGroup).height
143-
guidelineBottom.setGuidelineEnd(parentHeight - bottomSheet.top - systemBarsInsets.bottom)
144+
private fun updateGuidelinePosition(systemBarsInsets: Insets = context.systemBarsInsets.value) {
145+
val parentHeight = (infoPanel.parent as ViewGroup).height
146+
val maxPos = (parentHeight * GUIDELINE_MAX_POSITION_PERCENT).toInt()
147+
val pos = parentHeight - infoPanel.top - systemBarsInsets.bottom
148+
guidelineBottom.setGuidelineEnd(pos.coerceIn(0, maxPos))
149+
}
150+
151+
private fun resetSlideOffset(prevBottomSheetState: Int, bottomSheetState: Int) {
152+
if (prevBottomSheetState == BottomSheetBehavior.STATE_EXPANDED) {
153+
// BottomSheet slideOffset value is always in [-1,1] range.
154+
// From -1.0 when hidden, 0.0 when collapsed to 1.0 when expanded.
155+
when (bottomSheetState) {
156+
BottomSheetBehavior.STATE_EXPANDED -> 1.0f
157+
BottomSheetBehavior.STATE_HALF_EXPANDED -> 0.5f
158+
BottomSheetBehavior.STATE_COLLAPSED -> 0.0f
159+
BottomSheetBehavior.STATE_HIDDEN -> -1.0f
160+
else -> null
161+
}?.also { slideOffset ->
162+
context.infoPanelBehavior.updateSlideOffset(slideOffset)
163+
}
164+
}
144165
}
145166

146167
/**
@@ -168,4 +189,12 @@ internal class InfoPanelCoordinator(
168189
}
169190
}
170191
}
192+
193+
private companion object {
194+
/**
195+
* Guideline bottom maximum position within its parent view.
196+
* This value must be within 0.0f..1.0f range.
197+
*/
198+
const val GUIDELINE_MAX_POSITION_PERCENT = 0.3f
199+
}
171200
}
Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
package com.mapbox.navigation.dropin.coordinator
22

3-
import android.content.res.Configuration
3+
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
44
import android.view.ViewGroup
55
import androidx.constraintlayout.widget.ConstraintLayout
66
import androidx.core.view.updateLayoutParams
7+
import com.google.android.material.bottomsheet.BottomSheetBehavior
78
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
89
import com.mapbox.navigation.core.MapboxNavigation
910
import com.mapbox.navigation.dropin.NavigationViewContext
1011
import com.mapbox.navigation.dropin.R
1112
import com.mapbox.navigation.dropin.binder.roadlabel.RoadNameViewBinder
12-
import com.mapbox.navigation.ui.app.internal.navigation.NavigationState
1313
import com.mapbox.navigation.ui.base.lifecycle.UIBinder
1414
import com.mapbox.navigation.ui.base.lifecycle.UICoordinator
1515
import kotlinx.coroutines.flow.Flow
1616
import kotlinx.coroutines.flow.collect
17+
import kotlinx.coroutines.flow.filterNotNull
1718
import kotlinx.coroutines.flow.map
1819
import kotlinx.coroutines.launch
1920

@@ -26,42 +27,33 @@ internal class RoadNameLabelCoordinator(
2627
val roadNameLabelLayout: ViewGroup
2728
) : UICoordinator<ViewGroup>(roadNameLabelLayout) {
2829

29-
override fun MapboxNavigation.flowViewBinders(): Flow<UIBinder> {
30+
override fun onAttached(mapboxNavigation: MapboxNavigation) {
31+
super.onAttached(mapboxNavigation)
32+
3033
coroutineScope.launch {
31-
context.store.select { it.navigation }.collect { navigationState ->
32-
when (roadNameLabelLayout.resources.configuration.orientation) {
33-
Configuration.ORIENTATION_LANDSCAPE -> {
34-
adjustLandscapeConstraints(navigationState)
34+
context.infoPanelBehavior.bottomSheetState
35+
.filterNotNull()
36+
.map { bottomSheetState ->
37+
val isBottomSheetVisible = bottomSheetState != BottomSheetBehavior.STATE_HIDDEN
38+
if (isOrientationLandscape() && isBottomSheetVisible) {
39+
R.id.guidelineBegin
40+
} else {
41+
ConstraintLayout.LayoutParams.PARENT_ID
3542
}
36-
else -> {
37-
roadNameLabelLayout.updateLayoutParams<ConstraintLayout.LayoutParams> {
38-
startToStart = R.id.container
39-
}
43+
}.collect { startConstraintId ->
44+
roadNameLabelLayout.updateLayoutParams<ConstraintLayout.LayoutParams> {
45+
startToStart = startConstraintId
4046
}
4147
}
42-
}
4348
}
49+
}
4450

51+
private fun isOrientationLandscape() =
52+
roadNameLabelLayout.resources.configuration.orientation == ORIENTATION_LANDSCAPE
53+
54+
override fun MapboxNavigation.flowViewBinders(): Flow<UIBinder> {
4555
return context.uiBinders.roadName.map {
4656
it ?: RoadNameViewBinder(context)
4757
}
4858
}
49-
50-
private fun adjustLandscapeConstraints(navigationState: NavigationState) {
51-
when (navigationState) {
52-
NavigationState.FreeDrive -> {
53-
roadNameLabelLayout.updateLayoutParams<ConstraintLayout.LayoutParams> {
54-
startToStart = R.id.container
55-
}
56-
}
57-
NavigationState.DestinationPreview,
58-
NavigationState.RoutePreview,
59-
NavigationState.ActiveNavigation,
60-
NavigationState.Arrival -> {
61-
roadNameLabelLayout.updateLayoutParams<ConstraintLayout.LayoutParams> {
62-
startToStart = R.id.guidelineBegin
63-
}
64-
}
65-
}
66-
}
6759
}

0 commit comments

Comments
 (0)