Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New places api support #47085

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
"react-native-draggable-flatlist": "^4.0.1",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.18.0",
"react-native-google-places-autocomplete": "2.5.6",
"react-native-google-places-autocomplete": "2.5.7",
"react-native-haptic-feedback": "^2.3.3",
"react-native-image-picker": "^7.0.3",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9",
Expand Down
4 changes: 4 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,10 @@ const CONST = {

YOUR_LOCATION_TEXT: 'Your Location',

GOOGLE_PLACES_API: {
FIELDS_MASK: 'id,name,addressComponents,adrFormatAddress,formattedAddress,location,plusCode,shortFormattedAddress,types,viewport',
},

ATTACHMENT_MESSAGE_TEXT: '[Attachment]',
ATTACHMENT_SOURCE_ATTRIBUTE: 'data-expensify-source',
ATTACHMENT_OPTIMISTIC_SOURCE_ATTRIBUTE: 'data-optimistic-src',
Expand Down
56 changes: 28 additions & 28 deletions src/components/AddressSearch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ function AddressSearch(
lat: 'addressLat',
lng: 'addressLng',
},
resultTypes = 'address',
shouldSaveDraft = false,
value,
locationBias,
Expand All @@ -99,26 +98,25 @@ function AddressSearch(
const containerRef = useRef<View>(null);
const query = useMemo(
() => ({
language: preferredLocale,
types: resultTypes,
components: isLimitedToUSA ? 'country:us' : undefined,
...(locationBias && {locationbias: locationBias}),
languageCode: preferredLocale,
includedRegionCodes: isLimitedToUSA ? ['us'] : undefined,
...(locationBias && {locationBias}),
}),
[preferredLocale, resultTypes, isLimitedToUSA, locationBias],
[preferredLocale, isLimitedToUSA, locationBias],
);
const shouldShowCurrentLocationButton = canUseCurrentLocation && searchValue.trim().length === 0 && isFocused;
const saveLocationDetails = (autocompleteData: GooglePlaceData, details: GooglePlaceDetail | null) => {
const addressComponents = details?.address_components;
const addressComponents = details?.addressComponents;
if (!addressComponents) {
// When there are details, but no address_components, this indicates that some predefined options have been passed
// to this component which don't match the usual properties coming from auto-complete. In that case, only a limited
// amount of data massaging needs to happen for what the parent expects to get from this function.
if (details) {
onPress?.({
address: autocompleteData.description ?? '',
lat: details.geometry.location.lat ?? 0,
lng: details.geometry.location.lng ?? 0,
name: details.name,
lat: details.location?.latitude ?? details.geometry.location?.lat ?? 0,
lng: details.location?.longitude ?? details.geometry.location?.lng ?? 0,
name: autocompleteData?.structured_formatting?.main_text ?? details?.name ?? '',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

details.name isn't the same as it was in the old API, the format here is places/<id> so not really good to use here. The next closest thing is details?. shortFormattedAddress

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this context, the details.name is not used from old API. Instead, it indicates a predefined option i.e. saved places in our app. So, I think we need to keep
details.name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you mean, can you show me? The way I understand this code, the value in autocompleteData.structured_formatting.main_text in the new API is most similar to details.name from the old api. However, details.name is no longer a good fallback, as in the new API, it does not contain a short version of the address, but something else entirely

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you mean, can you show me?

Please find the test video that demonstrates the use of predefined places. This is the reason why I think we need to keep details.name.

47085-predefined-places.mp4

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm ok. Can you take a look at how this is utilized when changing your address on the profile settings page as well? I wonder if we need different values in the two places.

});
}
return;
Expand All @@ -138,27 +136,27 @@ function AddressSearch(
country: countryPrimary,
} = GooglePlacesUtils.getAddressComponents(addressComponents, {
// eslint-disable-next-line @typescript-eslint/naming-convention
street_number: 'long_name',
route: 'long_name',
subpremise: 'long_name',
locality: 'long_name',
sublocality: 'long_name',
street_number: 'longText',
route: 'longText',
subpremise: 'longText',
locality: 'longText',
sublocality: 'longText',
// eslint-disable-next-line @typescript-eslint/naming-convention
postal_town: 'long_name',
postal_town: 'longText',
// eslint-disable-next-line @typescript-eslint/naming-convention
postal_code: 'long_name',
postal_code: 'longText',
// eslint-disable-next-line @typescript-eslint/naming-convention
administrative_area_level_1: 'short_name',
administrative_area_level_1: 'shortText',
// eslint-disable-next-line @typescript-eslint/naming-convention
administrative_area_level_2: 'long_name',
country: 'short_name',
administrative_area_level_2: 'longText',
country: 'shortText',
});

// The state's iso code (short_name) is needed for the StatePicker component but we also
// need the state's full name (long_name) when we render the state in a TextInput.
// need the state's full name (longText) when we render the state in a TextInput.
const {administrative_area_level_1: longStateName} = GooglePlacesUtils.getAddressComponents(addressComponents, {
// eslint-disable-next-line @typescript-eslint/naming-convention
administrative_area_level_1: 'long_name',
administrative_area_level_1: 'longText',
});

// Make sure that the order of keys remains such that the country is always set above the state.
Expand All @@ -176,7 +174,7 @@ function AddressSearch(

const values = {
street: `${streetNumber} ${streetName}`.trim(),
name: details.name ?? '',
name: autocompleteData?.structured_formatting?.main_text ?? details?.name ?? '',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here as above

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

// Autocomplete returns any additional valid address fragments (e.g. Apt #) as subpremise.
street2: subpremise,
// Make sure country is updated first, since city and state will be reset if the country changes
Expand All @@ -189,9 +187,9 @@ function AddressSearch(
city: locality || postalTown || sublocality || cityAutocompleteFallback,
zipCode,

lat: details.geometry.location.lat ?? 0,
lng: details.geometry.location.lng ?? 0,
address: autocompleteData.description || details.formatted_address || '',
lat: details.location?.latitude ?? details.geometry.location?.lat ?? 0,
lng: details.location?.longitude ?? details.geometry.location?.lng ?? 0,
address: autocompleteData.description || details.formattedAddress || '',
};

// If the address is not in the US, use the full length state name since we're displaying the address's
Expand All @@ -213,9 +211,9 @@ function AddressSearch(

// Some edge-case addresses may lack both street_number and route in the API response, resulting in an empty "values.street"
// We are setting up a fallback to ensure "values.street" is populated with a relevant value
if (!values.street && details.adr_address) {
if (!values.street && details.adrFormatAddress) {
const streetAddressRegex = /<span class="street-address">([^<]*)<\/span>/;
const adrAddress = details.adr_address.match(streetAddressRegex);
const adrAddress = details.adrFormatAddress.match(streetAddressRegex);
const streetAddressFallback = adrAddress ? adrAddress?.[1] : null;
if (streetAddressFallback) {
values.street = streetAddressFallback;
Expand Down Expand Up @@ -480,6 +478,8 @@ function AddressSearch(
}
placeholder=""
listViewDisplayed
fields={CONST.GOOGLE_PLACES_API.FIELDS_MASK}
isNewPlacesAPI
>
<LocationErrorMessage
onClose={() => setLocationErrorCode(null)}
Expand Down
15 changes: 14 additions & 1 deletion src/components/AddressSearch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ type PredefinedPlace = Place & {
name?: string;
};

type LocationBias = {
rectangle: {
low: {
latitude: number;
longitude: number;
};
high: {
latitude: number;
longitude: number;
};
};
};

type AddressSearchProps = {
/** The ID used to uniquely identify the input in a Form */
inputID?: string;
Expand Down Expand Up @@ -83,7 +96,7 @@ type AddressSearchProps = {
resultTypes?: string;

/** Location bias for querying search results. */
locationBias?: string;
locationBias?: LocationBias | undefined;

/** The user's preferred locale e.g. 'en', 'es-ES' */
preferredLocale?: Locale;
Expand Down
19 changes: 16 additions & 3 deletions src/hooks/useLocationBias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,21 @@ export default function useLocationBias(allWaypoints: WaypointCollection, userLo
const north = maxLat < 90 ? maxLat : 90;
const east = maxLng < 180 ? maxLng : 180;

// Format: rectangle:south,west|north,east
const rectFormat = `rectangle:${south},${west}|${north},${east}`;
return rectFormat;
if (latitudes.length === 0 || longitudes.length === 0) {
return undefined;
}
const rectangularBoundary = {
rectangle: {
low: {
latitude: south,
longitude: west,
},
high: {
latitude: north,
longitude: east,
},
},
};
return rectangularBoundary;
}, [userLocation, allWaypoints]);
}
10 changes: 4 additions & 6 deletions src/libs/GooglePlacesUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
type AddressComponent = {
// eslint-disable-next-line @typescript-eslint/naming-convention
long_name: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
short_name: string;
longText: string;
shortText: string;
types: string[];
};
type FieldsToExtract = Record<string, Exclude<keyof AddressComponent, 'types'>>;
Expand All @@ -11,8 +9,8 @@ type FieldsToExtract = Record<string, Exclude<keyof AddressComponent, 'types'>>;
* Finds an address component by type, and returns the value associated to key. Each address component object
* inside the addressComponents array has the following structure:
* [{
* long_name: "New York",
* short_name: "New York",
* longText: "New York",
* shortText: "New York",
* types: [ "locality", "political" ]
* }]
*/
Expand Down
64 changes: 23 additions & 41 deletions src/pages/iou/request/step/IOURequestStepWaypoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, {useMemo, useRef, useState} from 'react';
import type {TextInput} from 'react-native';
import {View} from 'react-native';
import type {Place} from 'react-native-google-places-autocomplete';
import {withOnyx} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import AddressSearch from '@components/AddressSearch';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
Expand Down Expand Up @@ -37,26 +37,36 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound';
import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound';
import withWritableReportOrNotFound from './withWritableReportOrNotFound';

type IOURequestStepWaypointOnyxProps = {
/** List of recent waypoints */
recentWaypoints: OnyxEntry<Place[]>;

userLocation: OnyxEntry<OnyxTypes.UserLocation>;
type IOURequestStepWaypointProps = WithWritableReportOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.STEP_WAYPOINT> & {
transaction: OnyxEntry<OnyxTypes.Transaction>;
};

type IOURequestStepWaypointProps = IOURequestStepWaypointOnyxProps &
WithWritableReportOrNotFoundProps<typeof SCREENS.MONEY_REQUEST.STEP_WAYPOINT> & {
transaction: OnyxEntry<OnyxTypes.Transaction>;
};
const recentWaypointsSelector = (waypoints: OnyxEntry<OnyxTypes.RecentWaypoint[]>): OnyxEntry<Place[]> =>
// Only grab the most recent 20 waypoints because that's all that is shown in the UI. This also puts them into the format of data
// that the google autocomplete component expects for it's "predefined places" feature.
(waypoints ? waypoints.slice(0, CONST.RECENT_WAYPOINTS_NUMBER as number) : [])
.filter((waypoint) => waypoint.keyForList?.includes(CONST.YOUR_LOCATION_TEXT) !== true)
.map((waypoint) => ({
name: waypoint.name,
description: waypoint.address ?? '',
geometry: {
location: {
lat: waypoint.lat ?? 0,
lng: waypoint.lng ?? 0,
latitude: waypoint.lat ?? 0,
longitude: waypoint.lng ?? 0,
},
},
})) as OnyxEntry<Place[]>;

function IOURequestStepWaypoint({
route: {
params: {action, backTo, iouType, pageIndex, reportID, transactionID},
},
transaction,
recentWaypoints = [],
userLocation,
}: IOURequestStepWaypointProps) {
const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION);
const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {selector: recentWaypointsSelector});
const styles = useThemeStyles();
const {windowWidth} = useWindowDimensions();
const [isDeleteStopModalOpen, setIsDeleteStopModalOpen] = useState(false);
Expand Down Expand Up @@ -247,32 +257,4 @@ function IOURequestStepWaypoint({

IOURequestStepWaypoint.displayName = 'IOURequestStepWaypoint';

export default withWritableReportOrNotFound(
withFullTransactionOrNotFound(
withOnyx<IOURequestStepWaypointProps, IOURequestStepWaypointOnyxProps>({
userLocation: {
key: ONYXKEYS.USER_LOCATION,
},
recentWaypoints: {
key: ONYXKEYS.NVP_RECENT_WAYPOINTS,

// Only grab the most recent 20 waypoints because that's all that is shown in the UI. This also puts them into the format of data
// that the google autocomplete component expects for it's "predefined places" feature.
selector: (waypoints) =>
(waypoints ? waypoints.slice(0, CONST.RECENT_WAYPOINTS_NUMBER as number) : [])
.filter((waypoint) => waypoint.keyForList?.includes(CONST.YOUR_LOCATION_TEXT) !== true)
.map((waypoint) => ({
name: waypoint.name,
description: waypoint.address ?? '',
geometry: {
location: {
lat: waypoint.lat ?? 0,
lng: waypoint.lng ?? 0,
},
},
})),
},
})(IOURequestStepWaypoint),
),
true,
);
export default withWritableReportOrNotFound(withFullTransactionOrNotFound(IOURequestStepWaypoint));
Loading
Loading