From 3db593523616974f24f251fc30e1d7d276a1955d Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Fri, 25 Aug 2023 23:13:13 +0400 Subject: [PATCH 01/10] Fix Map Panning behavior --- src/components/DistanceRequest.js | 64 ++++++++++++++++--------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 78b4e7b87c11..cea96c2b2e50 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useMemo, useState} from 'react'; import {ScrollView, View} from 'react-native'; import lodashGet from 'lodash/get'; import _ from 'underscore'; @@ -64,7 +64,7 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { const {isOffline} = useNetwork(); const {translate} = useLocalize(); - const waypoints = lodashGet(transaction, 'comment.waypoints', {}); + const waypoints = useMemo(() => lodashGet(transaction, 'comment.waypoints', {}), [transaction]); const numberOfWaypoints = _.size(waypoints); const lastWaypointIndex = numberOfWaypoints - 1; @@ -74,34 +74,38 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { const haveWaypointsChanged = !_.isEqual(previousWaypoints, waypoints); const shouldFetchRoute = haveWaypointsChanged && !isOffline && !isLoadingRoute && TransactionUtils.validateWaypoints(waypoints); - const waypointMarkers = _.filter( - _.map(waypoints, (waypoint, key) => { - if (!waypoint || waypoint.lng === undefined || waypoint.lat === undefined) { - return; - } - - const index = Number(key.replace('waypoint', '')); - let MarkerComponent; - if (index === 0) { - MarkerComponent = Expensicons.DotIndicatorUnfilled; - } else if (index === lastWaypointIndex) { - MarkerComponent = Expensicons.Location; - } else { - MarkerComponent = Expensicons.DotIndicator; - } - - return { - coordinate: [waypoint.lng, waypoint.lat], - markerComponent: () => ( - - ), - }; - }), - (waypoint) => waypoint, + const waypointMarkers = useMemo( + () => + _.filter( + _.map(waypoints, (waypoint, key) => { + if (!waypoint || waypoint.lng === undefined || waypoint.lat === undefined) { + return; + } + + const index = Number(key.replace('waypoint', '')); + let MarkerComponent; + if (index === 0) { + MarkerComponent = Expensicons.DotIndicatorUnfilled; + } else if (index === lastWaypointIndex) { + MarkerComponent = Expensicons.Location; + } else { + MarkerComponent = Expensicons.DotIndicator; + } + + return { + coordinate: [waypoint.lng, waypoint.lat], + markerComponent: () => ( + + ), + }; + }), + (waypoint) => waypoint, + ), + [waypoints, lastWaypointIndex], ); // Show up to the max number of waypoints plus 1/2 of one to hint at scrolling From 1dbc1192a60620cd9dcf8071e807bad2b237e3a2 Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Fri, 25 Aug 2023 23:47:56 +0400 Subject: [PATCH 02/10] Add screen focus state props to MapView Added new props to avoid introducing unnecessary dependencies ('@react-navigation/native') to the 'react-native-x-maps' library. --- src/components/DistanceRequest.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index cea96c2b2e50..7d0ef8c8bd57 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -27,6 +27,7 @@ import ScreenWrapper from './ScreenWrapper'; import DotIndicatorMessage from './DotIndicatorMessage'; import * as ErrorUtils from '../libs/ErrorUtils'; import usePrevious from '../hooks/usePrevious'; +import { useIsFocused } from '@react-navigation/native'; const MAX_WAYPOINTS = 25; const MAX_WAYPOINTS_TO_DISPLAY = 4; @@ -63,7 +64,7 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { const [scrollContentHeight, setScrollContentHeight] = useState(0); const {isOffline} = useNetwork(); const {translate} = useLocalize(); - + const isFocused = useIsFocused() const waypoints = useMemo(() => lodashGet(transaction, 'comment.waypoints', {}), [transaction]); const numberOfWaypoints = _.size(waypoints); @@ -211,6 +212,7 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { {!isOffline && Boolean(mapboxAccessToken.token) ? ( Date: Wed, 30 Aug 2023 11:42:23 +0400 Subject: [PATCH 03/10] feat: Move code from react-native-x-maps to the App --- config/webpack/webpack.common.js | 2 +- package-lock.json | 19 ----- package.json | 1 - src/CONST.js | 5 ++ src/components/DistanceRequest.js | 43 +++++----- src/components/MapView/Direction.tsx | 32 +++++++ src/components/MapView/Direction.web.tsx | 40 +++++++++ src/components/MapView/MapView.tsx | 96 +++++++++++++++++++++ src/components/MapView/MapView.web.tsx | 103 +++++++++++++++++++++++ src/components/MapView/MapViewTypes.ts | 59 +++++++++++++ src/components/MapView/index.js | 5 ++ src/components/MapView/utils.ts | 13 +++ 12 files changed, 377 insertions(+), 41 deletions(-) create mode 100644 src/components/MapView/Direction.tsx create mode 100644 src/components/MapView/Direction.web.tsx create mode 100644 src/components/MapView/MapView.tsx create mode 100644 src/components/MapView/MapView.web.tsx create mode 100644 src/components/MapView/MapViewTypes.ts create mode 100644 src/components/MapView/index.js create mode 100644 src/components/MapView/utils.ts diff --git a/config/webpack/webpack.common.js b/config/webpack/webpack.common.js index f3c02b286623..7dc851c95c9e 100644 --- a/config/webpack/webpack.common.js +++ b/config/webpack/webpack.common.js @@ -194,7 +194,7 @@ const webpackConfig = ({envFile = '.env', platform = 'web'}) => ({ // This is also why we have to use .website.js for our own web-specific files... // Because desktop also relies on "web-specific" module implementations // This also skips packing web only dependencies to desktop and vice versa - extensions: ['.web.js', platform === 'web' ? '.website.js' : '.desktop.js', '.js', '.jsx', '.web.ts', platform === 'web' ? '.website.ts' : '.desktop.ts', '.ts', '.tsx'], + extensions: ['.web.js', platform === 'web' ? '.website.js' : '.desktop.js', '.js', '.jsx', '.web.ts', platform === 'web' ? '.website.ts' : '.desktop.ts', '.ts', '.web.tsx', '.tsx'], fallback: { 'process/browser': require.resolve('process/browser'), }, diff --git a/package-lock.json b/package-lock.json index bad7696a36a5..fc2769a360e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -106,7 +106,6 @@ "react-native-web-linear-gradient": "^1.1.2", "react-native-web-lottie": "^1.4.4", "react-native-webview": "^11.17.2", - "react-native-x-maps": "1.0.10", "react-pdf": "^6.2.2", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", @@ -43442,18 +43441,6 @@ "node": ">=8" } }, - "node_modules/react-native-x-maps": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/react-native-x-maps/-/react-native-x-maps-1.0.10.tgz", - "integrity": "sha512-jBRl5JzP3QmGY6tj5CR9UwbREZ3tnuSa7puZozai3bRFrN68k3W6x1p6O8SGp91VvcQlaqJUPFZ+bkYiY3XRvA==", - "peerDependencies": { - "@rnmapbox/maps": "^10.0.11", - "mapbox-gl": "^2.15.0", - "react": "^18.2.0", - "react-map-gl": "^7.1.3", - "react-native": "^0.72.3" - } - }, "node_modules/react-native/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -80285,12 +80272,6 @@ } } }, - "react-native-x-maps": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/react-native-x-maps/-/react-native-x-maps-1.0.10.tgz", - "integrity": "sha512-jBRl5JzP3QmGY6tj5CR9UwbREZ3tnuSa7puZozai3bRFrN68k3W6x1p6O8SGp91VvcQlaqJUPFZ+bkYiY3XRvA==", - "requires": {} - }, "react-pdf": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-6.2.2.tgz", diff --git a/package.json b/package.json index a6f79c4a8cd4..9b521caaa16e 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,6 @@ "react-native-web-linear-gradient": "^1.1.2", "react-native-web-lottie": "^1.4.4", "react-native-webview": "^11.17.2", - "react-native-x-maps": "1.0.10", "react-pdf": "^6.2.2", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", diff --git a/src/CONST.js b/src/CONST.js index e86a9c4660da..6ad2c90f8d06 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -2601,6 +2601,11 @@ const CONST = { SAASTR: 'SaaStrDemoSetup', SBE: 'SbeDemoSetup', }, + MAP_VIEW: { + PADDING: 50, + DEFAULT_ZOOM: 10, + DEFAULT_COORDINATE: [-122.4021, 37.7911], + }, }; export default CONST; diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 7d0ef8c8bd57..49d3ec5be2df 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -1,33 +1,38 @@ import React, {useEffect, useMemo, useState} from 'react'; import {ScrollView, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; -import _ from 'underscore'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import MapView from 'react-native-x-maps'; +import _ from 'underscore'; + +import CONST from '../CONST'; +import ROUTES from '../ROUTES'; import ONYXKEYS from '../ONYXKEYS'; -import * as Transaction from '../libs/actions/Transaction'; -import * as TransactionUtils from '../libs/TransactionUtils'; -import MenuItemWithTopDescription from './MenuItemWithTopDescription'; -import * as Expensicons from './Icon/Expensicons'; -import theme from '../styles/themes/default'; -import Button from './Button'; + import styles from '../styles/styles'; import variables from '../styles/variables'; -import LinearGradient from './LinearGradient'; -import * as MapboxToken from '../libs/actions/MapboxToken'; -import CONST from '../CONST'; -import BlockingView from './BlockingViews/BlockingView'; +import theme from '../styles/themes/default'; + +import transactionPropTypes from './transactionPropTypes'; + import useNetwork from '../hooks/useNetwork'; +import usePrevious from '../hooks/usePrevious'; import useLocalize from '../hooks/useLocalize'; + +import * as ErrorUtils from '../libs/ErrorUtils'; import Navigation from '../libs/Navigation/Navigation'; -import ROUTES from '../ROUTES'; -import transactionPropTypes from './transactionPropTypes'; +import * as MapboxToken from '../libs/actions/MapboxToken'; +import * as Transaction from '../libs/actions/Transaction'; +import * as TransactionUtils from '../libs/TransactionUtils'; + +import Button from './Button'; +import MapView from './MapView'; import ScreenWrapper from './ScreenWrapper'; +import LinearGradient from './LinearGradient'; +import * as Expensicons from './Icon/Expensicons'; +import BlockingView from './BlockingViews/BlockingView'; import DotIndicatorMessage from './DotIndicatorMessage'; -import * as ErrorUtils from '../libs/ErrorUtils'; -import usePrevious from '../hooks/usePrevious'; -import { useIsFocused } from '@react-navigation/native'; +import MenuItemWithTopDescription from './MenuItemWithTopDescription'; const MAX_WAYPOINTS = 25; const MAX_WAYPOINTS_TO_DISPLAY = 4; @@ -64,7 +69,6 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { const [scrollContentHeight, setScrollContentHeight] = useState(0); const {isOffline} = useNetwork(); const {translate} = useLocalize(); - const isFocused = useIsFocused() const waypoints = useMemo(() => lodashGet(transaction, 'comment.waypoints', {}), [transaction]); const numberOfWaypoints = _.size(waypoints); @@ -212,7 +216,6 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { {!isOffline && Boolean(mapboxAccessToken.token) ? ( + + + ); +} + +export default Direction; diff --git a/src/components/MapView/Direction.web.tsx b/src/components/MapView/Direction.web.tsx new file mode 100644 index 000000000000..deb02a41b88f --- /dev/null +++ b/src/components/MapView/Direction.web.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import {Layer, Source} from 'react-map-gl'; +import {View} from 'react-native'; +import {DirectionProps} from './MapViewTypes'; + +function Direction({coordinates, directionStyle}: DirectionProps) { + if (coordinates.length < 1) { + return null; + } + return ( + + {coordinates && ( + + + + )} + + ); +} + +export default Direction; diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx new file mode 100644 index 000000000000..b7b209019323 --- /dev/null +++ b/src/components/MapView/MapView.tsx @@ -0,0 +1,96 @@ +import {View} from 'react-native'; +import {useIsFocused} from '@react-navigation/native'; +import Mapbox, {MapState, MarkerView, setAccessToken} from '@rnmapbox/maps'; +import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'; + +import utils from './utils'; +import Direction from './Direction'; +import CONST from '../../CONST'; + +import {MapViewProps, MapViewHandle} from './MapViewTypes'; + +const MapView = forwardRef(({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, directionStyle}, ref) => { + const isFocused = useIsFocused(); + const cameraRef = useRef(null); + const [isIdle, setIsIdle] = useState(false); + + useImperativeHandle( + ref, + () => ({ + flyTo: (location: [number, number], zoomLevel: number = CONST.MAP_VIEW.DEFAULT_ZOOM, animationDuration?: number) => + cameraRef.current?.setCamera({zoomLevel, centerCoordinate: location, animationDuration}), + fitBounds: (ne: [number, number], sw: [number, number], paddingConfig?: number | number[] | undefined, animationDuration?: number | undefined) => + cameraRef.current?.fitBounds(ne, sw, paddingConfig, animationDuration), + }), + [], + ); + + useEffect(() => { + if (isFocused) return; + setIsIdle(false); + }, [isFocused]); + + useEffect(() => { + if (!waypoints?.length || !isIdle || !isFocused) return; + + if (waypoints.length === 1) { + cameraRef.current?.setCamera({ + zoomLevel: 15, + centerCoordinate: waypoints[0].coordinate, + }); + } else { + const {southWest, northEast} = utils.getBounds(waypoints.map((waypoint) => waypoint.coordinate)); + cameraRef.current?.fitBounds(northEast, southWest, mapPadding, 1000); + } + }, [mapPadding, waypoints, isFocused, isIdle]); + + useEffect(() => { + setAccessToken(accessToken); + }, [accessToken]); + + const setMapIdle = (e: MapState) => { + if (e.gestures.isGestureActive) return; + setIsIdle(true); + }; + + return ( + + + + + {waypoints?.map(({coordinate, markerComponent}) => { + const MarkerComponent = markerComponent; + return ( + + + + ); + })} + + {directionCoordinates && ( + + )} + + + ); +}); + +export default MapView; diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx new file mode 100644 index 000000000000..b7880807ae81 --- /dev/null +++ b/src/components/MapView/MapView.web.tsx @@ -0,0 +1,103 @@ +import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useState} from 'react'; +import {View} from 'react-native'; +import Map, {MapRef, Marker} from 'react-map-gl'; + +import utils from './utils'; + +import CONST from '../../CONST'; +import Direction from './Direction'; +import {MapViewHandle, MapViewProps} from './MapViewTypes'; + +import 'mapbox-gl/dist/mapbox-gl.css'; + +const MapView = forwardRef( + ( + { + style, + styleURL, + waypoints, + mapPadding, + accessToken, + directionStyle, + directionCoordinates, + initialState = {location: CONST.MAP_VIEW.DEFAULT_COORDINATE, zoom: CONST.MAP_VIEW.DEFAULT_ZOOM}, + }, + ref, + ) => { + const [mapRef, setMapRef] = useState(null); + const setRef = useCallback((newRef: MapRef | null) => setMapRef(newRef), []); + + useEffect(() => { + if (!waypoints || waypoints.length === 0) { + return; + } + + if (!mapRef) { + return; + } + + if (waypoints.length === 1) { + mapRef.flyTo({ + center: waypoints[0].coordinate, + zoom: 15, + }); + return; + } + + const map = mapRef.getMap(); + + const {northEast, southWest} = utils.getBounds(waypoints.map((waypoint) => waypoint.coordinate)); + map.fitBounds([northEast, southWest], {padding: mapPadding}); + }, [waypoints, mapRef, mapPadding]); + + useImperativeHandle( + ref, + () => ({ + flyTo: (location: [number, number], zoomLevel: number = CONST.MAP_VIEW.DEFAULT_ZOOM, animationDuration?: number) => + mapRef?.flyTo({ + center: location, + zoom: zoomLevel, + duration: animationDuration, + }), + fitBounds: (ne: [number, number], sw: [number, number]) => mapRef?.fitBounds([ne, sw]), + }), + [mapRef], + ); + + return ( + + + {waypoints?.map(({coordinate, markerComponent}) => { + const MarkerComponent = markerComponent; + return ( + + + + ); + })} + {directionCoordinates && ( + + )} + + + ); + }, +); + +export default MapView; diff --git a/src/components/MapView/MapViewTypes.ts b/src/components/MapView/MapViewTypes.ts new file mode 100644 index 000000000000..d6a7d93bc70d --- /dev/null +++ b/src/components/MapView/MapViewTypes.ts @@ -0,0 +1,59 @@ +import {ComponentType} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; + +type MapViewProps = { + // Public access token to be used to fetch map data from Mapbox. + accessToken: string; + // Style applied to MapView component. Note some of the View Style props are not available on ViewMap + style: StyleProp; + // Link to the style JSON document. + styleURL?: string; + // Whether map can tilt in the vertical direction. + pitchEnabled?: boolean; + // Padding to apply when the map is adjusted to fit waypoints and directions + mapPadding?: number; + // Initial coordinate and zoom level + initialState?: InitialState; + // Locations on which to put markers + waypoints?: WayPoint[]; + // List of coordinates which together forms a direction. + directionCoordinates?: Array<[number, number]>; + // Style used for the line that displays direction + directionStyle?: DirectionStyle; +}; + +type DirectionProps = { + // Coordinates of points that constitute the direction + coordinates: Array<[number, number]>; + // Style used for the line that displays direction + directionStyle?: DirectionStyle; +}; + +// Initial state of the map +type InitialState = { + // Coordinate on which to center the map + location: [number, number]; + zoom: number; +}; + +// Waypoint to be displayed on the map +type WayPoint = { + coordinate: [number, number]; + markerComponent: ComponentType; +}; + +// Style used for the line that displays direction +type DirectionStyle = { + width?: number; + color?: string; +}; + +// Represents a handle to interact with a map view. +type MapViewHandle = { + // Fly to a location on the map + flyTo: (location: [number, number], zoomLevel: number, animationDuration?: number) => void; + // Fit the map view to a bounding box + fitBounds: (ne: [number, number], sw: [number, number], paddingConfig?: number | number[], animationDuration?: number) => void; +}; + +export type {DirectionStyle, WayPoint, MapViewProps, DirectionProps, MapViewHandle}; diff --git a/src/components/MapView/index.js b/src/components/MapView/index.js new file mode 100644 index 000000000000..5bdd71f91959 --- /dev/null +++ b/src/components/MapView/index.js @@ -0,0 +1,5 @@ +import MapView from './MapView'; + +export {MapViewProps, MapViewHandle} from './MapViewTypes'; + +export default MapView; diff --git a/src/components/MapView/utils.ts b/src/components/MapView/utils.ts new file mode 100644 index 000000000000..c37d272296e5 --- /dev/null +++ b/src/components/MapView/utils.ts @@ -0,0 +1,13 @@ +function getBounds(waypoints: Array<[number, number]>): {southWest: [number, number]; northEast: [number, number]} { + const lngs = waypoints.map((waypoint) => waypoint[0]); + const lats = waypoints.map((waypoint) => waypoint[1]); + + return { + southWest: [Math.min(...lngs), Math.min(...lats)], + northEast: [Math.max(...lngs), Math.max(...lats)], + }; +} + +export default { + getBounds, +}; From 2405b2c275a8ffafe972c300eee904066b24cb47 Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Wed, 30 Aug 2023 11:57:33 +0400 Subject: [PATCH 04/10] fix: rename mapbox consts --- src/CONST.ts | 5 +++-- src/components/DistanceRequest.js | 11 ++++------- src/components/MapView/MapView.tsx | 2 +- src/components/MapView/MapView.web.tsx | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 89826aba6390..d8004226430b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2594,7 +2594,6 @@ const CONST = { SF_COORDINATES: [-122.4194, 37.7749], - MAPBOX_STYLE_URL: 'mapbox://styles/expensify/cllcoiqds00cs01r80kp34tmq', NAVIGATION: { TYPE: { FORCED_UP: 'FORCED_UP', @@ -2611,10 +2610,12 @@ const CONST = { SAASTR: 'SaaStrDemoSetup', SBE: 'SbeDemoSetup', }, - MAP_VIEW: { + + MAPBOX: { PADDING: 50, DEFAULT_ZOOM: 10, DEFAULT_COORDINATE: [-122.4021, 37.7911], + STYLE_URL: 'mapbox://styles/expensify/cllcoiqds00cs01r80kp34tmq', }, } as const; diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 96af89d50173..2307628c79f3 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -36,9 +36,6 @@ import MenuItemWithTopDescription from './MenuItemWithTopDescription'; const MAX_WAYPOINTS = 25; const MAX_WAYPOINTS_TO_DISPLAY = 4; -const MAP_PADDING = 50; -const DEFAULT_ZOOM_LEVEL = 10; - const propTypes = { /** The transactionID of this request */ transactionID: PropTypes.string, @@ -216,17 +213,17 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { {!isOffline && Boolean(mapboxAccessToken.token) ? ( ) : ( diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index b7b209019323..646c48188509 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -17,7 +17,7 @@ const MapView = forwardRef(({accessToken, style, ma useImperativeHandle( ref, () => ({ - flyTo: (location: [number, number], zoomLevel: number = CONST.MAP_VIEW.DEFAULT_ZOOM, animationDuration?: number) => + flyTo: (location: [number, number], zoomLevel: number = CONST.MAPBOX.DEFAULT_ZOOM, animationDuration?: number) => cameraRef.current?.setCamera({zoomLevel, centerCoordinate: location, animationDuration}), fitBounds: (ne: [number, number], sw: [number, number], paddingConfig?: number | number[] | undefined, animationDuration?: number | undefined) => cameraRef.current?.fitBounds(ne, sw, paddingConfig, animationDuration), diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx index b7880807ae81..9dbf66786801 100644 --- a/src/components/MapView/MapView.web.tsx +++ b/src/components/MapView/MapView.web.tsx @@ -20,7 +20,7 @@ const MapView = forwardRef( accessToken, directionStyle, directionCoordinates, - initialState = {location: CONST.MAP_VIEW.DEFAULT_COORDINATE, zoom: CONST.MAP_VIEW.DEFAULT_ZOOM}, + initialState = {location: CONST.MAPBOX.DEFAULT_COORDINATE, zoom: CONST.MAPBOX.DEFAULT_ZOOM}, }, ref, ) => { @@ -53,7 +53,7 @@ const MapView = forwardRef( useImperativeHandle( ref, () => ({ - flyTo: (location: [number, number], zoomLevel: number = CONST.MAP_VIEW.DEFAULT_ZOOM, animationDuration?: number) => + flyTo: (location: [number, number], zoomLevel: number = CONST.MAPBOX.DEFAULT_ZOOM, animationDuration?: number) => mapRef?.flyTo({ center: location, zoom: zoomLevel, From 56390672b012b0d8774628972607b361d244ab1e Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Wed, 30 Aug 2023 20:38:50 +0400 Subject: [PATCH 05/10] fix: Fix app crashing on waypoint delete --- src/libs/actions/Transaction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/Transaction.js b/src/libs/actions/Transaction.js index 1dad0219db1a..d1b3026047f6 100644 --- a/src/libs/actions/Transaction.js +++ b/src/libs/actions/Transaction.js @@ -93,6 +93,7 @@ function saveWaypoint(transactionID, index, waypoint) { }, }, }); + if (!waypoint) return; const recentWaypointAlreadyExists = _.find(recentWaypoints, (recentWaypoint) => recentWaypoint.address === waypoint.address); if (!recentWaypointAlreadyExists) { const clonedWaypoints = _.clone(recentWaypoints); From cb6cb07570355d00b12d8ad237d6ca88b9b07b8f Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Wed, 30 Aug 2023 20:50:38 +0400 Subject: [PATCH 06/10] Remove unnecessary re exports --- src/components/MapView/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/MapView/index.js b/src/components/MapView/index.js index 5bdd71f91959..551f57e34ed2 100644 --- a/src/components/MapView/index.js +++ b/src/components/MapView/index.js @@ -1,5 +1,3 @@ import MapView from './MapView'; -export {MapViewProps, MapViewHandle} from './MapViewTypes'; - export default MapView; From 2fbff6961b31ba2f9a335c4caa6c4160d1093962 Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Thu, 31 Aug 2023 21:42:39 +0400 Subject: [PATCH 07/10] Fix react duplicate keys #35 --- src/components/DistanceRequest.js | 1 + src/components/MapView/MapView.tsx | 11 ++++++----- src/components/MapView/MapView.web.tsx | 4 ++-- src/components/MapView/MapViewTypes.ts | 1 + 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 2307628c79f3..2c0d39efad8a 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -94,6 +94,7 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { } return { + id: `${waypoint.lng},${waypoint.lat},${index}`, coordinate: [waypoint.lng, waypoint.lat], markerComponent: () => ( (({accessToken, style, ma () => ({ flyTo: (location: [number, number], zoomLevel: number = CONST.MAPBOX.DEFAULT_ZOOM, animationDuration?: number) => cameraRef.current?.setCamera({zoomLevel, centerCoordinate: location, animationDuration}), - fitBounds: (ne: [number, number], sw: [number, number], paddingConfig?: number | number[] | undefined, animationDuration?: number | undefined) => - cameraRef.current?.fitBounds(ne, sw, paddingConfig, animationDuration), + fitBounds: (northEast: [number, number], southWest: [number, number], paddingConfig?: number | number[] | undefined, animationDuration?: number | undefined) => + cameraRef.current?.fitBounds(northEast, southWest, paddingConfig, animationDuration), }), [], ); @@ -36,6 +36,7 @@ const MapView = forwardRef(({accessToken, style, ma if (waypoints.length === 1) { cameraRef.current?.setCamera({ zoomLevel: 15, + animationDuration: 1000, centerCoordinate: waypoints[0].coordinate, }); } else { @@ -69,12 +70,12 @@ const MapView = forwardRef(({accessToken, style, ma }} /> - {waypoints?.map(({coordinate, markerComponent}) => { + {waypoints?.map(({coordinate, markerComponent, id}) => { const MarkerComponent = markerComponent; return ( diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx index 9dbf66786801..da56d4399d0e 100644 --- a/src/components/MapView/MapView.web.tsx +++ b/src/components/MapView/MapView.web.tsx @@ -76,11 +76,11 @@ const MapView = forwardRef( }} mapStyle={styleURL} > - {waypoints?.map(({coordinate, markerComponent}) => { + {waypoints?.map(({coordinate, markerComponent, id}) => { const MarkerComponent = markerComponent; return ( diff --git a/src/components/MapView/MapViewTypes.ts b/src/components/MapView/MapViewTypes.ts index d6a7d93bc70d..aa2f47257a16 100644 --- a/src/components/MapView/MapViewTypes.ts +++ b/src/components/MapView/MapViewTypes.ts @@ -38,6 +38,7 @@ type InitialState = { // Waypoint to be displayed on the map type WayPoint = { + id: string; coordinate: [number, number]; markerComponent: ComponentType; }; From 77867ce3fccdef36058e99161e85325752b0a521 Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Thu, 31 Aug 2023 23:05:19 +0400 Subject: [PATCH 08/10] Fix Not Found Page appearing issue and add comment --- src/components/DistanceRequest.js | 1 - src/components/MapView/Direction.tsx | 8 +++---- src/components/MapView/Direction.web.tsx | 20 ++++++++++++----- src/components/MapView/MapView.tsx | 13 +++++------ src/components/MapView/MapView.web.tsx | 28 +++++++----------------- src/components/MapView/MapViewTypes.ts | 4 ---- src/pages/iou/WaypointEditor.js | 4 +++- src/styles/styles.js | 9 ++++++-- 8 files changed, 41 insertions(+), 46 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 2c0d39efad8a..2f31a3b6ba48 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -221,7 +221,6 @@ function DistanceRequest({transactionID, transaction, mapboxAccessToken}) { location: CONST.MAPBOX.DEFAULT_COORDINATE, }} directionCoordinates={lodashGet(transaction, 'routes.route0.geometry.coordinates', [])} - directionStyle={styles.mapDirection} style={styles.mapView} waypoints={waypointMarkers} styleURL={CONST.MAPBOX.STYLE_URL} diff --git a/src/components/MapView/Direction.tsx b/src/components/MapView/Direction.tsx index f6f67954c185..920a3912dca4 100644 --- a/src/components/MapView/Direction.tsx +++ b/src/components/MapView/Direction.tsx @@ -1,7 +1,8 @@ import Mapbox from '@rnmapbox/maps'; import {DirectionProps} from './MapViewTypes'; +import styles from '../../styles/styles'; -function Direction({coordinates, directionStyle}: DirectionProps) { +function Direction({coordinates}: DirectionProps) { if (coordinates.length < 1) { return null; } @@ -20,10 +21,7 @@ function Direction({coordinates, directionStyle}: DirectionProps) { > ); diff --git a/src/components/MapView/Direction.web.tsx b/src/components/MapView/Direction.web.tsx index deb02a41b88f..190ecb03cb5e 100644 --- a/src/components/MapView/Direction.web.tsx +++ b/src/components/MapView/Direction.web.tsx @@ -1,9 +1,19 @@ +// Explanation: Different Mapbox libraries are required for web and native mobile platforms. +// This is why we have separate components for web and native to handle the specific implementations. +// For the web version, we use the Mapbox Web library called react-map-gl, while for the native mobile version, +// we utilize a different Mapbox library @rnmapbox/maps tailored for mobile development. + import React from 'react'; -import {Layer, Source} from 'react-map-gl'; import {View} from 'react-native'; +import {Layer, Source} from 'react-map-gl'; import {DirectionProps} from './MapViewTypes'; -function Direction({coordinates, directionStyle}: DirectionProps) { +import styles from '../../styles/styles'; + +function Direction({coordinates}: DirectionProps) { + const layerLayoutStyle: Record = styles.mapDirectionLayer.layout; + const layerPointStyle: Record = styles.mapDirectionLayer.paint; + if (coordinates.length < 1) { return null; } @@ -26,10 +36,8 @@ function Direction({coordinates, directionStyle}: DirectionProps) { id="route" type="line" source="route" - // eslint-disable-next-line @typescript-eslint/naming-convention - layout={{'line-join': 'round', 'line-cap': 'round'}} - // eslint-disable-next-line @typescript-eslint/naming-convention - paint={{'line-color': directionStyle?.color ?? '#000000', 'line-width': directionStyle?.width ?? 1}} + paint={layerPointStyle} + layout={layerLayoutStyle} /> )} diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index 220febdcb346..b001529c805a 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -9,7 +9,7 @@ import CONST from '../../CONST'; import {MapViewProps, MapViewHandle} from './MapViewTypes'; -const MapView = forwardRef(({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, directionStyle}, ref) => { +const MapView = forwardRef(({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates}, ref) => { const isFocused = useIsFocused(); const cameraRef = useRef(null); const [isIdle, setIsIdle] = useState(false); @@ -25,6 +25,10 @@ const MapView = forwardRef(({accessToken, style, ma [], ); + // When the page loses focus, we temporarily set the "idled" state to false. + // When the page regains focus, the onIdled method of the map will set the actual "idled" state, + // which in turn triggers the callback of the next useEffect. + // If map not idled, camera transitions not working properly. useEffect(() => { if (isFocused) return; setIsIdle(false); @@ -83,12 +87,7 @@ const MapView = forwardRef(({accessToken, style, ma ); })} - {directionCoordinates && ( - - )} + {directionCoordinates && } ); diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx index da56d4399d0e..446d375da318 100644 --- a/src/components/MapView/MapView.web.tsx +++ b/src/components/MapView/MapView.web.tsx @@ -1,3 +1,8 @@ +// Explanation: Different Mapbox libraries are required for web and native mobile platforms. +// This is why we have separate components for web and native to handle the specific implementations. +// For the web version, we use the Mapbox Web library called react-map-gl, while for the native mobile version, +// we utilize a different Mapbox library @rnmapbox/maps tailored for mobile development. + import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useState} from 'react'; import {View} from 'react-native'; import Map, {MapRef, Marker} from 'react-map-gl'; @@ -11,19 +16,7 @@ import {MapViewHandle, MapViewProps} from './MapViewTypes'; import 'mapbox-gl/dist/mapbox-gl.css'; const MapView = forwardRef( - ( - { - style, - styleURL, - waypoints, - mapPadding, - accessToken, - directionStyle, - directionCoordinates, - initialState = {location: CONST.MAPBOX.DEFAULT_COORDINATE, zoom: CONST.MAPBOX.DEFAULT_ZOOM}, - }, - ref, - ) => { + ({style, styleURL, waypoints, mapPadding, accessToken, directionCoordinates, initialState = {location: CONST.MAPBOX.DEFAULT_COORDINATE, zoom: CONST.MAPBOX.DEFAULT_ZOOM}}, ref) => { const [mapRef, setMapRef] = useState(null); const setRef = useCallback((newRef: MapRef | null) => setMapRef(newRef), []); @@ -59,7 +52,7 @@ const MapView = forwardRef( zoom: zoomLevel, duration: animationDuration, }), - fitBounds: (ne: [number, number], sw: [number, number]) => mapRef?.fitBounds([ne, sw]), + fitBounds: (northEast: [number, number], southWest: [number, number]) => mapRef?.fitBounds([northEast, southWest]), }), [mapRef], ); @@ -88,12 +81,7 @@ const MapView = forwardRef( ); })} - {directionCoordinates && ( - - )} + {directionCoordinates && } ); diff --git a/src/components/MapView/MapViewTypes.ts b/src/components/MapView/MapViewTypes.ts index aa2f47257a16..cf5abeed02b2 100644 --- a/src/components/MapView/MapViewTypes.ts +++ b/src/components/MapView/MapViewTypes.ts @@ -18,15 +18,11 @@ type MapViewProps = { waypoints?: WayPoint[]; // List of coordinates which together forms a direction. directionCoordinates?: Array<[number, number]>; - // Style used for the line that displays direction - directionStyle?: DirectionStyle; }; type DirectionProps = { // Coordinates of points that constitute the direction coordinates: Array<[number, number]>; - // Style used for the line that displays direction - directionStyle?: DirectionStyle; }; // Initial state of the map diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.js index 8536ff8980e6..c6f1f1bc3860 100644 --- a/src/pages/iou/WaypointEditor.js +++ b/src/pages/iou/WaypointEditor.js @@ -4,6 +4,7 @@ import lodashGet from 'lodash/get'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; +import {useIsFocused} from '@react-navigation/native'; import AddressSearch from '../../components/AddressSearch'; import ScreenWrapper from '../../components/ScreenWrapper'; import FullPageNotFoundView from '../../components/BlockingViews/FullPageNotFoundView'; @@ -74,6 +75,7 @@ const defaultProps = { function WaypointEditor({transactionID, route: {params: {iouType = '', waypointIndex = ''} = {}} = {}, transaction, recentWaypoints}) { const {windowWidth} = useWindowDimensions(); const [isDeleteStopModalOpen, setIsDeleteStopModalOpen] = useState(false); + const isFocused = useIsFocused(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); const textInput = useRef(null); @@ -159,7 +161,7 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI onEntryTransitionEnd={() => textInput.current && textInput.current.focus()} shouldEnableMaxHeight > - waypointCount - 1}> + waypointCount - 1) && isFocused}> Date: Fri, 1 Sep 2023 00:57:55 +0400 Subject: [PATCH 09/10] Fix panning issue --- src/components/MapView/MapView.tsx | 48 ++++++++++++++---------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index b001529c805a..c1dd064127a9 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -1,7 +1,7 @@ import {View} from 'react-native'; -import {useIsFocused} from '@react-navigation/native'; +import {useFocusEffect} from '@react-navigation/native'; import Mapbox, {MapState, MarkerView, setAccessToken} from '@rnmapbox/maps'; -import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from 'react'; +import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import utils from './utils'; import Direction from './Direction'; @@ -10,7 +10,6 @@ import CONST from '../../CONST'; import {MapViewProps, MapViewHandle} from './MapViewTypes'; const MapView = forwardRef(({accessToken, style, mapPadding, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates}, ref) => { - const isFocused = useIsFocused(); const cameraRef = useRef(null); const [isIdle, setIsIdle] = useState(false); @@ -27,27 +26,26 @@ const MapView = forwardRef(({accessToken, style, ma // When the page loses focus, we temporarily set the "idled" state to false. // When the page regains focus, the onIdled method of the map will set the actual "idled" state, - // which in turn triggers the callback of the next useEffect. - // If map not idled, camera transitions not working properly. - useEffect(() => { - if (isFocused) return; - setIsIdle(false); - }, [isFocused]); - - useEffect(() => { - if (!waypoints?.length || !isIdle || !isFocused) return; - - if (waypoints.length === 1) { - cameraRef.current?.setCamera({ - zoomLevel: 15, - animationDuration: 1000, - centerCoordinate: waypoints[0].coordinate, - }); - } else { - const {southWest, northEast} = utils.getBounds(waypoints.map((waypoint) => waypoint.coordinate)); - cameraRef.current?.fitBounds(northEast, southWest, mapPadding, 1000); - } - }, [mapPadding, waypoints, isFocused, isIdle]); + // which in turn triggers the callback. + useFocusEffect( + useCallback(() => { + if (waypoints?.length && isIdle) { + if (waypoints.length === 1) { + cameraRef.current?.setCamera({ + zoomLevel: 15, + animationDuration: 1500, + centerCoordinate: waypoints[0].coordinate, + }); + } else { + const {southWest, northEast} = utils.getBounds(waypoints.map((waypoint) => waypoint.coordinate)); + cameraRef.current?.fitBounds(northEast, southWest, mapPadding, 1000); + } + } + return () => { + setIsIdle(false); + }; + }, [mapPadding, waypoints, isIdle]), + ); useEffect(() => { setAccessToken(accessToken); @@ -93,4 +91,4 @@ const MapView = forwardRef(({accessToken, style, ma ); }); -export default MapView; +export default memo(MapView); From 9af7e4e20f5f64d3360c25d7330d851699e2d26d Mon Sep 17 00:00:00 2001 From: Gegham Khachatryan Date: Fri, 1 Sep 2023 20:10:53 +0400 Subject: [PATCH 10/10] fix: add PropTypes import which is mised during conflict resolution --- src/components/DistanceRequest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index 86c5fe4da35d..0dd74e4225d8 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -3,6 +3,7 @@ import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; import lodashHas from 'lodash/has'; +import PropTypes from 'prop-types'; import _ from 'underscore'; import CONST from '../CONST';