Skip to content

Commit

Permalink
Merge pull request #5187 from HSLdevcom/DT-6564
Browse files Browse the repository at this point in the history
DT-6564 Navigator journey end modal
  • Loading branch information
vesameskanen authored Dec 10, 2024
2 parents 51a013a + 778dce0 commit 3a022a3
Show file tree
Hide file tree
Showing 22 changed files with 721 additions and 281 deletions.
4 changes: 2 additions & 2 deletions app/component/itinerary/ItineraryPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,6 @@ export default function ItineraryPage(props, context) {

const cancelNavigatorUsage = () => {
setNavigation(false);
toggleNavigatorIntro();
};

const walkPlan = altStates[PLANTYPE.WALK][0].plan;
Expand Down Expand Up @@ -1183,12 +1182,13 @@ export default function ItineraryPage(props, context) {
/>
)}
<NaviContainer
itinerary={itineraryForNavigator}
legs={itineraryForNavigator.legs}
focusToLeg={focusToLeg}
relayEnvironment={props.relayEnvironment}
setNavigation={setNavigation}
mapRef={mwtRef.current}
mapLayerRef={mapLayerRef}
isNavigatorIntroDismissed={isNavigatorIntroDismissed}
/>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion app/component/itinerary/navigator/NaviBottom.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function NaviBottom(
) {
const remainingDuration = Math.ceil((arrival - time) / 60000); // ms to minutes
return (
<div className="navibottomsheet">
<div className="navi-bottom-sheet">
<div className="divider" />
<div className="navi-bottom-controls">
<button
Expand Down
103 changes: 55 additions & 48 deletions app/component/itinerary/navigator/NaviCardContainer.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,55 @@
import React, { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { intlShape } from 'react-intl';
import distance from '@digitransit-search-util/digitransit-search-util-distance';
import { matchShape, routerShape } from 'found';
import { legShape, configShape } from '../../../util/shapes';
import PropTypes from 'prop-types';
import React, { useEffect, useRef, useState } from 'react';
import { intlShape } from 'react-intl';
import { legTime, legTimeStr } from '../../../util/legUtils';
import { configShape, legShape } from '../../../util/shapes';
import NaviCard from './NaviCard';
import NaviStack from './NaviStack';
import {
getAdditionalMessages,
getItineraryAlerts,
getTransitLegState,
getAdditionalMessages,
getFirstLastLegs,
LEGTYPE,
} from './NaviUtils';

const DESTINATION_RADIUS = 20; // meters
const TIME_AT_DESTINATION = 3; // * 10 seconds
const TOPBAR_PADDING = 8; // pixels

function getNextLeg(legs, time) {
return legs.find(leg => legTime(leg.start) > time);
}

function addMessages(incominMessages, newMessages) {
newMessages.forEach(m => {
incominMessages.set(m.id, m);
});
}
function NaviCardContainer(
{ focusToLeg, time, legs, position, origin, mapLayerRef },
{
focusToLeg,
time,
legs,
position,
origin,
mapLayerRef,
currentLeg,
nextLeg,
firstLeg,
lastLeg,
isJourneyCompleted,
},
{ intl, config, match, router },
) {
const [currentLeg, setCurrentLeg] = useState(null);
const [cardExpanded, setCardExpanded] = useState(false);
// All notifications including those user has dismissed.
const [messages, setMessages] = useState(new Map());
// notifications that are shown to the user.
const [activeMessages, setActiveMessages] = useState([]);
const [topPosition, setTopPosition] = useState(0);

const legRef = useRef(currentLeg);
const focusRef = useRef(false);
// Destination counter. How long user has been at the destination. * 10 seconds
const destCountRef = useRef(0);
const [topPosition, setTopPosition] = useState(0);
const cardRef = useRef(null);

const handleRemove = index => {
Expand All @@ -61,38 +69,32 @@ function NaviCardContainer(
}, [currentLeg, cardExpanded]);

useEffect(() => {
const newLeg = legs.find(leg => {
return legTime(leg.start) <= time && time <= legTime(leg.end);
});

setCurrentLeg(newLeg);
const incomingMessages = new Map();

const legChanged = legRef.current?.legId
? legRef.current.legId !== currentLeg?.legId
: legRef.current?.mode !== currentLeg?.mode;

if (legChanged) {
legRef.current = currentLeg;
}

// Alerts for NaviStack
addMessages(
incomingMessages,
getItineraryAlerts(legs, intl, messages, match.params, router),
);

const legChanged = newLeg?.legId
? newLeg.legId !== currentLeg?.legId
: currentLeg?.mode !== newLeg?.mode;
const l = currentLeg || newLeg;

if (l) {
const nextLeg = getNextLeg(legs, legTime(l.start));

if (currentLeg) {
if (nextLeg?.transitLeg) {
// Messages for NaviStack.
addMessages(incomingMessages, [
...getTransitLegState(nextLeg, intl, messages, time),
...getAdditionalMessages(nextLeg, time, intl, config, messages),
]);
}
if (newLeg && legChanged) {
focusToLeg?.(newLeg);
}
if (legChanged) {
focusToLeg?.(currentLeg);
setCardExpanded(false);
}
}
Expand Down Expand Up @@ -120,16 +122,13 @@ function NaviCardContainer(

if (!focusRef.current && focusToLeg) {
// handle initial focus when not tracking
if (newLeg) {
focusToLeg(newLeg);
if (currentLeg) {
focusToLeg(currentLeg);
destCountRef.current = 0;
} else if (time < legTime(firstLeg.start)) {
focusToLeg(firstLeg);
} else {
const { first, last } = getFirstLastLegs(legs);
if (time < legTime(first.start)) {
focusToLeg(first);
} else {
focusToLeg(last);
}
focusToLeg(lastLeg);
}
focusRef.current = true;
}
Expand All @@ -147,12 +146,9 @@ function NaviCardContainer(
}
}, [time]);

const { first, last } = getFirstLastLegs(legs);
let legType;
const t = currentLeg ? legTime(currentLeg.start) : time;
const nextLeg = getNextLeg(legs, t);

if (time < legTime(first.start)) {
if (time < legTime(firstLeg.start)) {
legType = LEGTYPE.PENDING;
} else if (currentLeg) {
if (!currentLeg.transitLeg) {
Expand All @@ -164,21 +160,21 @@ function NaviCardContainer(
} else {
legType = LEGTYPE.TRANSIT;
}
} else if (time > legTime(last.end)) {
legType = LEGTYPE.END;
} else {
legType = LEGTYPE.WAIT;
}

const cardTop =
const containerTopPosition =
mapLayerRef.current.getBoundingClientRect().top + TOPBAR_PADDING;

return (
<>
<div
className={`navi-card-container ${isJourneyCompleted ? 'slide-out' : ''}`}
style={{ top: containerTopPosition }}
>
<button
type="button"
className={`navitop ${cardExpanded ? 'expanded' : ''}`}
style={{ top: cardTop }}
onClick={handleClick}
ref={cardRef}
>
Expand All @@ -188,7 +184,7 @@ function NaviCardContainer(
nextLeg={nextLeg}
cardExpanded={cardExpanded}
legType={legType}
startTime={legTimeStr(first.start)}
startTime={legTimeStr(firstLeg.start)}
time={time}
position={position}
origin={origin}
Expand All @@ -202,7 +198,7 @@ function NaviCardContainer(
topPosition={topPosition}
/>
)}
</>
</div>
);
}

Expand All @@ -219,6 +215,12 @@ NaviCardContainer.propTypes = {
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}).isRequired,
currentLeg: legShape,
nextLeg: legShape,
firstLeg: legShape,
lastLeg: legShape,
isJourneyCompleted: PropTypes.bool,

/*
focusToPoint: PropTypes.func.isRequired,
*/
Expand All @@ -227,6 +229,11 @@ NaviCardContainer.propTypes = {
NaviCardContainer.defaultProps = {
focusToLeg: undefined,
position: undefined,
currentLeg: undefined,
nextLeg: undefined,
firstLeg: undefined,
lastLeg: undefined,
isJourneyCompleted: false,
};

NaviCardContainer.contextTypes = {
Expand Down
76 changes: 46 additions & 30 deletions app/component/itinerary/navigator/NaviContainer.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,43 @@
import distance from '@digitransit-search-util/digitransit-search-util-distance';
import PropTypes from 'prop-types';
import React, { useEffect, useState } from 'react';
import polyUtil from 'polyline-encoded';
import { legTime } from '../../../util/legUtils';
import { checkPositioningPermission } from '../../../action/PositionActions';
import { GeodeticToEcef, GeodeticToEnu } from '../../../util/geo-utils';
import { itineraryShape, relayShape } from '../../../util/shapes';
import { legShape, relayShape } from '../../../util/shapes';
import NaviBottom from './NaviBottom';
import NaviCardContainer from './NaviCardContainer';
import { useRealtimeLegs } from './hooks/useRealtimeLegs';
import NavigatorOutroModal from './navigatoroutro/NavigatorOutroModal';

const DESTINATION_RADIUS = 20; // meters
const ADDITIONAL_ARRIVAL_TIME = 60000; // 60 seconds in ms

function NaviContainer(
{
itinerary,
legs,
focusToLeg,
relayEnvironment,
setNavigation,
isNavigatorIntroDismissed,
mapRef,
mapLayerRef,
},
{ getStore },
) {
const [planarLegs, setPlanarLegs] = useState([]);
const [origin, setOrigin] = useState();
const [isPositioningAllowed, setPositioningAllowed] = useState(false);

const position = getStore('PositionStore').getLocationState();

useEffect(() => {
const { lat, lon } = itinerary.legs[0].from;
const orig = GeodeticToEcef(lat, lon);
const legs = itinerary.legs.map(leg => {
const geometry = polyUtil.decode(leg.legGeometry.points);
return {
...leg,
geometry: geometry.map(p => GeodeticToEnu(p[0], p[1], orig)),
};
});
setPlanarLegs(legs);
setOrigin(orig);
}, [itinerary]);
const {
realTimeLegs,
time,
origin,
firstLeg,
lastLeg,
previousLeg,
currentLeg,
nextLeg,
} = useRealtimeLegs(relayEnvironment, legs);

useEffect(() => {
if (position.hasLocation) {
Expand All @@ -54,20 +53,22 @@ function NaviContainer(
}
}, [mapRef]);

const { realTimeLegs, time } = useRealtimeLegs(
planarLegs,
mapRef,
relayEnvironment,
);

if (!realTimeLegs.length) {
if (!realTimeLegs?.length) {
return null;
}

const arrivalTime = legTime(lastLeg.end);

const isDestinationReached =
position && lastLeg && distance(position, lastLeg.to) <= DESTINATION_RADIUS;

const isPastExpectedArrival = time > arrivalTime + ADDITIONAL_ARRIVAL_TIME;

const isJourneyCompleted = isDestinationReached || isPastExpectedArrival;

return (
<>
<NaviCardContainer
itinerary={itinerary}
legs={realTimeLegs}
focusToLeg={
mapRef?.state.mapTracking || isPositioningAllowed ? null : focusToLeg
Expand All @@ -76,21 +77,33 @@ function NaviContainer(
position={position}
mapLayerRef={mapLayerRef}
origin={origin}
currentLeg={time > arrivalTime ? previousLeg : currentLeg}
nextLeg={nextLeg}
firstLeg={firstLeg}
lastLeg={lastLeg}
isJourneyCompleted={isJourneyCompleted}
/>
{isJourneyCompleted && isNavigatorIntroDismissed && (
<NavigatorOutroModal
destination={lastLeg.to.name}
onClose={() => setNavigation(false)}
/>
)}
<NaviBottom
setNavigation={setNavigation}
arrival={legTime(realTimeLegs[realTimeLegs.length - 1].end)}
arrival={arrivalTime}
time={time}
/>
</>
);
}

NaviContainer.propTypes = {
itinerary: itineraryShape.isRequired,
legs: PropTypes.arrayOf(legShape).isRequired,
focusToLeg: PropTypes.func.isRequired,
relayEnvironment: relayShape.isRequired,
setNavigation: PropTypes.func.isRequired,
isNavigatorIntroDismissed: PropTypes.bool,
// eslint-disable-next-line
mapRef: PropTypes.object,
mapLayerRef: PropTypes.func.isRequired,
Expand All @@ -100,6 +113,9 @@ NaviContainer.contextTypes = {
getStore: PropTypes.func.isRequired,
};

NaviContainer.defaultProps = { mapRef: undefined };
NaviContainer.defaultProps = {
mapRef: undefined,
isNavigatorIntroDismissed: false,
};

export default NaviContainer;
Loading

0 comments on commit 3a022a3

Please sign in to comment.