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 @@ -50,7 +50,6 @@ import org.onebusaway.android.directions.util.TripRequestBuilder
import org.onebusaway.android.notifications.NotificationChannels
import org.onebusaway.android.time.ServerTime
import org.onebusaway.android.time.WallTime
import org.onebusaway.android.ui.nav.NavRoutes
import org.onebusaway.android.ui.tripplan.TripPlanRepository
import java.time.Instant
import kotlin.coroutines.coroutineContext
Expand Down Expand Up @@ -204,8 +203,9 @@ class TripPlanMonitorService : Service() {

/** The low-key ongoing notification required while the foreground service runs. */
private fun buildOngoingNotification(target: Class<*>): Notification {
// Opens HOME (the trip planner is now an on-map directions focus, not a separate destination).
// Restoring the watched trip into directions focus is a deferred follow-up.
val openIntent = Intent(applicationContext, target)
.putExtra(NavRoutes.EXTRA_NAV_ROUTE, NavRoutes.TRIP_PLAN)
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
return NotificationCompat.Builder(this, NotificationChannels.TRIP_PLAN_UPDATES_ID)
.setSmallIcon(R.drawable.ic_bus)
Expand Down Expand Up @@ -239,7 +239,8 @@ class TripPlanMonitorService : Service() {
putExtras(requestExtras)
putExtra(OTPConstants.ITINERARIES, itineraries.toJson())
putExtra(OTPConstants.INTENT_SOURCE, OTPConstants.Source.NOTIFICATION)
putExtra(NavRoutes.EXTRA_NAV_ROUTE, NavRoutes.TRIP_PLAN)
// The request/itinerary bundle rides along for a future restore-into-directions (#1939); the
// intent opens HOME (no EXTRA_NAV_ROUTE) rather than the retired standalone trip-plan screen.
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.onebusaway.android.R
import org.onebusaway.android.directions.model.TripItinerary
import org.onebusaway.android.models.ObaRoute
import org.onebusaway.android.models.ObaStop
import org.onebusaway.android.models.RouteMapDirection
Expand Down Expand Up @@ -89,8 +90,9 @@ data class RouteHeader(
* binds to the host.
*
* Other map screens have their own slim view models over the same building blocks — `StopsMapViewModel`
* (the report + location-picker maps) and `DirectionsMapViewModel` (the trip-plan results map) — so
* they pull in only the controllers they use rather than this whole coordinator.
* (the report + location-picker maps) — so they pull in only the controllers they use rather than this
* whole coordinator. Trip-plan directions now render on this home map too (the [DirectionsMapController]
* session below), rather than a separate results-map view model.
*
* Scoped to the hosting Activity (obtained via `by viewModels()`), so the home screen + the trip-focus
* destination share one model and the rendered state survives a configuration change. Its data
Expand Down Expand Up @@ -215,6 +217,13 @@ class MapViewModel @Inject constructor(
scope = viewModelScope,
)

// The trip-plan directions use case (draws an itinerary's legs + start/end pins). Reused from the
// standalone trip-results map so the home map can render directions itself. `directionsActive` is the
// mode flag (paralleling `routeController.isActive` for route mode); it gates teardown in
// [leaveCurrentView].
private val directionsController = DirectionsMapController(mapHost)
private var directionsActive = false

// Keeps vehicle markers clear of whichever top control anchors route focus: the standalone route
// banner or, for a route selected within stop focus, the always-visible search field.
private val focusBannerMarkerPaddingPx =
Expand Down Expand Up @@ -267,8 +276,8 @@ class MapViewModel @Inject constructor(
// There is no stored "mode" field: [RouteMapController.routeId] is the single source of truth
// for whether a route is shown. Each transition tears down the prior view, persists the intended
// restore view, and starts the new view's loaders. The home bike layer is gated purely by the user's
// layer toggle (directions — the only thing that forces bikes on / filters them — is its own
// DirectionsMapViewModel), so both transitions start it the same way.
// layer toggle; directions — the only thing that forces bikes on / filters them — is handled by the
// [showItinerary] session below, so both nearby/route transitions start the bike layer the same way.

/** Show nearby stops in the current viewport (the default home view). */
fun showNearbyStops() {
Expand Down Expand Up @@ -322,6 +331,11 @@ class MapViewModel @Inject constructor(
*/
private fun leaveCurrentView(clearStopFocus: Boolean) {
if (clearStopFocus) routeController.clearStopFocus()
if (directionsActive) {
directionsController.clear()
renderState.clearRoutePolylines()
directionsActive = false
}
stopsController.stop()
routeController.stop()
bikeController.stop()
Expand Down Expand Up @@ -435,6 +449,35 @@ class MapViewModel @Inject constructor(
}
}

/**
* Draw [itinerary] on the home map (trip-plan directions focus) via the [DirectionsMapController]
* session. The first call leaves the current view (dropping
* stop/route focus + nearby stops) and enters directions mode; later calls (a different option
* selected) just redraw the legs/pins. Deliberately does not restart the nearby-stops loader —
* directions hides nearby stops. Exiting (any other transition → [leaveCurrentView]) tears it down.
*/
fun showItinerary(itinerary: TripItinerary) {
if (!directionsActive) {
leaveCurrentView(clearStopFocus = true)
persistRoute(null)
directionsActive = true
}
directionsController.clear()
renderState.clearRoutePolylines()
directionsController.start(itinerary)
bikeController.start(
directions = true,
selectedBikeStationIds = DirectionsMapController.bikeStationIdsFromItinerary(itinerary),
)
}

/** Clear the drawn itinerary while staying in directions mode (e.g. the plan became unsubmittable). */
fun clearShownItinerary() {
if (!directionsActive) return
directionsController.clear()
renderState.clearRoutePolylines()
}

/** Leave a route selected within stop focus and restore the stop's full adjacency presentation. */
fun clearSelectedRoute() = showNearbyStops()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ import org.onebusaway.android.ui.home.HomeScreen
import org.onebusaway.android.ui.home.HomeViewModel
import org.onebusaway.android.ui.home.HomeNavHost
import org.onebusaway.android.ui.home.HomeDestinationDeps
import org.onebusaway.android.ui.tripplan.TripPlanViewModel
import org.onebusaway.android.ui.tripresults.TripResultsViewModel
import org.onebusaway.android.ui.home.LaunchIntentChannel
import org.onebusaway.android.ui.home.LaunchIntentEffect
import org.onebusaway.android.ui.home.SettingsRehomeEffect
Expand Down Expand Up @@ -110,6 +112,12 @@ class HomeActivity : AppCompatActivity() {
// The help / what's-new / legend dialogs feature module. Activity-scoped.
private val helpViewModel: HelpViewModel by viewModels()

// Trip planner, now hosted on HOME (directions focus) rather than a standalone destination. Activity-
// scoped so the reactive form + results survive config changes while HOME is on screen; TripPlanViewModel
// persists its form via the activity's SavedStateHandle.
private val tripPlanViewModel: TripPlanViewModel by viewModels()
private val tripResultsViewModel: TripResultsViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand Down Expand Up @@ -146,6 +154,8 @@ class HomeActivity : AppCompatActivity() {
donationViewModel = donationViewModel,
weatherViewModel = weatherViewModel,
helpViewModel = helpViewModel,
tripPlanViewModel = tripPlanViewModel,
tripResultsViewModel = tripResultsViewModel,
arrivalsViewModelFactory = arrivalsViewModelFactory,
activityActions = activityActions,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ sealed interface CurrentFocus {
) : CurrentFocus
data class Route(val target: RouteTarget) : CurrentFocus
data class BikeStation(val id: String) : CurrentFocus

/**
* Trip-plan directions mode. A marker with no payload: the itinerary/plan identity lives in
* `TripPlanViewModel`/`TripResultsViewModel` (and persists via their own SavedStateHandle), so
* duplicating it here would create a second source of truth for "which itinerary". This only says
* "the map is in directions mode" — the chrome swaps to the trip-plan form and the map draws the
* itinerary the results VM selects.
*/
data object Directions : CurrentFocus
}

val CurrentFocus.focusedStop: FocusedStop?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal object CurrentFocusPersistence {
private const val FOCUS_STOP = "stop"
private const val FOCUS_ROUTE = "route"
private const val FOCUS_BIKE = "bike"
private const val FOCUS_DIRECTIONS = "directions"
private const val NO_DIRECTION = Int.MIN_VALUE

fun read(state: SavedStateHandle): CurrentFocus {
Expand All @@ -49,6 +50,7 @@ internal object CurrentFocusPersistence {
FOCUS_ROUTE -> readRouteTarget(state)?.let { CurrentFocus.Route(it) } ?: CurrentFocus.None
FOCUS_BIKE -> state.get<String>(KEY_BIKE_STATION)?.let { CurrentFocus.BikeStation(it) }
?: CurrentFocus.None
FOCUS_DIRECTIONS -> CurrentFocus.Directions
FOCUS_NONE -> CurrentFocus.None
else -> readLegacyFocus(state, stop)
}
Expand All @@ -60,6 +62,7 @@ internal object CurrentFocusPersistence {
is CurrentFocus.Stop -> FOCUS_STOP
is CurrentFocus.Route -> FOCUS_ROUTE
is CurrentFocus.BikeStation -> FOCUS_BIKE
CurrentFocus.Directions -> FOCUS_DIRECTIONS
}
val stop = (focus as? CurrentFocus.Stop)?.stop
state[KEY_STOP_ID] = stop?.id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ import org.onebusaway.android.ui.home.help.HelpViewModel
import org.onebusaway.android.ui.home.nav.extraDestinations
import org.onebusaway.android.ui.home.weather.WeatherViewModel
import org.onebusaway.android.ui.mylists.myListsGraph
import org.onebusaway.android.ui.tripplan.TripPlanViewModel
import org.onebusaway.android.ui.tripresults.TripResultsViewModel
import org.onebusaway.android.ui.nav.IntentRouteMapper
import org.onebusaway.android.ui.nav.NavHelp
import org.onebusaway.android.ui.nav.NavRoutes
Expand Down Expand Up @@ -103,6 +105,8 @@ class HomeDestinationDeps(
val donationViewModel: DonationViewModel,
val weatherViewModel: WeatherViewModel,
val helpViewModel: HelpViewModel,
val tripPlanViewModel: TripPlanViewModel,
val tripResultsViewModel: TripResultsViewModel,
val arrivalsViewModelFactory: ArrivalsViewModel.Factory,
val activityActions: HomeActivityActions,
)
Expand Down Expand Up @@ -178,7 +182,12 @@ fun HomeNavHost(
onStarredStops = { menuNav(NavRoutes.HOME_STARRED_STOPS, R.string.analytics_label_button_press_star) },
onStarredRoutes = { menuNav(NavRoutes.HOME_STARRED_ROUTES, R.string.analytics_label_button_press_star) },
onReminders = { menuNav(NavRoutes.MY_REMINDERS, R.string.analytics_label_button_press_reminders) },
onPlanTrip = { menuNav(NavRoutes.TRIP_PLAN, R.string.analytics_label_button_press_trip_plan) },
// Trip planning is now an on-map directions focus (compact form in the top chrome +
// itinerary on the home map), not a separate destination.
onPlanTrip = {
home.homeViewModel.enterDirections(home.mapViewModel.viewport)
home.homeViewModel.reportMenuAnalytics(R.string.analytics_label_button_press_trip_plan)
},
onSettings = { menuNav(NavRoutes.SETTINGS, R.string.analytics_label_button_press_settings) },
onSearch = { query -> navController.navigateFromHome(NavRoutes.search(query)) },
onRecentStopsRoutes = {
Expand Down Expand Up @@ -213,6 +222,8 @@ fun HomeNavHost(
donationViewModel = home.donationViewModel,
weatherViewModel = home.weatherViewModel,
helpViewModel = home.helpViewModel,
tripPlanViewModel = home.tripPlanViewModel,
tripResultsViewModel = home.tripResultsViewModel,
arrivalsViewModelFactory = home.arrivalsViewModelFactory,
callbacks = callbacks,
)
Expand Down
Loading