-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Added option to get user's current location #26546
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
Changes from all commits
cd8cb9b
27f52d2
6d55057
4f3c0ff
8203e03
a55358c
f718ed6
98f7570
c2d67b8
1221d12
1be54a1
c097915
fa5aaef
befbb1f
fab2259
19d0742
f7654d6
cc97747
67c7c8a
45947ce
a7f6e18
7656511
f4ee7d7
fb37344
40792cc
e0be146
79c187d
cb65a73
49c619b
08debbc
809a449
1d31921
1122848
7d7f05f
07d3da3
393d582
b1a7da2
774fcd1
50680b4
caa536d
7f309ad
63b4379
7c8109e
2ba6260
74b16e2
2f0c0f0
8d16400
c0d32e2
2f04195
3e8a077
572cd7a
853f7e4
2b34ccd
6c505e4
ae8ec7a
37a47c2
98fcdc5
8a5cc0b
c226384
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| import PropTypes from 'prop-types'; | ||
| import React from 'react'; | ||
| import {View} from 'react-native'; | ||
| import CONST from '../../CONST'; | ||
| import colors from '../../styles/colors'; | ||
| import styles from '../../styles/styles'; | ||
| import Icon from '../Icon'; | ||
| import * as Expensicons from '../Icon/Expensicons'; | ||
| import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; | ||
| import Text from '../Text'; | ||
| import TextLink from '../TextLink'; | ||
| import Tooltip from '../Tooltip'; | ||
| import withLocalize, {withLocalizePropTypes} from '../withLocalize'; | ||
| import * as locationErrorMessagePropTypes from './locationErrorMessagePropTypes'; | ||
|
|
||
| const propTypes = { | ||
| /** A callback that runs when 'allow location permission' link is pressed */ | ||
| onAllowLocationLinkPress: PropTypes.func.isRequired, | ||
|
|
||
| // eslint-disable-next-line react/forbid-foreign-prop-types | ||
| ...locationErrorMessagePropTypes.propTypes, | ||
|
|
||
| /* Onyx Props */ | ||
| ...withLocalizePropTypes, | ||
| }; | ||
|
|
||
| function BaseLocationErrorMessage({onClose, onAllowLocationLinkPress, locationErrorCode, translate}) { | ||
| if (!locationErrorCode) { | ||
| return null; | ||
| } | ||
|
|
||
| const isPermissionDenied = locationErrorCode === 1; | ||
|
|
||
| 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}> | ||
| {isPermissionDenied ? ( | ||
| <Text> | ||
| <Text style={[styles.offlineFeedback.text]}>{`${translate('location.permissionDenied')} ${translate('location.please')}`}</Text> | ||
| <TextLink | ||
| onPress={onAllowLocationLinkPress} | ||
| style={styles.locationErrorLinkText} | ||
| > | ||
| {` ${translate('location.allowPermission')} `} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NAB: we should use padding instead of spaces
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spaces work better when text wraps the next lines as they can be collapsed. |
||
| </TextLink> | ||
| <Text style={[styles.offlineFeedback.text]}>{translate('location.tryAgain')}</Text> | ||
| </Text> | ||
| ) : ( | ||
| <Text style={styles.offlineFeedback.text}>{translate('location.notFound')}</Text> | ||
| )} | ||
| </View> | ||
| <View> | ||
| <Tooltip text={translate('common.close')}> | ||
| <PressableWithoutFeedback | ||
| onPress={onClose} | ||
| 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 = locationErrorMessagePropTypes.defaultProps; | ||
| export default withLocalize(BaseLocationErrorMessage); | ||
|
huzaifa-99 marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import React from 'react'; | ||
| import {Linking} from 'react-native'; | ||
| import CONST from '../../CONST'; | ||
| import BaseLocationErrorMessage from './BaseLocationErrorMessage'; | ||
| import * as locationErrorMessagePropTypes from './locationErrorMessagePropTypes'; | ||
|
|
||
| /** Opens expensify help site in a new browser tab */ | ||
| const navigateToExpensifyHelpSite = () => { | ||
| Linking.openURL(CONST.NEWHELP_URL); | ||
| }; | ||
|
|
||
| function LocationErrorMessage(props) { | ||
| return ( | ||
| <BaseLocationErrorMessage | ||
| // eslint-disable-next-line react/jsx-props-no-spreading | ||
| {...props} | ||
| onAllowLocationLinkPress={navigateToExpensifyHelpSite} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| LocationErrorMessage.displayName = 'LocationErrorMessage'; | ||
| LocationErrorMessage.propTypes = locationErrorMessagePropTypes.propTypes; | ||
|
huzaifa-99 marked this conversation as resolved.
|
||
| LocationErrorMessage.defaultProps = locationErrorMessagePropTypes.defaultProps; | ||
| export default LocationErrorMessage; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import React from 'react'; | ||
| import {Linking} from 'react-native'; | ||
| import BaseLocationErrorMessage from './BaseLocationErrorMessage'; | ||
| import * as locationErrorMessagePropTypes from './locationErrorMessagePropTypes'; | ||
|
|
||
| /** Opens app level settings from the native system settings */ | ||
| const openAppSettings = () => { | ||
| Linking.openSettings(); | ||
| }; | ||
|
|
||
| function LocationErrorMessage(props) { | ||
| return ( | ||
| <BaseLocationErrorMessage | ||
| // eslint-disable-next-line react/jsx-props-no-spreading | ||
| {...props} | ||
| onAllowLocationLinkPress={openAppSettings} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| LocationErrorMessage.displayName = 'LocationErrorMessage'; | ||
| LocationErrorMessage.propTypes = locationErrorMessagePropTypes.propTypes; | ||
|
huzaifa-99 marked this conversation as resolved.
|
||
| LocationErrorMessage.defaultProps = locationErrorMessagePropTypes.defaultProps; | ||
| export default LocationErrorMessage; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import PropTypes from 'prop-types'; | ||
|
|
||
| const propTypes = { | ||
| /** A callback that runs when close icon is pressed */ | ||
| onClose: PropTypes.func.isRequired, | ||
|
|
||
| /** | ||
| * The location error code from onyx | ||
| * - code -1 = location not supported (web only) | ||
| * - code 1 = location permission is not enabled | ||
| * - code 2 = location is unavailable or there is some connection issue | ||
| * - code 3 = location fetch timeout | ||
| */ | ||
| locationErrorCode: PropTypes.oneOf([-1, 1, 2, 3]), | ||
| }; | ||
|
|
||
| const defaultProps = { | ||
| locationErrorCode: null, | ||
| }; | ||
|
|
||
| export {propTypes, defaultProps}; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| import PropTypes from 'prop-types'; | ||
| import React, {useEffect, useRef, useState} from 'react'; | ||
| import {Text} from 'react-native'; | ||
| import getCurrentPosition from '../libs/getCurrentPosition'; | ||
| 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, | ||
|
|
||
| /** Callback that runs when fetching location has errors */ | ||
| onLocationError: PropTypes.func, | ||
|
|
||
| /** Callback that runs when location button is clicked */ | ||
| onClick: PropTypes.func, | ||
|
|
||
| /** Boolean to indicate if the button is clickable */ | ||
| isDisabled: PropTypes.bool, | ||
|
|
||
| ...withLocalizePropTypes, | ||
|
huzaifa-99 marked this conversation as resolved.
|
||
| }; | ||
|
|
||
| const defaultProps = { | ||
| isDisabled: false, | ||
| onLocationError: () => {}, | ||
| onClick: () => {}, | ||
| }; | ||
|
|
||
| function UserCurrentLocationButton({onLocationFetched, onLocationError, onClick, isDisabled, translate}) { | ||
| const isFetchingLocation = useRef(false); | ||
| const shouldTriggerCallbacks = useRef(true); | ||
| const [locationErrorCode, setLocationErrorCode] = useState(null); | ||
|
|
||
| /** Gets the user's current location and registers success/error callbacks */ | ||
| const getUserLocation = () => { | ||
| if (isFetchingLocation.current) { | ||
| return; | ||
| } | ||
|
|
||
| isFetchingLocation.current = true; | ||
|
|
||
| onClick(); | ||
|
|
||
| getCurrentPosition( | ||
|
hayata-suenaga marked this conversation as resolved.
|
||
| (successData) => { | ||
| isFetchingLocation.current = false; | ||
| if (!shouldTriggerCallbacks.current) { | ||
| return; | ||
| } | ||
|
|
||
| setLocationErrorCode(null); | ||
| onLocationFetched(successData); | ||
| }, | ||
| (errorData) => { | ||
| isFetchingLocation.current = false; | ||
| if (!shouldTriggerCallbacks.current) { | ||
| return; | ||
| } | ||
|
|
||
| setLocationErrorCode(errorData.code); | ||
| onLocationError(errorData); | ||
| }, | ||
| { | ||
| maximumAge: 0, // No cache, always get fresh location info | ||
| timeout: 5000, | ||
| }, | ||
| ); | ||
| }; | ||
|
|
||
| // eslint-disable-next-line arrow-body-style | ||
| useEffect(() => { | ||
| return () => { | ||
| // If the component unmounts we don't want any of the callback for geolocation to run. | ||
| shouldTriggerCallbacks.current = false; | ||
| }; | ||
| }, []); | ||
|
|
||
| return ( | ||
| <> | ||
| <PressableWithFeedback | ||
| style={[styles.flexRow, styles.mt4, styles.alignSelfStart, isDisabled && styles.buttonOpacityDisabled]} | ||
| onPress={getUserLocation} | ||
| accessibilityLabel={translate('location.useCurrent')} | ||
| disabled={isDisabled} | ||
| > | ||
| <Icon | ||
| src={Expensicons.Location} | ||
| fill={colors.green} | ||
| /> | ||
| <Text style={[styles.textLabel, styles.mh2, isDisabled && styles.userSelectNone]}>{translate('location.useCurrent')}</Text> | ||
| </PressableWithFeedback> | ||
| <LocationErrorMessage | ||
| onClose={() => setLocationErrorCode(null)} | ||
| locationErrorCode={locationErrorCode} | ||
| /> | ||
| </> | ||
| ); | ||
| } | ||
|
|
||
| UserCurrentLocationButton.displayName = 'UserCurrentLocationButton'; | ||
| UserCurrentLocationButton.propTypes = propTypes; | ||
| UserCurrentLocationButton.defaultProps = defaultProps; | ||
|
|
||
| // This components gets used inside <Form/>, we are using an HOC (withLocalize) as function components with | ||
| // hooks give hook errors when nested inside <Form/>. | ||
| export default withLocalize(UserCurrentLocationButton); | ||
|
huzaifa-99 marked this conversation as resolved.
|
||
Uh oh!
There was an error while loading. Please reload this page.