feat(mobile): a trip opens without its loading screen on the phone and the tablet

The touch shell waited for the desktop's minimum splash of a second and a half after every trip load. It no longer does: while the trip loads it shows only its background, marked as a route fallback, so the page transition holds the dashboard until the trip is there and the trip slides in whole, as the dashboard slides back on the way out. The splash only comes up when a load takes longer than a second. The trip opened before, still in the store for a frame, is never shown for the one opening.
This commit is contained in:
Maurice
2026-11-08 10:33:49 +01:00
parent 3458238db0
commit 0ef3cbf520
3 changed files with 76 additions and 21 deletions
@@ -1,3 +1,4 @@
import { useEffect, useState } from 'react'
import { useTranslation } from '../../../i18n'
import MDancingTrek from '../../components/MDancingTrek'
import { useTripSplashBeats } from '../../../components/shared/useTripSplashBeats'
@@ -10,6 +11,36 @@ import { useTripSplashBeats } from '../../../components/shared/useTripSplashBeat
* progress dots track which beat is on. Under reduced motion it holds a single
* frame (the real "loading photos" one) with no transitions.
*/
/**
* How long a trip may take to open before the splash comes up. The page
* transition holds the page the trip was opened from for up to 800ms
* (mobile/routeTransition.ts), so a trip that loads in that time slides in
* whole, the way the dashboard slides back in on the way out, and the splash is
* only for a slow connection.
*/
export const TRIP_SPLASH_AFTER_MS = 1000
/**
* While a trip opens. At first nothing but the screen's own background, marked
* as a route fallback, which the page transition waits on; the splash only once
* the load takes longer than TRIP_SPLASH_AFTER_MS.
*/
export function MTripOpening({ title }: { title: string }) {
const [slow, setSlow] = useState(false)
useEffect(() => {
const timer = window.setTimeout(() => setSlow(true), TRIP_SPLASH_AFTER_MS)
return () => window.clearTimeout(timer)
}, [])
if (slow) return <MTripLoadingSplash title={title} />
return (
<div
data-route-fallback=""
aria-busy="true"
className="fixed inset-0 z-50 bg-[color:var(--m-bg)] bg-[image:var(--m-scr)]"
/>
)
}
export default function MTripLoadingSplash({ title }: { title: string }) {
const { t } = useTranslation()
const { reduceMotion, activeIndex, step, steps } = useTripSplashBeats({ liveReducedMotion: true })
@@ -16,7 +16,7 @@ import MPlacesBrowser from './places/MPlacesBrowser'
import MTripTabPanel from './tabs/MTripTabPanel'
import MTripSheets from './sheets/MTripSheets'
import MReceiptScanButton from './tabs/MReceiptScanButton'
import MTripLoadingSplash from './MTripLoadingSplash'
import { MTripOpening } from './MTripLoadingSplash'
import { usePluginDayTints, dayTintBackground } from '../../../components/Plugins/PluginDaySchedule'
import { stageOf } from '../../../components/Roadtrip/roadtripRowModel'
import { badgeLabel, distanceBadge } from './roadtrip/stageBadges'
@@ -450,9 +450,11 @@ export default function MTripShell({
// With the plan beside the map, the map shows the day the plan shows: its
// places framed, its route drawn, the other days' pins put away, exactly as
// a phone does when it switches over to the map.
// Not before the splash has gone: the map is not there to frame until then.
const mapReady = !planner.isLoading && planner.splashDone
const followDayId = mapReady && split && trTab === 'plan' && view === 'plan' && mode !== 'browse' ? planner.selectedDayId : null
// Not before the trip has opened: the map is not there to frame until then.
// The store can still hold the trip opened before, for the frame before loadTrip resets it.
const ownTrip = trip && String(trip.id) === String(tripId) ? trip : null
const opened = !planner.isLoading && !!ownTrip
const followDayId = opened && split && trTab === 'plan' && view === 'plan' && mode !== 'browse' ? planner.selectedDayId : null
const { autoShowRoute, setExpandedDayIds } = planner
const selectDayRef = useRef(planner.handleSelectDay)
useEffect(() => {
@@ -521,9 +523,9 @@ export default function MTripShell({
openDocSyncSignal,
}
// Splash — same gate as the desktop page, in the mobile design language.
if (planner.isLoading || !planner.splashDone) {
return <MTripLoadingSplash title={trip?.title || ''} />
// No minimum splash as on the desktop: the page transition hides the wait (MTripOpening).
if (!opened && (planner.isLoading || trip)) {
return <MTripOpening title={ownTrip?.title || ''} />
}
if (!trip) return null