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

Allow user to get current location #25990

Merged
merged 29 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fc04db7
Allow user to get current location
huzaifa-99 Aug 26, 2023
5fec130
Added default props and JSDoc
huzaifa-99 Aug 26, 2023
7465ef1
Added comment explaining locationErrorCode default value
huzaifa-99 Aug 26, 2023
5f11209
Fix import
huzaifa-99 Aug 26, 2023
8ab688f
Updated react-native-x-geolocation to 1.0.10
huzaifa-99 Aug 26, 2023
ffb3138
Fix onyx key type
huzaifa-99 Aug 26, 2023
31658da
Use undefined instead of 0 as deafult value for location error
huzaifa-99 Aug 26, 2023
d73335a
Added variable for conditional rendering
huzaifa-99 Aug 26, 2023
312cff2
Added address hint with updated message
huzaifa-99 Aug 26, 2023
d7bd1ea
Merge branch 'main' into 25855-get-user-location
huzaifa-99 Aug 27, 2023
a12968e
Removed accessibilityLabel as prop from UserCurrentLocationButton
huzaifa-99 Aug 27, 2023
9bf0236
Added platform specific files for 'allow location permission' link
huzaifa-99 Aug 27, 2023
f371f95
Fixed type
huzaifa-99 Aug 27, 2023
14494b9
Removed unnecessary prop objects
huzaifa-99 Aug 28, 2023
6403a08
Fix unnecessary prop objects
huzaifa-99 Aug 28, 2023
348fc5c
Removed unnecessary default prop
huzaifa-99 Aug 28, 2023
0fd62fc
Added default prop back to location error message
huzaifa-99 Aug 28, 2023
c582e75
Added geolocation util from react-native-x-geolocation
huzaifa-99 Aug 30, 2023
24dfaf7
Removed react-native-x-geolocation
huzaifa-99 Aug 30, 2023
90e5866
Added react-native-geolocation^1.0.0
huzaifa-99 Aug 30, 2023
2633ae3
Merge branch 'main' into 25855-get-user-location
huzaifa-99 Aug 30, 2023
3b9fe10
Fix lint
huzaifa-99 Aug 30, 2023
ca04daf
Added GOOGLE_GEOLOCATION_API_KEY tokens
huzaifa-99 Aug 30, 2023
3bd4faf
Merge branch 'main' into 25855-get-user-location
huzaifa-99 Aug 30, 2023
ee60ec3
Use null instead of undefined when clearing location error
huzaifa-99 Aug 30, 2023
72fc797
Added geolocation api key as default value in config
huzaifa-99 Aug 30, 2023
86b61b7
Show close icon with location error message
huzaifa-99 Aug 30, 2023
3856f7f
Use set instead of merge for location error code onyx
huzaifa-99 Aug 31, 2023
4dea2d6
Removed unnecessary desktop config
huzaifa-99 Aug 31, 2023
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
1 change: 1 addition & 0 deletions desktop/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ const mainWindow = () => {
preload: `${__dirname}/contextBridge.js`,
contextIsolation: true,
sandbox: false,
nodeIntegration: true, // required for geolocation https://github.com/electron/electron/blob/main/docs/api/environment-variables.md#google_api_key
stitesExpensify marked this conversation as resolved.
Show resolved Hide resolved
},
titleBarStyle: 'hidden',
});
Expand Down
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,8 @@ PODS:
- React-Core
- react-native-flipper (0.159.0):
- React-Core
- react-native-geolocation (3.0.6):
- React-Core
- react-native-image-manipulator (1.0.5):
- React
- react-native-image-picker (5.1.0):
Expand Down Expand Up @@ -892,6 +894,7 @@ DEPENDENCIES:
- react-native-config (from `../node_modules/react-native-config`)
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- react-native-flipper (from `../node_modules/react-native-flipper`)
- "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)"
- "react-native-image-manipulator (from `../node_modules/@oguzhnatly/react-native-image-manipulator`)"
- react-native-image-picker (from `../node_modules/react-native-image-picker`)
- react-native-key-command (from `../node_modules/react-native-key-command`)
Expand Down Expand Up @@ -1065,6 +1068,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker"
react-native-flipper:
:path: "../node_modules/react-native-flipper"
react-native-geolocation:
:path: "../node_modules/@react-native-community/geolocation"
react-native-image-manipulator:
:path: "../node_modules/@oguzhnatly/react-native-image-manipulator"
react-native-image-picker:
Expand Down Expand Up @@ -1249,6 +1254,7 @@ SPEC CHECKSUMS:
react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e
react-native-document-picker: f68191637788994baed5f57d12994aa32cf8bf88
react-native-flipper: dc5290261fbeeb2faec1bdc57ae6dd8d562e1de4
react-native-geolocation: 0f7fe8a4c2de477e278b0365cce27d089a8c5903
react-native-image-manipulator: c48f64221cfcd46e9eec53619c4c0374f3328a56
react-native-image-picker: c33d4e79f0a14a2b66e5065e14946ae63749660b
react-native-key-command: c2645ec01eb1fa664606c09480c05cb4220ef67b
Expand Down
10,293 changes: 9,862 additions & 431 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@react-native-camera-roll/camera-roll": "5.4.0",
"@react-native-community/clipboard": "^1.5.1",
"@react-native-community/datetimepicker": "^3.5.2",
"@react-native-community/geolocation": "^3.0.6",
"@react-native-community/netinfo": "^9.3.10",
"@react-native-firebase/analytics": "^12.3.0",
"@react-native-firebase/app": "^12.3.0",
Expand Down Expand Up @@ -146,6 +147,7 @@
"react-native-web-linear-gradient": "^1.1.2",
"react-native-web-lottie": "^1.4.4",
"react-native-webview": "^11.17.2",
"react-native-x-geolocation": "^1.0.9",
"react-native-x-maps": "1.0.10",
"react-pdf": "^6.2.2",
"react-plaid-link": "3.3.2",
Expand Down
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ const ONYXKEYS = {
* It is expected to provide a two-letter country code such as US for United States, and so on. */
COUNTRY: 'country',

/** Represents current user's location error code, this error code comes from the geolocation api */
LOCATION_ERROR_CODE: 'locationErrorCode',
huzaifa-99 marked this conversation as resolved.
Show resolved Hide resolved

/** Contains all the users settings for the Settings page and sub pages */
USER: 'user',

Expand Down Expand Up @@ -306,6 +309,7 @@ type OnyxValues = {
[ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest;
[ONYXKEYS.COUNTRY_CODE]: number;
[ONYXKEYS.COUNTRY]: string;
[ONYXKEYS.LOCATION_ERROR_CODE]: string;
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
[ONYXKEYS.USER]: OnyxTypes.User;
[ONYXKEYS.LOGIN_LIST]: OnyxTypes.Login;
[ONYXKEYS.SESSION]: OnyxTypes.Session;
Expand Down
82 changes: 82 additions & 0 deletions src/components/LocationErrorMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import PropTypes from 'prop-types';
import React from 'react';
import {Linking, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';
import TextLink from './TextLink';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import CONST from '../CONST';
import ONYXKEYS from '../ONYXKEYS';
import compose from '../libs/compose';
import getPlatform from '../libs/getPlatform';
import colors from '../styles/colors';
import styles from '../styles/styles';

const propTypes = {
/** The location error code from onyx */
locationErrorCode: PropTypes.number,

...withLocalizePropTypes,
};

const defaultProps = {
locationErrorCode: 0,
};

function LocationErrorMessage({locationErrorCode, translate}) {
if (!locationErrorCode) {
return null;
}

const navigateToSettings = () => {
const platform = getPlatform();
if (platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID) {
Linking.openSettings();
} else {
Linking.openURL(CONST.NEWHELP_URL);
}
};
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved

return (
<View style={[styles.dotIndicatorMessage, styles.mt4]}>
<View style={styles.offlineFeedback.errorDot}>
<Icon
src={Expensicons.DotIndicator}
fill={colors.red}
/>
</View>
<View style={styles.offlineFeedback.textContainer}>
{/*
Show appropriate error msg on location issues
- errorCode = -1 -> location not supported (web only)
- errorCode = 1 -> location permission is not enabled
- errorCode = 2 -> location is unavailable or there is some connection issue
- errorCode = 3 -> location fetch timeout
*/}
{locationErrorCode === 1 ? (
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
<Text style={styles.offlineFeedback.text}>
<Text>{`${translate('location.permissionDenied')} ${translate('common.please')}`}</Text>
<TextLink onPress={navigateToSettings}>{` ${translate('location.allowPermission')} `}</TextLink>
<Text>{translate('location.tryAgain')}</Text>
</Text>
) : (
<Text style={styles.offlineFeedback.text}>{translate('location.notFound')}</Text>
)}
</View>
</View>
);
}

LocationErrorMessage.displayName = 'LocationErrorMessage';
LocationErrorMessage.propTypes = propTypes;
LocationErrorMessage.defaultProps = defaultProps;
export default compose(
withOnyx({
locationErrorCode: {
key: ONYXKEYS.LOCATION_ERROR_CODE,
},
}),
withLocalize,
)(LocationErrorMessage);
93 changes: 93 additions & 0 deletions src/components/UserCurrentLocationButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import PropTypes from 'prop-types';
import React, {useEffect, useRef} from 'react';
import {Text} from 'react-native';
import {getCurrentPosition} from 'react-native-x-geolocation';
import * as User from '../libs/actions/User';
import styles from '../styles/styles';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import LocationErrorMessage from './LocationErrorMessage';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import colors from '../styles/colors';
import PressableWithFeedback from '../Pressable/PressableWithFeedback';

const propTypes = {
/** Callback that runs when location data is fetched */
onLocationFetched: PropTypes.func.isRequired,

/** Accessibility label for the component */
accessibilityLabel: PropTypes.string,

...withLocalizePropTypes,
};

const defaultProps = {
accessibilityLabel: 'location.useCurrent',
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
};

function UserCurrentLocationButton({onLocationFetched, accessibilityLabel, translate}) {
const isFetchingLocation = useRef(false);

/**
* handles error when failed to get user's current location
* @param {Object} errorData
* @param {Number} errorData.code
*/
const onError = (errorData) => {
isFetchingLocation.current = false;

User.setLocationError(errorData.code);
};

/**
* handles success after getting user's current location
* @param {Object} successData
* @param {Object} successData.coords
* @param {Number} successData.coords.longitude
* @param {Number} successData.coords.latitude
* @param {Number} successData.timestamp
*/
const onSuccess = (successData) => {
isFetchingLocation.current = false;

User.clearLocationError();

onLocationFetched(successData);
};

/** Gets the user's current location and registers success/error callbacks */
const useCurrentLocation = () => {
if (isFetchingLocation.current) return;

isFetchingLocation.current = true;

getCurrentPosition(onSuccess, onError);
};

useEffect(() => {
// clear location errors on mount
User.clearLocationError();
}, []);

return (
<>
<PressableWithFeedback
style={[styles.flexRow, styles.mt4]}
onPress={useCurrentLocation}
accessibilityLabel={accessibilityLabel}
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
>
<Icon
src={Expensicons.Location}
fill={colors.green}
/>
<Text style={[styles.textLabel, styles.mh2]}>{translate('location.useCurrent')}</Text>
</PressableWithFeedback>
<LocationErrorMessage />
</>
);
}

UserCurrentLocationButton.displayName = 'UserCurrentLocationButton';
UserCurrentLocationButton.propTypes = propTypes;
UserCurrentLocationButton.defaultProps = defaultProps;
export default withLocalize(UserCurrentLocationButton);
Copy link
Contributor

@narefyev91 narefyev91 Aug 28, 2023

Choose a reason for hiding this comment

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

you can use useLocalize hook

Copy link
Contributor Author

@huzaifa-99 huzaifa-99 Aug 28, 2023

Choose a reason for hiding this comment

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

Actually since UserCurrentLocationButton is nested inside the Form component here, it was giving hooks error (react rules of hook realted). I made an assumption here that the UserCurrentLocationButton might be used in other places and maybe inside Form too, so didn't use the localize hook.

7 changes: 7 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ export default {
receipt: 'Receipt',
replace: 'Replace',
},
location: {
useCurrent: 'Use current location',
notFound: 'We were unable to find your location, please try again or enter an address manually',
permissionDenied: 'It looks like you have denied permission to your location.',
allowPermission: 'allow location permission in settings',
tryAgain: 'and then try again.',
},
anonymousReportFooter: {
logoTagline: 'Join the discussion.',
},
Expand Down
7 changes: 7 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,13 @@ export default {
receipt: 'Recibo',
replace: 'Sustituir',
},
location: {
useCurrent: 'Usar ubicación actual',
notFound: 'No pudimos encontrar su ubicación, inténtelo nuevamente o ingrese una dirección manualmente',
permissionDenied: 'Parece que has denegado el permiso a tu ubicación.',
allowPermission: 'permitir permiso de ubicación en la configuración',
tryAgain: 'y luego inténtalo de nuevo.',
},
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
anonymousReportFooter: {
logoTagline: 'Únete a la discusión.',
},
Expand Down
25 changes: 25 additions & 0 deletions src/libs/actions/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,29 @@ function clearDraftCustomStatus() {
Onyx.merge(ONYXKEYS.CUSTOM_STATUS_DRAFT, {text: '', emojiCode: '', clearAfter: ''});
}

/**
* Sets the error code when trying to get user's current location. When
* errorCode = -1 -> location not supported (web only)
* errorCode 0 = when assume there is no error
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
* errorCode 1 = location permission is not enabled
* errorCode 2 = location is unavailable or there is some connection issue
* errorCode 3 = location fetch timeout
*
* @param {Object} errorCode - indicates location error.
*/
function setLocationError(errorCode) {
Onyx.merge(ONYXKEYS.LOCATION_ERROR_CODE, errorCode);
}

/**
* Clears the error code from location
*
*/
function clearLocationError() {
// when errorCode is 0, we assume no error
Onyx.merge(ONYXKEYS.LOCATION_ERROR_CODE, 0);
}

export {
closeAccount,
resendValidateCode,
Expand Down Expand Up @@ -914,4 +937,6 @@ export {
clearCustomStatus,
updateDraftCustomStatus,
clearDraftCustomStatus,
setLocationError,
clearLocationError,
};
22 changes: 21 additions & 1 deletion src/pages/iou/WaypointEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import ROUTES from '../../ROUTES';
import {withNetwork} from '../../components/OnyxProvider';
import networkPropTypes from '../../components/networkPropTypes';
import transactionPropTypes from '../../components/transactionPropTypes';
import * as User from '../../libs/actions/User';
import UserCurrentLocationButton from '../../components/UserCurrentLocationButton';

const propTypes = {
/** The transactionID of the IOU */
Expand Down Expand Up @@ -103,10 +105,28 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI
address: values.address,
};

User.clearLocationError();
Transaction.saveWaypoint(transactionID, waypointIndex, waypoint);
Navigation.goBack(ROUTES.getMoneyRequestDistanceTabRoute(iouType));
};

/**
* sets user current location as a waypoint
* @param {Object} geolocationData
* @param {Object} geolocationData.coords.latitude
* @param {Object} geolocationData.coords.longitude
* @param {Object} geolocationData.timestamp
*/
const selectWaypointFromCurrentLocation = (geolocationData) => {
const waypoint = {
lat: geolocationData.coords.latitude,
lng: geolocationData.coords.longitude,
address: 'Your Location',
};

selectWaypoint(waypoint);
};

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
Expand Down Expand Up @@ -134,7 +154,6 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI
<AddressSearch
inputID={`waypoint${waypointIndex}`}
ref={(e) => (textInput.current = e)}
hint={!network.isOffline ? translate('distance.errors.selectSuggestedAddress') : ''}
hayata-suenaga marked this conversation as resolved.
Show resolved Hide resolved
containerStyles={[styles.mt4]}
label={translate('distance.address')}
defaultValue={waypointAddress}
Expand All @@ -153,6 +172,7 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI
}}
/>
</View>
<UserCurrentLocationButton onLocationFetched={selectWaypointFromCurrentLocation} />
</Form>
</ScreenWrapper>
);
Expand Down