diff --git a/client/src/components/Map/MapViewGL.test.tsx b/client/src/components/Map/MapViewGL.test.tsx index c10a58491..513e1ddc1 100644 --- a/client/src/components/Map/MapViewGL.test.tsx +++ b/client/src/components/Map/MapViewGL.test.tsx @@ -2037,6 +2037,28 @@ describe('MapViewGL', () => { ) }) + it('FE-COMP-MAPVIEWGL-100: the imagery goes under the offered roads too, not just under the route', async () => { + // The roads offered for a leg are added before the route source so they sit beneath the + // current route, which also puts them before the first `trip-` layer. Anchoring the + // imagery on that first `trip-` layer therefore painted it straight over them, and an + // offered road on a satellite basemap could not be seen at all. + loadOnAttach() + useSettingsStore.setState({ + settings: { ...useSettingsStore.getState().settings, map_base_layer: 'satellite' }, + } as never) + glMap.getStyle.mockReturnValue({ + layers: [{ id: 'background' }, { id: 'road' }, { id: 'route-alt-hit' }, { id: 'route-alt-line' }, { id: 'trip-route' }], + }) + + render() + await flushFrames() + + expect(glMap.addLayer).toHaveBeenCalledWith( + expect.objectContaining({ id: 'trip-satellite-raster', type: 'raster' }), + 'route-alt-hit', + ) + }) + it('FE-COMP-MAPVIEWGL-075: with the default basemap no imagery is fetched at all', async () => { loadOnAttach() useSettingsStore.setState({ diff --git a/client/src/components/Map/MapViewGL.tsx b/client/src/components/Map/MapViewGL.tsx index e72b138ef..db8584d28 100644 --- a/client/src/components/Map/MapViewGL.tsx +++ b/client/src/components/Map/MapViewGL.tsx @@ -74,7 +74,11 @@ const GPX_HIT_LAYER_ID = 'trip-gpx-hit' const SATELLITE_SOURCE_ID = 'trip-satellite' const SATELLITE_LAYER_ID = 'trip-satellite-raster' /** Everything TREK draws is prefixed; the imagery is inserted before the first of them. */ -const OWN_LAYER_PREFIXES = ['trip-', 'trek-'] +// Everything TREK draws on a GL map, so the satellite raster can be slipped underneath all +// of it. `route-alt-` earns its place here: the roads offered for a leg are added before the +// route source so they sit under the current route, which also puts them before the first +// `trip-` layer, and anchoring the imagery there painted right over them. +const OWN_LAYER_PREFIXES = ['trip-', 'trek-', 'route-alt-'] type PlaceWithCoords = Place & { lat: number; lng: number } diff --git a/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx b/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx index dd1bc50cf..c784d5385 100644 --- a/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx +++ b/client/src/mobile/screens/trip/roadtrip/MRoadtripRows.tsx @@ -5,6 +5,7 @@ import MIconBtn from '../../../components/MIconBtn' import { formatDurationShort, serviceColor } from '../../../../components/Roadtrip/roadtripModel' import { STOP_KIND_BY_KEY } from '../../../../components/Roadtrip/stopKinds' import { formatDistance } from '../../../../utils/units' +import { formatClockTime } from '../../../../utils/formatters' import type { StopRow } from '../../../../components/Roadtrip/roadtripRowModel' import type { RefuelSearch } from '../../../../components/Roadtrip/useRefuelSearch' import { REFUEL_EMPTY_KEY, REFUEL_WORDS, refuelBandState, type RefuelCandidate } from '../../../../components/Roadtrip/refuelSuggestion' @@ -29,6 +30,15 @@ import type { TranslationFn } from '../../../../types' export interface RowChrome { t: TranslationFn unit: DistanceUnit + /** + * Whether this reader is on a twelve hour clock. + * + * The model hands every clock over as `HH:MM`, which is the shape the schedule computes + * in, and printing that straight to the screen ignored the setting the desktop rail, the + * day timeline and this tab's own sheets all honour. It rides in the chrome rather than + * being read per row: the chain draws dozens of rows and they must not disagree. + */ + is12h: boolean } /** @@ -151,7 +161,7 @@ export function RtStopRow({ row, chrome, onOpen, onPickKind }: { // dir=ltr so a clock reads the same way round in an RTL locale. {row.pinned && } - {row.time} + {formatClockTime(row.time, chrome.is12h)} )} @@ -470,7 +480,7 @@ export function RtAutoRow({ phase, time, chrome }: { {phase === 'end' ? t('roadtrip.window.stop') : t('roadtrip.window.resume')} - {time && {time}} + {time && {formatClockTime(time, chrome.is12h)}} ) } diff --git a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx index c3314e1e7..6d4dca398 100644 --- a/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx +++ b/client/src/mobile/screens/trip/roadtrip/MRoadtripTab.tsx @@ -13,6 +13,7 @@ import { formatDurationShort } from '../../../../components/Roadtrip/roadtripMod import { getNavigationTargets } from '../../../../components/Planner/placeNavigation' import { useSettingsStore } from '../../../../store/settingsStore' import { formatDistance } from '../../../../utils/units' +import { formatClockTime } from '../../../../utils/formatters' import { isRtlLanguage } from '../../../../i18n' import type { MTripTabPanelProps } from '../MTripShell' import { legReroutable, type StopRow } from '../../../../components/Roadtrip/roadtripRowModel' @@ -40,7 +41,8 @@ export default function MRoadtripTab({ planner, shell }: MTripTabPanelProps) { // the map read one controller, so a leg shows pressed for exactly the picker on the map. const alts = useMRtAlternatives(planner, shell) const unit = useSettingsStore(s => s.settings.distance_unit) - const chrome: RowChrome = { t, unit } + const is12h = useSettingsStore(s => s.settings.time_format) === '12h' + const chrome: RowChrome = { t, unit, is12h } // The same gesture the day timeline uses, called rather than rebuilt: 370 lines of // worked-out conflict avoidance, down to the dead 24px gutter that stops iOS from @@ -176,7 +178,7 @@ export default function MRoadtripTab({ planner, shell }: MTripTabPanelProps) { {t('mobileTrip.rtStart')} - {rt.clocks.start ?? '-'} + {rt.clocks.start ? formatClockTime(rt.clocks.start, is12h) : '-'} @@ -184,7 +186,7 @@ export default function MRoadtripTab({ planner, shell }: MTripTabPanelProps) { {t('roadtrip.stay.arrive')} - {rt.clocks.arrive ?? '-'} + {rt.clocks.arrive ? formatClockTime(rt.clocks.arrive, is12h) : '-'} @@ -291,6 +293,7 @@ function UpNext({ planner, shell, rt, stageDayId, onOpen }: { onOpen: (row: StopRow) => void }) { const { t } = planner + const is12h = useSettingsStore(s => s.settings.time_format) === '12h' const next = rt.upNext if (!next) return null const late = next.minutesUntil < 0 @@ -320,7 +323,7 @@ function UpNext({ planner, shell, rt, stageDayId, onOpen }: { {next.row.stop.name} {next.row.time && ( - {next.row.time} + {formatClockTime(next.row.time, is12h)} )}
@@ -332,7 +335,7 @@ function UpNext({ planner, shell, rt, stageDayId, onOpen }: { className={`flex h-11 flex-1 items-center justify-center gap-[7px] rounded-full bg-m-act text-[0.8125rem] font-semibold text-m-actfg shadow-[0_10px_24px_-10px_rgba(0,0,0,.45)] ${targets.length ? '' : 'pointer-events-none opacity-40'}`} >