Skip to content

Commit de3e3bf

Browse files
authored
Merge pull request Expensify#90169 from software-mansion-labs/@GCyganek/update-map-markers-and-line
[Payment due @dukenv0307] Update map markers and route styles
2 parents e34c921 + 0dd6b33 commit de3e3bf

19 files changed

Lines changed: 380 additions & 130 deletions

src/CONST/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5013,7 +5013,12 @@ const CONST = {
50135013
PINK: 'Pink',
50145014
},
50155015

5016-
MAP_MARKER_SIZE: 20,
5016+
MAP_MARKER_SIZES: {
5017+
CURRENT_LOCATION: {width: 48, height: 48},
5018+
START_WAYPOINT: {width: 48, height: 48},
5019+
STOP_WAYPOINT: {width: 48, height: 53},
5020+
WAYPOINT: {width: 40, height: 40},
5021+
},
50175022

50185023
QUICK_REACTIONS: [
50195024
{
@@ -9229,6 +9234,7 @@ const CONST = {
92299234
USER_LOCATION: 'user-location',
92309235
ROUTE_SOURCE: 'route-source',
92319236
ROUTE_FILL: 'route-fill',
9237+
ROUTE_BORDER: 'route-border',
92329238
},
92339239

92349240
PARTNER_ID: {

src/components/ConfirmedRoute.tsx

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
import React, {useEffect} from 'react';
22
import type {ReactNode} from 'react';
33
import type {OnyxEntry} from 'react-native-onyx';
4-
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
4+
import useMapMarkers from '@hooks/useMapMarkers';
5+
import type {MapMarkerType} from '@hooks/useMapMarkers/types';
56
import useNetwork from '@hooks/useNetwork';
67
import useOnyx from '@hooks/useOnyx';
78
import useStyleUtils from '@hooks/useStyleUtils';
8-
import useTheme from '@hooks/useTheme';
99
import useThemeStyles from '@hooks/useThemeStyles';
1010
import getArrayDepth from '@libs/getArrayDepth';
1111
import {getWaypointIndex} from '@libs/TransactionUtils';
1212
import {init as initMapboxToken, stop as stopMapboxToken} from '@userActions/MapboxToken';
1313
import CONST from '@src/CONST';
1414
import ONYXKEYS from '@src/ONYXKEYS';
1515
import type {Transaction} from '@src/types/onyx';
16-
import type IconAsset from '@src/types/utils/IconAsset';
1716
import DistanceMapView from './DistanceMapView';
18-
import ImageSVG from './ImageSVG';
1917
import type {WayPoint} from './MapView/MapViewTypes';
2018
import PendingMapView from './MapView/PendingMapView';
2119

@@ -42,10 +40,9 @@ function ConfirmedRoute({transaction, isSmallerIcon, shouldHaveBorderRadius = tr
4240
const {route0: route} = transaction?.routes ?? {};
4341
const waypoints = transaction?.comment?.waypoints ?? {};
4442
const coordinates = route?.geometry?.coordinates ?? [];
45-
const theme = useTheme();
4643
const styles = useThemeStyles();
4744
const StyleUtils = useStyleUtils();
48-
const expensifyIcons = useMemoizedLazyExpensifyIcons(['DotIndicator', 'DotIndicatorUnfilled', 'Location']);
45+
const getMapMarkerIconComponent = useMapMarkers();
4946

5047
const [mapboxAccessToken] = useOnyx(ONYXKEYS.MAPBOX_ACCESS_TOKEN);
5148

@@ -54,15 +51,6 @@ function ConfirmedRoute({transaction, isSmallerIcon, shouldHaveBorderRadius = tr
5451
return stopMapboxToken;
5552
}, []);
5653

57-
const getMarkerComponent = (icon: IconAsset): ReactNode => (
58-
<ImageSVG
59-
src={icon}
60-
width={CONST.MAP_MARKER_SIZE}
61-
height={CONST.MAP_MARKER_SIZE}
62-
fill={theme.icon}
63-
/>
64-
);
65-
6654
const lastWaypointIndex = Object.keys(waypoints).length - 1;
6755
const waypointMarkers: WayPoint[] = [];
6856
for (const [key, waypoint] of Object.entries(waypoints)) {
@@ -71,19 +59,17 @@ function ConfirmedRoute({transaction, isSmallerIcon, shouldHaveBorderRadius = tr
7159
}
7260

7361
const index = getWaypointIndex(key);
74-
let MarkerComponent: IconAsset;
62+
let markerType: MapMarkerType = 'WAYPOINT';
7563
if (index === 0) {
76-
MarkerComponent = expensifyIcons.DotIndicatorUnfilled;
64+
markerType = 'START_WAYPOINT';
7765
} else if (index === lastWaypointIndex) {
78-
MarkerComponent = expensifyIcons.Location;
79-
} else {
80-
MarkerComponent = expensifyIcons.DotIndicator;
66+
markerType = 'STOP_WAYPOINT';
8167
}
8268

8369
waypointMarkers.push({
8470
id: `${waypoint.lng},${waypoint.lat},${index}`,
8571
coordinate: [waypoint.lng, waypoint.lat] as const,
86-
markerComponent: (): ReactNode => getMarkerComponent(MarkerComponent),
72+
markerComponent: (): ReactNode => getMapMarkerIconComponent(markerType),
8773
});
8874
}
8975

@@ -105,6 +91,7 @@ function ConfirmedRoute({transaction, isSmallerIcon, shouldHaveBorderRadius = tr
10591
waypoints={waypointMarkers}
10692
styleURL={CONST.MAPBOX.STYLE_URL}
10793
requireRouteToDisplayMap={requireRouteToDisplayMap}
94+
shouldDisplayCurrentLocation={false}
10895
/>
10996
) : (
11097
<PendingMapView

src/components/DistanceRequest/DistanceRequestFooter.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import type {StyleProp, ViewStyle} from 'react-native';
55
import type {OnyxEntry} from 'react-native-onyx';
66
import Button from '@components/Button';
77
import DistanceMapView from '@components/DistanceMapView';
8-
import ImageSVG from '@components/ImageSVG';
98
import type {WayPoint} from '@components/MapView/MapViewTypes';
109
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
1110
import useLocalize from '@hooks/useLocalize';
11+
import useMapMarkers from '@hooks/useMapMarkers';
12+
import type {MapMarkerType} from '@hooks/useMapMarkers/types';
1213
import useOnyx from '@hooks/useOnyx';
1314
import usePolicy from '@hooks/usePolicy';
14-
import useTheme from '@hooks/useTheme';
1515
import useThemeStyles from '@hooks/useThemeStyles';
1616
import DistanceRequestUtils from '@libs/DistanceRequestUtils';
1717
import {getDistanceInMeters, getWaypointIndex, isCustomUnitRateIDForP2P} from '@libs/TransactionUtils';
@@ -20,7 +20,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
2020
import type {Policy} from '@src/types/onyx';
2121
import type {WaypointCollection} from '@src/types/onyx/Transaction';
2222
import type Transaction from '@src/types/onyx/Transaction';
23-
import type IconAsset from '@src/types/utils/IconAsset';
2423

2524
const MAX_WAYPOINTS = 25;
2625

@@ -42,10 +41,10 @@ type DistanceRequestFooterProps = {
4241
};
4342

4443
function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPage, policy, mapContainerStyle}: DistanceRequestFooterProps) {
45-
const theme = useTheme();
4644
const styles = useThemeStyles();
4745
const {translate} = useLocalize();
48-
const expensifyIcons = useMemoizedLazyExpensifyIcons(['DotIndicator', 'DotIndicatorUnfilled', 'Location', 'Plus']);
46+
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Plus']);
47+
const getMapMarkerIconComponent = useMapMarkers();
4948
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
5049
const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID);
5150
const activePolicy = usePolicy(activePolicyID);
@@ -60,35 +59,24 @@ function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPa
6059
const mileageRate = isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) : defaultMileageRate;
6160
const {unit} = mileageRate ?? {};
6261

63-
const getMarkerComponent = (icon: IconAsset): ReactNode => (
64-
<ImageSVG
65-
src={icon}
66-
width={CONST.MAP_MARKER_SIZE}
67-
height={CONST.MAP_MARKER_SIZE}
68-
fill={theme.icon}
69-
/>
70-
);
71-
7262
const waypointMarkers: WayPoint[] = [];
7363
for (const [key, waypoint] of Object.entries(waypoints ?? {})) {
7464
if (!waypoint?.lat || !waypoint?.lng) {
7565
continue;
7666
}
7767

7868
const index = getWaypointIndex(key);
79-
let MarkerComponent: IconAsset;
69+
let markerType: MapMarkerType = 'WAYPOINT';
8070
if (index === 0) {
81-
MarkerComponent = expensifyIcons.DotIndicatorUnfilled;
71+
markerType = 'START_WAYPOINT';
8272
} else if (index === lastWaypointIndex) {
83-
MarkerComponent = expensifyIcons.Location;
84-
} else {
85-
MarkerComponent = expensifyIcons.DotIndicator;
73+
markerType = 'STOP_WAYPOINT';
8674
}
8775

8876
waypointMarkers.push({
8977
id: `${waypoint.lng},${waypoint.lat},${index}`,
9078
coordinate: [waypoint.lng, waypoint.lat] as const,
91-
markerComponent: (): ReactNode => getMarkerComponent(MarkerComponent),
79+
markerComponent: (): ReactNode => getMapMarkerIconComponent(markerType),
9280
});
9381
}
9482

src/components/Icon/chunks/expensify-icons.chunk.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,12 @@ import Workflows from '@assets/images/workflows.svg';
265265
import Workspace from '@assets/images/workspace-default-avatar.svg';
266266
import Clear from '@assets/images/x-circle.svg';
267267
import Zoom from '@assets/images/zoom.svg';
268+
// These icons are React components because they have
269+
// drop shadow that is NOT handled by babel-plugin-transform-react-native-svg
270+
import MapCurrentLocation from '@components/MapView/Icons/MapCurrentLocation';
271+
import MapStartWaypoint from '@components/MapView/Icons/MapStartWaypoint';
272+
import MapStopWaypoint from '@components/MapView/Icons/MapStopWaypoint';
273+
import MapWaypoint from '@components/MapView/Icons/MapWaypoint';
268274

269275
const Expensicons = {
270276
ReceiptBody,
@@ -396,6 +402,10 @@ const Expensicons = {
396402
Mail,
397403
MakeAdmin,
398404
Map,
405+
MapCurrentLocation,
406+
MapStartWaypoint,
407+
MapStopWaypoint,
408+
MapWaypoint,
399409
Menu,
400410
Meter,
401411
Megaphone,

src/components/MapView/Direction.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ function Direction({coordinates, belowLayerID}: DirectionProps) {
3636
id={`${CONST.MAP_VIEW_LAYERS.ROUTE_FILL}-segment-${index}`}
3737
style={styles.mapDirection}
3838
/>
39+
<Mapbox.LineLayer
40+
belowLayerID={`${CONST.MAP_VIEW_LAYERS.ROUTE_FILL}-segment-${index}`}
41+
id={`${CONST.MAP_VIEW_LAYERS.ROUTE_BORDER}-segment-${index}`}
42+
style={styles.mapDirectionBorder}
43+
/>
3944
</Mapbox.ShapeSource>
4045
))}
4146
</>
@@ -63,6 +68,11 @@ function Direction({coordinates, belowLayerID}: DirectionProps) {
6368
id={CONST.MAP_VIEW_LAYERS.ROUTE_FILL}
6469
style={styles.mapDirection}
6570
/>
71+
<Mapbox.LineLayer
72+
belowLayerID={CONST.MAP_VIEW_LAYERS.ROUTE_FILL}
73+
id={CONST.MAP_VIEW_LAYERS.ROUTE_BORDER}
74+
style={styles.mapDirectionBorder}
75+
/>
6676
</Mapbox.ShapeSource>
6777
);
6878
}

src/components/MapView/Direction.web.tsx

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ function Direction({coordinates}: DirectionProps) {
1414
const styles = useThemeStyles();
1515
const layerLayoutStyle: Record<string, string> = styles.mapDirectionLayer.layout;
1616
const layerPointStyle: Record<string, string | number> = styles.mapDirectionLayer.paint;
17+
const layerBorderLayoutStyle: Record<string, string> = styles.mapDirectionLayerBorder.layout;
18+
const layerBorderPointStyle: Record<string, string | number> = styles.mapDirectionLayerBorder.paint;
1719

1820
if (!utils.isSingleSegmentRoute(coordinates)) {
1921
const validSegments = coordinates.filter((segment) => segment.length >= 2);
@@ -23,31 +25,39 @@ function Direction({coordinates}: DirectionProps) {
2325

2426
return (
2527
<View>
26-
{validSegments.map((segmentCoordinates, index) => (
27-
<Source
28-
// Using index as key is safe because we are not reordering the routes
29-
// eslint-disable-next-line react/no-array-index-key
30-
key={`${CONST.MAP_VIEW_LAYERS.ROUTE_SOURCE}-segment-${index}`}
31-
id={`${CONST.MAP_VIEW_LAYERS.ROUTE_SOURCE}-segment-${index}`}
32-
type="geojson"
33-
data={{
34-
type: 'Feature',
35-
properties: {},
36-
geometry: {
37-
type: 'LineString',
38-
coordinates: segmentCoordinates,
39-
},
40-
}}
41-
>
42-
<Layer
43-
id={`${CONST.MAP_VIEW_LAYERS.ROUTE_FILL}-segment-${index}`}
44-
type="line"
45-
source={`${CONST.MAP_VIEW_LAYERS.ROUTE_SOURCE}-segment-${index}`}
46-
paint={layerPointStyle}
47-
layout={layerLayoutStyle}
48-
/>
49-
</Source>
50-
))}
28+
{validSegments.map((segmentCoordinates, index) => {
29+
const sourceId = `${CONST.MAP_VIEW_LAYERS.ROUTE_SOURCE}-segment-${index}`;
30+
return (
31+
<Source
32+
key={sourceId}
33+
id={sourceId}
34+
type="geojson"
35+
data={{
36+
type: 'Feature',
37+
properties: {},
38+
geometry: {
39+
type: 'LineString',
40+
coordinates: segmentCoordinates,
41+
},
42+
}}
43+
>
44+
<Layer
45+
id={`${CONST.MAP_VIEW_LAYERS.ROUTE_BORDER}-segment-${index}`}
46+
type="line"
47+
source={sourceId}
48+
paint={layerBorderPointStyle}
49+
layout={layerBorderLayoutStyle}
50+
/>
51+
<Layer
52+
id={`${CONST.MAP_VIEW_LAYERS.ROUTE_FILL}-segment-${index}`}
53+
type="line"
54+
source={sourceId}
55+
paint={layerPointStyle}
56+
layout={layerLayoutStyle}
57+
/>
58+
</Source>
59+
);
60+
})}
5161
</View>
5262
);
5363
}
@@ -71,6 +81,13 @@ function Direction({coordinates}: DirectionProps) {
7181
},
7282
}}
7383
>
84+
<Layer
85+
id={CONST.MAP_VIEW_LAYERS.ROUTE_BORDER}
86+
type="line"
87+
source={CONST.MAP_VIEW_LAYERS.ROUTE_SOURCE}
88+
paint={layerBorderPointStyle}
89+
layout={layerBorderLayoutStyle}
90+
/>
7491
<Layer
7592
id={CONST.MAP_VIEW_LAYERS.ROUTE_FILL}
7693
type="line"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, {useId} from 'react';
2+
import {G, Path, Svg} from 'react-native-svg';
3+
import type {SvgProps} from 'react-native-svg';
4+
import MapMarkerShadowFilter from './MapMarkerShadowFilter';
5+
6+
function MapCurrentLocation({width = 48, height = 48}: SvgProps) {
7+
const filterId = useId();
8+
return (
9+
<Svg
10+
width={width}
11+
height={height}
12+
viewBox="0 0 48 48"
13+
fill="none"
14+
>
15+
<MapMarkerShadowFilter
16+
id={filterId}
17+
width="48"
18+
height="48"
19+
/>
20+
<G filter={`url(#${filterId})`}>
21+
<Path
22+
fill="#0185ff"
23+
d="M36 20c0 6.627-5.373 12-12 12s-12-5.373-12-12S17.373 8 24 8s12 5.373 12 12"
24+
/>
25+
<Path
26+
fill="#fcfbf9"
27+
fillRule="evenodd"
28+
clipRule="evenodd"
29+
d="M24 8c6.627 0 12 5.373 12 12s-5.373 12-12 12-12-5.373-12-12S17.373 8 24 8m0 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18"
30+
/>
31+
</G>
32+
</Svg>
33+
);
34+
}
35+
36+
export default MapCurrentLocation;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import {Defs, FeDropShadow, Filter} from 'react-native-svg';
3+
4+
type MapMarkerShadowFilterProps = {
5+
id: string;
6+
width: string;
7+
height: string;
8+
};
9+
10+
function MapMarkerShadowFilter({id, width, height}: MapMarkerShadowFilterProps) {
11+
return (
12+
<Defs>
13+
<Filter
14+
id={id}
15+
x="0"
16+
y="0"
17+
width={width}
18+
height={height}
19+
filterUnits="userSpaceOnUse"
20+
>
21+
<FeDropShadow
22+
dx={0}
23+
dy={4}
24+
stdDeviation={6}
25+
floodColor="#021204"
26+
floodOpacity={0.06}
27+
/>
28+
</Filter>
29+
</Defs>
30+
);
31+
}
32+
33+
export default MapMarkerShadowFilter;

0 commit comments

Comments
 (0)