Skip to content

Commit 9f0cf95

Browse files
pengdevgithub-actions[bot]
authored andcommitted
[plugin-indoorselector] Fix scroll position not resetting when switching buildings (#15677)
GitOrigin-RevId: 2bb69f9bab9c949188107543a09fe49cef8d1d1a
1 parent 8e06bb6 commit 9f0cf95

4 files changed

Lines changed: 110 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ Mapbox welcomes participation and contributions from everyone.
8080
## Dependencies
8181
* Update gl-native to [v11.26.0](https://github.com/mapbox/mapbox-maps-android/releases/tag/v11.26.0), common to [v24.26.0](https://github.com/mapbox/mapbox-maps-android/releases/tag/v11.26.0).
8282

83+
## Bug fixes 🐞
84+
* [plugin-indoorselector] Fix scroll position not scrolling to the selected floor when switching to a different building.
85+
8386
# 11.26.0-rc.1 June 29, 2026
8487

8588
## Features ✨ and improvements 🏁

plugin-indoorselector/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ android {
99
minSdk = libs.versions.androidMinSdkVersion.get().toInt()
1010
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1111
}
12+
testOptions {
13+
unitTests {
14+
isIncludeAndroidResources = true
15+
}
16+
}
1217
}
1318

1419
mapboxLibrary {

plugin-indoorselector/src/main/java/com/mapbox/maps/plugin/indoorselector/IndoorSelectorViewImpl.kt

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ import android.widget.ImageView
1515
import android.widget.LinearLayout
1616
import android.widget.ScrollView
1717
import android.widget.TextView
18+
import androidx.annotation.VisibleForTesting
19+
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
1820
import androidx.core.content.ContextCompat
21+
import androidx.core.view.doOnLayout
1922
import androidx.core.view.isVisible
2023
import com.mapbox.maps.IndoorFloor
2124
import com.mapbox.maps.MapboxExperimental
@@ -40,7 +43,8 @@ internal class IndoorSelectorViewImpl @JvmOverloads constructor(
4043
defStyleAttr: Int = 0
4144
) : IndoorSelectorView, FrameLayout(context, attrs, defStyleAttr) {
4245

43-
private val itemHeightPx = resources.getDimensionPixelSize(R.dimen.mapbox_indoor_item_height)
46+
@VisibleForTesting(otherwise = PRIVATE)
47+
internal val itemHeightPx = resources.getDimensionPixelSize(R.dimen.mapbox_indoor_item_height)
4448
private val cornerRadiusPx = resources.getDimension(R.dimen.mapbox_indoor_corner_radius)
4549

4650
private val containerLayout = LinearLayout(context).apply {
@@ -166,7 +170,22 @@ internal class IndoorSelectorViewImpl @JvmOverloads constructor(
166170
}
167171

168172
updateContainerHeight()
169-
post { updateArrows() }
173+
val targetScrollY = selectedFloorScrollOffset(floors, selectedFloorId)
174+
doOnLayout {
175+
scrollView.scrollTo(0, targetScrollY)
176+
updateArrows()
177+
}
178+
}
179+
180+
@VisibleForTesting(otherwise = PRIVATE)
181+
internal fun selectedFloorScrollOffset(floors: List<IndoorFloor>, selectedId: String?): Int {
182+
val index = floors.indexOfFirst { it.id == selectedId }
183+
if (index < 0) return 0
184+
// Offset by 1 so the selected floor lands below the up-arrow overlay when there are floors above it.
185+
val rawOffset = (index - 1).coerceAtLeast(0) * itemHeightPx
186+
if (floors.size <= MAX_VISIBLE_ITEMS) return rawOffset
187+
val maxScroll = (floors.size - MAX_VISIBLE_ITEMS) * itemHeightPx
188+
return rawOffset.coerceAtMost(maxScroll)
170189
}
171190

172191
// Updates background and text color for all floor items based on selection
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.mapbox.maps.plugin.indoorselector
2+
3+
import com.mapbox.annotation.MapboxExperimental
4+
import com.mapbox.maps.indoorFloor
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Before
7+
import org.junit.Test
8+
import org.junit.runner.RunWith
9+
import org.robolectric.RobolectricTestRunner
10+
import org.robolectric.RuntimeEnvironment
11+
12+
@OptIn(MapboxExperimental::class)
13+
@RunWith(RobolectricTestRunner::class)
14+
class IndoorSelectorViewImplTest {
15+
16+
private lateinit var view: IndoorSelectorViewImpl
17+
18+
@Before
19+
fun setUp() {
20+
view = IndoorSelectorViewImpl(RuntimeEnvironment.getApplication())
21+
}
22+
23+
@Test
24+
fun `scroll offset positions selected floor below up-arrow`() {
25+
val floors = listOf(
26+
indoorFloor("b0", "G"),
27+
indoorFloor("b1", "1"),
28+
indoorFloor("b2", "2"),
29+
)
30+
// index 2 → (2-1)*itemHeightPx so floor above selected is covered by arrow, not selected itself
31+
assertEquals(1 * view.itemHeightPx, view.selectedFloorScrollOffset(floors, "b2"))
32+
}
33+
34+
@Test
35+
fun `scroll offset is zero when first floor selected`() {
36+
val floors = listOf(
37+
indoorFloor("b0", "G"),
38+
indoorFloor("b1", "1"),
39+
indoorFloor("b2", "2"),
40+
)
41+
// index 0 → coerceAtLeast(0) = 0
42+
assertEquals(0, view.selectedFloorScrollOffset(floors, "b0"))
43+
}
44+
45+
@Test
46+
fun `scroll offset is zero when second floor selected`() {
47+
val floors = listOf(
48+
indoorFloor("b0", "G"),
49+
indoorFloor("b1", "1"),
50+
indoorFloor("b2", "2"),
51+
)
52+
// index 1 → (1-1)*itemHeightPx = 0
53+
assertEquals(0, view.selectedFloorScrollOffset(floors, "b1"))
54+
}
55+
56+
@Test
57+
fun `scroll offset is zero when selected floor not found`() {
58+
val floors = listOf(
59+
indoorFloor("b0", "G"),
60+
indoorFloor("b1", "1"),
61+
)
62+
assertEquals(0, view.selectedFloorScrollOffset(floors, null))
63+
}
64+
65+
@Test
66+
fun `scroll offset is clamped to max scroll when last floor selected beyond visible window`() {
67+
// 6 floors > MAX_VISIBLE_ITEMS (4): naive (index - 1) offset would be 4 * itemHeightPx,
68+
// but only 2 items' worth of content is scrollable, so it must clamp to that.
69+
val floors = (0..5).map { indoorFloor("b$it", "$it") }
70+
val maxScroll = 2 * view.itemHeightPx
71+
assertEquals(maxScroll, view.selectedFloorScrollOffset(floors, "b5"))
72+
}
73+
74+
@Test
75+
fun `scroll offset is unclamped when mid-list floor selected in a long floor list`() {
76+
// 6 floors > MAX_VISIBLE_ITEMS (4), but index 2 → (2-1)*itemHeightPx stays within
77+
// max scroll (2*itemHeightPx), so the clamp should be a no-op here.
78+
val floors = (0..5).map { indoorFloor("b$it", "$it") }
79+
assertEquals(1 * view.itemHeightPx, view.selectedFloorScrollOffset(floors, "b2"))
80+
}
81+
}

0 commit comments

Comments
 (0)