Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -594,40 +594,39 @@ class RouteMapController(
val focus = stopFocusSession
val emphasizedRoute = routeId?.let { RouteDirectionKey(it, presentationDirectionId) }
val routeColors = _focusedRouteColors.value
val badges = if (emphasizedRoute == null) {
focusedGeometry.toRouteBadges(focusedRoutes, routeColors)
} else emptyList()
val selected = selectedTripPresentation()
// A route emphasized inside stop focus carries an adjacency color (keyed on the focused stop's
// trips); the selected trip keeps that color so selecting a vehicle doesn't drop the route back
// to its GTFS hue. In whole-route mode there is no adjacency entry, so fall back to GTFS.
val selectedAdjacencyColor = selected?.let { routeColors[it.routeDirection] }
val selectedTrip = selected?.points
?.takeIf { it.size >= 2 }
?.let { points ->
focusedRoutePolyline(selectedAdjacencyColor ?: currentRouteColor(), points, directional = true)
if (selected != null) {
// See selectedTripStyle: stop focus alone gates the underlay, not whether an adjacency
// color happened to be found for this exact direction (#1902).
val style = selectedTripStyle(focus != null, selected.routeDirection, routeColors, currentRouteColor())
val selectedTrip = selected.points
.takeIf { it.size >= 2 }
?.let { points -> focusedRoutePolyline(style.color, points, directional = true) }
// A selected vehicle shows its exact trip everywhere: the trip line, framed to that trip,
// over its scheduled stops.
if (selectedTrip != null) {
renderState.setRoutePolylines(
polylines = focusedGeometry.toTripFocusedRoutePolylines(
selected.routeDirection,
routeColors,
if (style.includeUnderlay) {
selectedDirectionUnderlay(selected.routeDirection.directionId)
} else emptyList(),
selectedTrip,
),
framingPolylines = listOf(selectedTrip),
routeModeScalesStopsWithZoom = isActive,
)
// A selected trip is only reachable in single-route mode, where emphasizedRoute is
// always non-null too — the badges below are always suppressed here regardless.
renderState.setRouteBadges(emptyList())
stopsController.setRoutePresentation(selectedTripStopPresentation(selected))
return
}
// A selected vehicle shows its exact trip everywhere: the trip line, framed to that trip, over
// its scheduled stops. In whole-route mode it sits on a thin same-direction underlay; inside
// stop focus the emphasized route already reads via its adjacency color, so the generic
// direction underlay is dropped (it would otherwise look like standalone route mode) and only
// the focused-stop siblings remain beneath the exact trip.
if (selected != null && selectedTrip != null) {
renderState.setRoutePolylines(
polylines = focusedGeometry.toTripFocusedRoutePolylines(
selected.routeDirection,
routeColors,
if (selectedAdjacencyColor != null) emptyList()
else selectedDirectionUnderlay(selected.routeDirection.directionId),
selectedTrip,
),
framingPolylines = listOf(selectedTrip),
routeModeScalesStopsWithZoom = isActive,
)
renderState.setRouteBadges(badges)
stopsController.setRoutePresentation(selectedTripStopPresentation(selected))
return
}
val badges = if (emphasizedRoute == null) {
focusedGeometry.toRouteBadges(focusedRoutes, routeColors)
} else emptyList()
if (focus == null) {
renderState.setRoutePolylines(basePolylines, routeModeScalesStopsWithZoom = isActive)
renderState.setRouteBadges(emptyList())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,27 @@ private data class RouteBadgeSpec(
val shapes: List<FocusedTripShape>,
)

/** The selected-trip line's color and whether the generic same-direction underlay stays beneath it. */
internal data class SelectedTripStyle(val color: Int, val includeUnderlay: Boolean)

/**
* [stopFocusActive] alone gates the underlay: inside stop focus the focused-stop's own siblings
* already carry the route's other geometry, so the underlay is dropped even when [selectedRouteDirection]
* isn't among the focused stop's own trips and [routeColors] carries no adjacency entry for it — that
* combination used to fall back to a whole-route-style underlay (the #1899 regression fixed by #1902),
* because the underlay decision was proxied off the color lookup instead of the real stop-focus state.
* A color miss still falls back to [gtfsColor]; only the underlay must not follow it.
*/
internal fun selectedTripStyle(
stopFocusActive: Boolean,
selectedRouteDirection: RouteDirectionKey,
routeColors: Map<RouteDirectionKey, Int>,
gtfsColor: Int,
): SelectedTripStyle = SelectedTripStyle(
color = routeColors[selectedRouteDirection] ?: gtfsColor,
includeUnderlay = !stopFocusActive,
)

/** Presented route-direction identities at each scheduled stop, optionally narrowed to [route]. */
internal fun FocusedTripStops.routeDirectionsByStopId(
trips: Set<FocusedTrip>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,38 @@ class RouteViewGeometryTest {
assertSame(selectedTrip, lines.last())
}

@Test
fun `selected trip style is gated on stop-focus state, not on an adjacency color hit`() {
val direction = RouteDirectionKey("45", 0)
val withColor = mapOf(direction to 10)

// Whole-route mode: always the thin underlay, and always the GTFS color since there's no
// adjacency map to begin with.
assertEquals(
SelectedTripStyle(color = 99, includeUnderlay = true),
selectedTripStyle(stopFocusActive = false, direction, routeColors = emptyMap(), gtfsColor = 99),
)
// Stop focus, no adjacency entry for this exact direction (e.g. an opposite-direction vehicle
// whose direction isn't among the focused stop's own trips, #1902): underlay still drops, but
// the color falls back to GTFS since there's no adjacency color to carry instead.
assertEquals(
SelectedTripStyle(color = 99, includeUnderlay = false),
selectedTripStyle(stopFocusActive = true, direction, routeColors = emptyMap(), gtfsColor = 99),
)
// Whole-route mode with a stray adjacency entry (shouldn't occur in practice, since routeColors
// is only ever populated during stop focus) still keeps the underlay — it is not proxied off
// the color lookup.
assertEquals(
SelectedTripStyle(color = 10, includeUnderlay = true),
selectedTripStyle(stopFocusActive = false, direction, routeColors = withColor, gtfsColor = 99),
)
// Stop focus with a matching adjacency entry: the ordinary case — adjacency color, no underlay.
assertEquals(
SelectedTripStyle(color = 10, includeUnderlay = false),
selectedTripStyle(stopFocusActive = true, direction, routeColors = withColor, gtfsColor = 99),
)
}

@Test
fun `scheduled stops carry every presented route that serves them`() {
val trips = setOf(
Expand Down