Skip to content

Commit

Permalink
Merge pull request #25990 from huzaifa-99/25855-get-user-location
Browse files Browse the repository at this point in the history
Allow user to get current location
  • Loading branch information
stitesExpensify authored Aug 31, 2023
2 parents dbef39a + 4dea2d6 commit f604e90
Show file tree
Hide file tree
Showing 21 changed files with 9,743 additions and 401 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ USE_WEB_PROXY=false
USE_WDYR=false
CAPTURE_METRICS=false
ONYX_METRICS=false
GOOGLE_GEOLOCATION_API_KEY=AIzaSyBqg6bMvQU7cPWDKhhzpYqJrTEnSorpiLI

EXPENSIFY_ACCOUNT_ID_ACCOUNTING=-1
EXPENSIFY_ACCOUNT_ID_ADMIN=-1
Expand Down
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ PUSHER_APP_KEY=268df511a204fbb60884
USE_WEB_PROXY=false
ENVIRONMENT=production
SEND_CRASH_REPORTS=true
GOOGLE_GEOLOCATION_API_KEY=AIzaSyBFKujMpzExz0_z2pAGfPUwkmlaUc-uw1Q
1 change: 1 addition & 0 deletions .env.staging
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ PUSHER_APP_KEY=268df511a204fbb60884
USE_WEB_PROXY=false
ENVIRONMENT=staging
SEND_CRASH_REPORTS=true
GOOGLE_GEOLOCATION_API_KEY=AIzaSyD2T1mlByThbUN88O8OPOD8vKuMMwLD4-M
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
9,658 changes: 9,259 additions & 399 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 @@ -115,6 +116,7 @@
"react-native-document-picker": "^8.0.0",
"react-native-fast-image": "^8.6.3",
"react-native-fs": "^2.20.0",
"react-native-geolocation": "^1.0.0",
"react-native-gesture-handler": "2.12.0",
"react-native-google-places-autocomplete": "git+https://github.com/Expensify/react-native-google-places-autocomplete.git#ee87343c3e827ff7818abc71b6bb04fcc1f120e0",
"react-native-haptic-feedback": "^1.13.0",
Expand Down
2 changes: 2 additions & 0 deletions src/CONFIG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const secureExpensifyUrl = Url.addTrailingForwardSlash(get(Config, 'SECURE_EXPEN
const useNgrok = get(Config, 'USE_NGROK', 'false') === 'true';
const useWebProxy = get(Config, 'USE_WEB_PROXY', 'true') === 'true';
const expensifyComWithProxy = getPlatform() === 'web' && useWebProxy ? '/' : expensifyURL;
const googleGeolocationAPIKey = get(Config, 'GOOGLE_GEOLOCATION_API_KEY', 'AIzaSyBqg6bMvQU7cPWDKhhzpYqJrTEnSorpiLI');

// Throw errors on dev if config variables are not set correctly
if (ENVIRONMENT === CONST.ENVIRONMENT.DEV) {
Expand Down Expand Up @@ -91,4 +92,5 @@ export default {
WEB_CLIENT_ID: '921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com',
IOS_CLIENT_ID: '921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3.apps.googleusercontent.com',
},
GOOGLE_GEOLOCATION_API_KEY: googleGeolocationAPIKey,
} as const;
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',

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

Expand Down Expand Up @@ -320,6 +323,7 @@ type OnyxValues = {
[ONYXKEYS.SCREEN_SHARE_REQUEST]: OnyxTypes.ScreenShareRequest;
[ONYXKEYS.COUNTRY_CODE]: number;
[ONYXKEYS.COUNTRY]: string;
[ONYXKEYS.LOCATION_ERROR_CODE]: number;
[ONYXKEYS.USER]: OnyxTypes.User;
[ONYXKEYS.LOGIN_LIST]: OnyxTypes.Login;
[ONYXKEYS.SESSION]: OnyxTypes.Session;
Expand Down
99 changes: 99 additions & 0 deletions src/components/LocationErrorMessage/BaseLocationErrorMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
import compose from '../../libs/compose';
import colors from '../../styles/colors';
import styles from '../../styles/styles';
import Icon from '../Icon';
import * as Expensicons from '../Icon/Expensicons';
import Text from '../Text';
import TextLink from '../TextLink';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import Tooltip from '../Tooltip';
import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback';
import * as User from '../../libs/actions/User';
import CONST from '../../CONST';

const propTypes = {
/** The location error code from onyx */
locationErrorCode: PropTypes.oneOfType([PropTypes.number, PropTypes.oneOf([null])]),

/** A callback that runs when 'allow location permission' link is pressed */
onAllowLocationLinkPress: PropTypes.func.isRequired,

...withLocalizePropTypes,
};

const defaultProps = {
locationErrorCode: undefined,
};

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

const isPermissionDenied = locationErrorCode === 1;

/**
* Clears the location error on press of close icon
*/
const dismissError = () => {
User.clearLocationError();
};

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
*/}
{isPermissionDenied ? (
<Text style={styles.offlineFeedback.text}>
<Text>{`${translate('location.permissionDenied')} ${translate('common.please')}`}</Text>
<TextLink onPress={onAllowLocationLinkPress}>{` ${translate('location.allowPermission')} `}</TextLink>
<Text>{translate('location.tryAgain')}</Text>
</Text>
) : (
<Text style={styles.offlineFeedback.text}>{translate('location.notFound')}</Text>
)}
</View>
<View>
<Tooltip text={translate('common.close')}>
<PressableWithoutFeedback
onPress={dismissError}
style={[styles.touchableButtonImage]}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={translate('common.close')}
>
<Icon src={Expensicons.Close} />
</PressableWithoutFeedback>
</Tooltip>
</View>
</View>
);
}

BaseLocationErrorMessage.displayName = 'BaseLocationErrorMessage';
BaseLocationErrorMessage.propTypes = propTypes;
BaseLocationErrorMessage.defaultProps = defaultProps;
export default compose(
withOnyx({
locationErrorCode: {
key: ONYXKEYS.LOCATION_ERROR_CODE,
},
}),
withLocalize,
)(BaseLocationErrorMessage);
16 changes: 16 additions & 0 deletions src/components/LocationErrorMessage/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import {Linking} from 'react-native';
import CONST from '../../CONST';
import BaseLocationErrorMessage from './BaseLocationErrorMessage';

function LocationErrorMessage() {
/** opens expensify help site in a new browser tab */
const navigateToExpensifyHelpSite = () => {
Linking.openURL(CONST.NEWHELP_URL);
};

return <BaseLocationErrorMessage onAllowLocationLinkPress={navigateToExpensifyHelpSite} />;
}

LocationErrorMessage.displayName = 'LocationErrorMessage';
export default LocationErrorMessage;
15 changes: 15 additions & 0 deletions src/components/LocationErrorMessage/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import {Linking} from 'react-native';
import BaseLocationErrorMessage from './BaseLocationErrorMessage';

function LocationErrorMessage() {
/** opens app level settings from the system settings */
const openAppSettings = () => {
Linking.openSettings();
};

return <BaseLocationErrorMessage onAllowLocationLinkPress={openAppSettings} />;
}

LocationErrorMessage.displayName = 'LocationErrorMessage';
export default LocationErrorMessage;
88 changes: 88 additions & 0 deletions src/components/UserCurrentLocationButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import PropTypes from 'prop-types';
import React, {useEffect, useRef} from 'react';
import {Text} from 'react-native';
import getCurrentPosition from '../libs/getCurrentPosition';
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,

...withLocalizePropTypes,
};

const defaultProps = {};

function UserCurrentLocationButton({onLocationFetched, 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={translate('location.useCurrent')}
>
<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);
9 changes: 8 additions & 1 deletion src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ export default {
recent: 'Recent',
all: 'All',
},
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 Expand Up @@ -1657,7 +1664,7 @@ export default {
onlineSubtitle: 'One moment while we set up the map',
},
errors: {
selectSuggestedAddress: 'Please select a suggested address',
selectSuggestedAddress: 'Please select a suggested address or use current location',
},
},
countrySelectorModal: {
Expand Down
9 changes: 8 additions & 1 deletion src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ export default {
recent: 'Reciente',
all: 'Todo',
},
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.',
},
anonymousReportFooter: {
logoTagline: 'Únete a la discusión.',
},
Expand Down Expand Up @@ -2145,7 +2152,7 @@ export default {
onlineSubtitle: 'Un momento mientras configuramos el mapa',
},
errors: {
selectSuggestedAddress: 'Por favor, selecciona una dirección sugerida',
selectSuggestedAddress: 'Por favor, selecciona una dirección sugerida o usa la ubicación actual.',
},
},
countrySelectorModal: {
Expand Down
Loading

0 comments on commit f604e90

Please sign in to comment.