diff --git a/app/graphql/cache.ts b/app/graphql/cache.ts index 65bec3ad6f..4bf440fd76 100644 --- a/app/graphql/cache.ts +++ b/app/graphql/cache.ts @@ -79,6 +79,9 @@ export const createCache = () => colorScheme: { read: (value) => value ?? "system", }, + countryCode: { + read: (value) => value ?? "SV", + }, feedbackModalShown: { read: (value) => value ?? false, }, diff --git a/app/graphql/client-only-query.ts b/app/graphql/client-only-query.ts index 1d6fae2f6e..06e121440c 100644 --- a/app/graphql/client-only-query.ts +++ b/app/graphql/client-only-query.ts @@ -4,6 +4,8 @@ import { BetaQuery, ColorSchemeDocument, ColorSchemeQuery, + CountryCodeDocument, + CountryCodeQuery, FeedbackModalShownDocument, FeedbackModalShownQuery, HasPromptedSetDefaultAccountDocument, @@ -16,6 +18,7 @@ import { IntroducingCirclesModalShownDocument, IntroducingCirclesModalShownQuery, } from "./generated" +import { CountryCode } from "libphonenumber-js/mobile" export default gql` query hideBalance { @@ -34,6 +37,10 @@ export default gql` colorScheme @client # "system" | "light" | "dark" } + query countryCode { + countryCode @client + } + query feedbackModalShown { feedbackModalShown @client } @@ -115,6 +122,23 @@ export const updateColorScheme = (client: ApolloClient, colorScheme: st } } +export const updateCountryCode = ( + client: ApolloClient, + countryCode: CountryCode, +) => { + try { + client.writeQuery({ + query: CountryCodeDocument, + data: { + __typename: "Query", + countryCode, + }, + }) + } catch { + console.warn("impossible to update country code") + } +} + export const setFeedbackModalShown = (client: ApolloClient, shown: boolean) => { try { client.writeQuery({ diff --git a/app/graphql/generated.gql b/app/graphql/generated.gql index e2284bf32d..40ff79b9c2 100644 --- a/app/graphql/generated.gql +++ b/app/graphql/generated.gql @@ -949,6 +949,10 @@ query conversionScreen { } } +query countryCode { + countryCode @client +} + query currencyList { currencyList { __typename diff --git a/app/graphql/generated.ts b/app/graphql/generated.ts index f3eb1ca04f..6095213169 100644 --- a/app/graphql/generated.ts +++ b/app/graphql/generated.ts @@ -1511,6 +1511,7 @@ export type Query = { readonly btcPriceList?: Maybe>>; readonly businessMapMarkers?: Maybe>>; readonly colorScheme: Scalars['String']['output']; + readonly countryCode: Scalars['String']['output']; readonly currencyList: ReadonlyArray; readonly feedbackModalShown: Scalars['Boolean']['output']; readonly globals?: Maybe; @@ -2231,6 +2232,11 @@ export type ColorSchemeQueryVariables = Exact<{ [key: string]: never; }>; export type ColorSchemeQuery = { readonly __typename: 'Query', readonly colorScheme: string }; +export type CountryCodeQueryVariables = Exact<{ [key: string]: never; }>; + + +export type CountryCodeQuery = { readonly __typename: 'Query', readonly countryCode: string }; + export type FeedbackModalShownQueryVariables = Exact<{ [key: string]: never; }>; @@ -3374,6 +3380,38 @@ export function useColorSchemeLazyQuery(baseOptions?: Apollo.LazyQueryHookOption export type ColorSchemeQueryHookResult = ReturnType; export type ColorSchemeLazyQueryHookResult = ReturnType; export type ColorSchemeQueryResult = Apollo.QueryResult; +export const CountryCodeDocument = gql` + query countryCode { + countryCode @client +} + `; + +/** + * __useCountryCodeQuery__ + * + * To run a query within a React component, call `useCountryCodeQuery` and pass it any options that fit your needs. + * When your component renders, `useCountryCodeQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useCountryCodeQuery({ + * variables: { + * }, + * }); + */ +export function useCountryCodeQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(CountryCodeDocument, options); + } +export function useCountryCodeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(CountryCodeDocument, options); + } +export type CountryCodeQueryHookResult = ReturnType; +export type CountryCodeLazyQueryHookResult = ReturnType; +export type CountryCodeQueryResult = Apollo.QueryResult; export const FeedbackModalShownDocument = gql` query feedbackModalShown { feedbackModalShown @client @@ -8107,6 +8145,7 @@ export type QueryResolvers>>, ParentType, ContextType, RequireFields>; businessMapMarkers?: Resolver>>, ParentType, ContextType>; colorScheme?: Resolver; + countryCode?: Resolver; currencyList?: Resolver, ParentType, ContextType>; feedbackModalShown?: Resolver; globals?: Resolver, ParentType, ContextType>; diff --git a/app/graphql/local-schema.gql b/app/graphql/local-schema.gql index 51e01386fa..296e025309 100644 --- a/app/graphql/local-schema.gql +++ b/app/graphql/local-schema.gql @@ -9,6 +9,7 @@ extend type Query { price: String # FIXME test only? beta: Boolean! colorScheme: String! + countryCode: String! feedbackModalShown: Boolean! hasPromptedSetDefaultAccount: Boolean! introducingCirclesModalShown: Boolean! diff --git a/app/hooks/use-device-location.ts b/app/hooks/use-device-location.ts new file mode 100644 index 0000000000..b3518d591d --- /dev/null +++ b/app/hooks/use-device-location.ts @@ -0,0 +1,51 @@ +import { useApolloClient } from "@apollo/client" +import { useEffect, useState } from "react" +import { updateCountryCode } from "@app/graphql/client-only-query" +import { useCountryCodeQuery } from "@app/graphql/generated" +import axios from "axios" +import { CountryCode } from "libphonenumber-js/mobile" + +const useDeviceLocation = () => { + const client = useApolloClient() + const { data, error } = useCountryCodeQuery() + + const [loading, setLoading] = useState(true) + const [countryCode, setCountryCode] = useState("SV") + + useEffect(() => { + if (error) { + setLoading(false) + } + }, [error]) + + useEffect(() => { + if (data) { + const getLocation = async () => { + try { + const response = await axios.get("https://ipapi.co/json/", { + timeout: 5000, + }) + const _countryCode = response?.data?.country_code + if (_countryCode) { + setCountryCode(_countryCode) + updateCountryCode(client, _countryCode) + } else { + console.warn("no data. default of SV will be used") + } + // can throw a 429 for device's rate-limiting. resort to cached value if available + } catch (err) { + setCountryCode(data.countryCode as CountryCode) + } + setLoading(false) + } + getLocation() + } + }, [data, client, setLoading, setCountryCode]) + + return { + countryCode, + loading, + } +} + +export default useDeviceLocation diff --git a/app/screens/map-screen/map-screen.tsx b/app/screens/map-screen/map-screen.tsx index 4fbdc041f6..678fcc1967 100644 --- a/app/screens/map-screen/map-screen.tsx +++ b/app/screens/map-screen/map-screen.tsx @@ -3,19 +3,20 @@ import { StackNavigationProp } from "@react-navigation/stack" import * as React from "react" import { useCallback } from "react" // eslint-disable-next-line react-native/split-platform-components -import { PermissionsAndroid, View } from "react-native" +import { ActivityIndicator, Dimensions, View } from "react-native" +import Geolocation from "@react-native-community/geolocation" import MapView, { Callout, CalloutSubview, MapMarkerProps, Marker, + Region, } from "react-native-maps" import { Screen } from "../../components/screen" import { RootStackParamList } from "../../navigation/stack-param-lists" import { isIos } from "../../utils/helper" import { toastShow } from "../../utils/toast" import { useI18nContext } from "@app/i18n/i18n-react" -import crashlytics from "@react-native-firebase/crashlytics" import { useBusinessMapMarkersQuery } from "@app/graphql/generated" import { gql } from "@apollo/client" import { useIsAuthed } from "@app/graphql/is-authed-context" @@ -24,10 +25,27 @@ import { PhoneLoginInitiateType } from "../phone-auth-screen" import { GaloyPrimaryButton } from "@app/components/atomic/galoy-primary-button" import MapStyles from "./map-styles.json" +import countryCodes from "../../../utils/countryInfo.json" +import { CountryCode } from "libphonenumber-js/mobile" +import useDeviceLocation from "@app/hooks/use-device-location" + +// essentially calculates zoom for location being set based on country +const { height, width } = Dimensions.get("window") +const LATITUDE_DELTA = 15 // <-- decrease for more zoom +const LONGITUDE_DELTA = LATITUDE_DELTA * (width / height) + type Props = { navigation: StackNavigationProp } +type GeolocationPermissionNegativeError = { + code: number + message: string + PERMISSION_DENIED: number + POSITION_UNAVAILABLE: number + TIMEOUT: number +} + gql` query businessMapMarkers { businessMapMarkers { @@ -49,8 +67,12 @@ export const MapScreen: React.FC = ({ navigation }) => { } = useTheme() const styles = useStyles() const isAuthed = useIsAuthed() + const { countryCode, loading } = useDeviceLocation() + const [isLoadingLocation, setIsLoadingLocation] = React.useState(true) + const [userLocation, setUserLocation] = React.useState() const [isRefreshed, setIsRefreshed] = React.useState(false) + const [wasLocationDenied, setLocationDenied] = React.useState(false) const { data, error, refetch } = useBusinessMapMarkersQuery({ notifyOnNetworkStatusChange: true, fetchPolicy: "cache-and-network", @@ -70,38 +92,79 @@ export const MapScreen: React.FC = ({ navigation }) => { const maps = data?.businessMapMarkers ?? [] + // if getting location was denied and device's country code has been found (or defaulted) + // this is used to finalize the initial location shown on the Map + React.useEffect(() => { + if (countryCode && wasLocationDenied && !loading) { + // JSON 'hashmap' with every countrys' code listed with their lat and lng + const countryCodesToCoords: { + data: Record + } = JSON.parse(JSON.stringify(countryCodes)) + const countryCoords: { lat: number; lng: number } = + countryCodesToCoords.data[countryCode] + if (countryCoords) { + const region: Region = { + latitude: countryCoords.lat, + longitude: countryCoords.lng, + latitudeDelta: LATITUDE_DELTA, + longitudeDelta: LONGITUDE_DELTA, + } + setUserLocation(region) + } + setIsLoadingLocation(false) + } + }, [wasLocationDenied, countryCode, loading, setIsLoadingLocation, setUserLocation]) + + const getUserRegion = (callback: (region?: Region) => void) => { + try { + Geolocation.getCurrentPosition( + (data: GeolocationPosition) => { + if (data) { + const region: Region = { + latitude: data.coords.latitude, + longitude: data.coords.longitude, + latitudeDelta: 0.02, + longitudeDelta: 0.02, + } + callback(region) + } + }, + () => { + callback(undefined) + }, + { timeout: 5000 }, + ) + } catch (e) { + callback(undefined) + } + } + const requestLocationPermission = useCallback(() => { - const asyncRequestLocationPermission = async () => { - try { - const granted = await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, - { - title: LL.MapScreen.locationPermissionTitle(), - message: LL.MapScreen.locationPermissionMessage(), - buttonNeutral: LL.MapScreen.locationPermissionNeutral(), - buttonNegative: LL.MapScreen.locationPermissionNegative(), - buttonPositive: LL.MapScreen.locationPermissionPositive(), - }, - ) - if (granted === PermissionsAndroid.RESULTS.GRANTED) { - console.debug("You can use the location") + const permittedResponse = () => { + getUserRegion(async (region) => { + if (region) { + setUserLocation(region) + setIsLoadingLocation(false) } else { - console.debug("Location permission denied") - } - } catch (err: unknown) { - if (err instanceof Error) { - crashlytics().recordError(err) + setLocationDenied(true) } - console.debug(err) - } + }) + } + + const negativeResponse = (error: GeolocationPermissionNegativeError) => { + console.debug("Permission location denied: ", error) + setLocationDenied(true) } - asyncRequestLocationPermission() - // disable eslint because we don't want to re-run this function when the language changes + + Geolocation.requestAuthorization(permittedResponse, negativeResponse) + // disable eslint because we only want to ask for permissions once // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useFocusEffect(requestLocationPermission) + // TODO this should be memoized for performance improvements. Use reduce() inside a useMemo() with some dependency array values + // and/or a generator function that yields markers asynchronously??? const markers: ReturnType>[] = [] maps.forEach((item) => { if (item) { @@ -152,19 +215,20 @@ export const MapScreen: React.FC = ({ navigation }) => { return ( - - {markers} - + {isLoadingLocation ? ( + + + + ) : ( + + {markers} + + )} ) } @@ -172,6 +236,11 @@ export const MapScreen: React.FC = ({ navigation }) => { const useStyles = makeStyles(({ colors }) => ({ android: { marginTop: 18 }, + loaderContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, customView: { alignItems: "center", margin: 12, diff --git a/app/screens/phone-auth-screen/request-phone-code-login.ts b/app/screens/phone-auth-screen/request-phone-code-login.ts index 2a987b9204..abb96ee6bf 100644 --- a/app/screens/phone-auth-screen/request-phone-code-login.ts +++ b/app/screens/phone-auth-screen/request-phone-code-login.ts @@ -32,6 +32,7 @@ export const ErrorType = { import axios, { isAxiosError } from "axios" import useAppCheckToken from "../get-started-screen/use-device-token" +import useDeviceLocation from "@app/hooks/use-device-location" type ErrorType = (typeof ErrorType)[keyof typeof ErrorType] @@ -111,6 +112,8 @@ export const useRequestPhoneCodeLogin = (): UseRequestPhoneCodeReturn => { const [captchaRequestAuthCode] = useCaptchaRequestAuthCodeMutation() const { data, loading: loadingSupportedCountries } = useSupportedCountriesQuery() + const { countryCode: detectedCountryCode, loading: loadingDetectedCountryCode } = + useDeviceLocation() const appCheckToken = useAppCheckToken({}) @@ -147,30 +150,11 @@ export const useRequestPhoneCodeLogin = (): UseRequestPhoneCodeReturn => { // setting default country code from IP useEffect(() => { - const getCountryCodeFromIP = async () => { - let defaultCountryCode = "SV" as CountryCode - try { - const response = await axios.get("https://ipapi.co/json/", { - timeout: 5000, - }) - const data = response.data - - if (data && data.country_code) { - const countryCode = data.country_code - defaultCountryCode = countryCode - } else { - console.warn("no data or country_code in response") - } - } catch (error) { - console.error(error) - } - - setCountryCode(defaultCountryCode) + if (detectedCountryCode) { + setCountryCode(detectedCountryCode) setStatus(RequestPhoneCodeStatus.InputtingPhoneNumber) } - - getCountryCodeFromIP() - }, []) + }, [detectedCountryCode]) // when phone number is submitted and either captcha is requested, or appcheck is used useEffect(() => { @@ -352,6 +336,6 @@ export const useRequestPhoneCodeLogin = (): UseRequestPhoneCodeReturn => { setCountryCode, setPhoneNumber, supportedCountries: allSupportedCountries, - loadingSupportedCountries, + loadingSupportedCountries: loadingSupportedCountries || loadingDetectedCountryCode, } } diff --git a/app/screens/phone-auth-screen/request-phone-code-registration.ts b/app/screens/phone-auth-screen/request-phone-code-registration.ts index c71a7e82e4..63a7069385 100644 --- a/app/screens/phone-auth-screen/request-phone-code-registration.ts +++ b/app/screens/phone-auth-screen/request-phone-code-registration.ts @@ -27,10 +27,10 @@ export const ErrorType = { UnsupportedCountryError: "UnsupportedCountryError", } as const -import axios from "axios" import { useNavigation } from "@react-navigation/native" import { StackNavigationProp } from "@react-navigation/stack" import { RootStackParamList } from "@app/navigation/stack-param-lists" +import useDeviceLocation from "@app/hooks/use-device-location" type ErrorType = (typeof ErrorType)[keyof typeof ErrorType] @@ -97,6 +97,7 @@ export const useRequestPhoneCodeRegistration = (): UseRequestPhoneCodeReturn => useNavigation>() const { data } = useSupportedCountriesQuery() + const { countryCode: detectedCountryCode } = useDeviceLocation() const { isWhatsAppSupported, isSmsSupported, allSupportedCountries } = useMemo(() => { const currentCountry = data?.globals?.supportedCountries.find( @@ -120,33 +121,13 @@ export const useRequestPhoneCodeRegistration = (): UseRequestPhoneCodeReturn => } }, [data?.globals, countryCode]) + // setting default country code from IP useEffect(() => { - const getCountryCodeFromIP = async () => { - let defaultCountryCode = "SV" as CountryCode - try { - const response = await axios({ - method: "get", - url: "https://ipapi.co/json/", - timeout: 5000, - }) - const data = response.data - - if (data && data.country_code) { - const countryCode = data.country_code - defaultCountryCode = countryCode - } else { - console.warn("no data or country_code in response") - } - } catch (error) { - console.error(error) - } - - setCountryCode(defaultCountryCode) + if (detectedCountryCode) { + setCountryCode(detectedCountryCode) setStatus(RequestPhoneCodeStatus.InputtingPhoneNumber) } - - getCountryCodeFromIP() - }, []) + }, [detectedCountryCode]) const setPhoneNumber = (number: string) => { if (status === RequestPhoneCodeStatus.RequestingCode) { diff --git a/ios/GaloyApp.xcodeproj/project.pbxproj b/ios/GaloyApp.xcodeproj/project.pbxproj index 405bbd7d90..e706e10484 100644 --- a/ios/GaloyApp.xcodeproj/project.pbxproj +++ b/ios/GaloyApp.xcodeproj/project.pbxproj @@ -562,7 +562,8 @@ OTHER_CPLUSPLUSFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", - " ", + "-Wl", + "-ld_classic", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -628,7 +629,8 @@ OTHER_CPLUSPLUSFLAGS = "$(inherited)"; OTHER_LDFLAGS = ( "$(inherited)", - " ", + "-Wl", + "-ld_classic", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 79fb9ce58f..8891d6fc6f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -448,6 +448,8 @@ PODS: - react-native-geetest-module (0.1.5): - GT3Captcha-iOS - React-Core + - react-native-geolocation (3.1.0): + - React-Core - react-native-image-picker (7.0.3): - React-Core - react-native-in-app-review (4.3.3): @@ -704,6 +706,7 @@ DEPENDENCIES: - react-native-config (from `../node_modules/react-native-config`) - react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`) - "react-native-geetest-module (from `../node_modules/@galoymoney/react-native-geetest-module`)" + - "react-native-geolocation (from `../node_modules/@react-native-community/geolocation`)" - react-native-image-picker (from `../node_modules/react-native-image-picker`) - react-native-in-app-review (from `../node_modules/react-native-in-app-review`) - react-native-maps (from `../node_modules/react-native-maps`) @@ -837,6 +840,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-fingerprint-scanner" react-native-geetest-module: :path: "../node_modules/@galoymoney/react-native-geetest-module" + react-native-geolocation: + :path: "../node_modules/@react-native-community/geolocation" react-native-image-picker: :path: "../node_modules/react-native-image-picker" react-native-in-app-review: @@ -944,7 +949,7 @@ SPEC CHECKSUMS: AppCheckCore: 4687c03428947d5b26a6bf9bb5566b39f5473bf1 boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3 - DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 + DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 5fbbff1d7734827299274638deb8ba3024f6c597 FBReactNativeSpec: 638095fe8a01506634d77b260ef8a322019ac671 Firebase: 414ad272f8d02dfbf12662a9d43f4bba9bec2a06 @@ -962,7 +967,7 @@ SPEC CHECKSUMS: FirebaseSessions: f90fe9212ee2818641eda051c0835c9c4e30d9ae FirebaseSharedSwift: 62e248642c0582324d0390706cadd314687c116b fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 035f1e36e53b355cf70f6434d161b36e7d21fecd + glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b GoogleAppMeasurement: 70ce9aa438cff1cfb31ea3e660bcc67734cb716e GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2 GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34 @@ -990,6 +995,7 @@ SPEC CHECKSUMS: react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8 react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe react-native-geetest-module: ed6a20774a7975640b79a2f639327c674a488cb5 + react-native-geolocation: ef66fb798d96284c6043f0b16c15d9d1d4955db4 react-native-image-picker: 2381c008bbb09e72395a2d043c147b11bd1523d9 react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4 react-native-maps: f699e0753c22c4d5c3a44d03895b193a4dbca6c2 @@ -1046,4 +1052,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 74b5a90bf745a883167a4889de7db80dfc529ced -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.3 diff --git a/package.json b/package.json index 35ed9f356b..f81cebc83c 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@galoymoney/react-native-geetest-module": "^0.1.3", "@react-native-async-storage/async-storage": "^1.19.3", "@react-native-clipboard/clipboard": "^1.12.1", + "@react-native-community/geolocation": "^3.1.0", "@react-native-firebase/analytics": "^18.6.0", "@react-native-firebase/app": "^18.6.0", "@react-native-firebase/app-check": "^18.6.0", diff --git a/utils/countryInfo.json b/utils/countryInfo.json new file mode 100644 index 0000000000..5d7b51ab76 --- /dev/null +++ b/utils/countryInfo.json @@ -0,0 +1,247 @@ +{ + "data": { + "AD": { "lat": 42.5, "lng": 1.6 }, + "AE": { "lat": 24, "lng": 54 }, + "AF": { "lat": 33, "lng": 65 }, + "AG": { "lat": 17.05, "lng": -61.8 }, + "AI": { "lat": 18.25, "lng": -63.1667 }, + "AL": { "lat": 41, "lng": 20 }, + "AM": { "lat": 40, "lng": 45 }, + "AN": { "lat": 12.25, "lng": -68.75 }, + "AO": { "lat": -12.5, "lng": 18.5 }, + "AQ": { "lat": -90, "lng": 0 }, + "AR": { "lat": -34, "lng": -64 }, + "AS": { "lat": -14.3333, "lng": -170 }, + "AT": { "lat": 47.3333, "lng": 13.3333 }, + "AU": { "lat": -27, "lng": 133 }, + "AW": { "lat": 12.5, "lng": -69.9667 }, + "AZ": { "lat": 40.5, "lng": 47.5 }, + "BA": { "lat": 44, "lng": 18 }, + "BB": { "lat": 13.1667, "lng": -59.5333 }, + "BD": { "lat": 24, "lng": 90 }, + "BE": { "lat": 50.8333, "lng": 4 }, + "BF": { "lat": 13, "lng": -2 }, + "BG": { "lat": 43, "lng": 25 }, + "BH": { "lat": 26, "lng": 50.55 }, + "BI": { "lat": -3.5, "lng": 30 }, + "BJ": { "lat": 9.5, "lng": 2.25 }, + "BM": { "lat": 32.3333, "lng": -64.75 }, + "BN": { "lat": 4.5, "lng": 114.6667 }, + "BO": { "lat": -17, "lng": -65 }, + "BR": { "lat": -10, "lng": -55 }, + "BS": { "lat": 24.25, "lng": -76 }, + "BT": { "lat": 27.5, "lng": 90.5 }, + "BV": { "lat": -54.4333, "lng": 3.4 }, + "BW": { "lat": -22, "lng": 24 }, + "BY": { "lat": 53, "lng": 28 }, + "BZ": { "lat": 17.25, "lng": -88.75 }, + "CA": { "lat": 60, "lng": -95 }, + "CC": { "lat": -12.5, "lng": 96.8333 }, + "CD": { "lat": 0, "lng": 25 }, + "CF": { "lat": 7, "lng": 21 }, + "CG": { "lat": -1, "lng": 15 }, + "CH": { "lat": 47, "lng": 8 }, + "CI": { "lat": 8, "lng": -5 }, + "CK": { "lat": -21.2333, "lng": -159.7667 }, + "CL": { "lat": -30, "lng": -71 }, + "CM": { "lat": 6, "lng": 12 }, + "CN": { "lat": 35, "lng": 105 }, + "CO": { "lat": 4, "lng": -72 }, + "CR": { "lat": 10, "lng": -84 }, + "CU": { "lat": 21.5, "lng": -80 }, + "CV": { "lat": 16, "lng": -24 }, + "CX": { "lat": -10.5, "lng": 105.6667 }, + "CY": { "lat": 35, "lng": 33 }, + "CZ": { "lat": 49.75, "lng": 15.5 }, + "DE": { "lat": 51, "lng": 9 }, + "DJ": { "lat": 11.5, "lng": 43 }, + "DK": { "lat": 56, "lng": 10 }, + "DM": { "lat": 15.4167, "lng": -61.3333 }, + "DO": { "lat": 19, "lng": -70.6667 }, + "DZ": { "lat": 28, "lng": 3 }, + "EC": { "lat": -2, "lng": -77.5 }, + "EE": { "lat": 59, "lng": 26 }, + "EG": { "lat": 27, "lng": 30 }, + "EH": { "lat": 24.5, "lng": -13 }, + "ER": { "lat": 15, "lng": 39 }, + "ES": { "lat": 40, "lng": -4 }, + "ET": { "lat": 8, "lng": 38 }, + "FI": { "lat": 64, "lng": 26 }, + "FJ": { "lat": -18, "lng": 175 }, + "FK": { "lat": -51.75, "lng": -59 }, + "FM": { "lat": 6.9167, "lng": 158.25 }, + "FO": { "lat": 62, "lng": -7 }, + "FR": { "lat": 46, "lng": 2 }, + "GA": { "lat": -1, "lng": 11.75 }, + "GB": { "lat": 54, "lng": -2 }, + "GD": { "lat": 12.1167, "lng": -61.6667 }, + "GE": { "lat": 42, "lng": 43.5 }, + "GF": { "lat": 4, "lng": -53 }, + "GG": { "lat": 49.5, "lng": -2.56 }, + "GH": { "lat": 8, "lng": -2 }, + "GI": { "lat": 36.1833, "lng": -5.3667 }, + "GL": { "lat": 72, "lng": -40 }, + "GM": { "lat": 13.4667, "lng": -16.5667 }, + "GN": { "lat": 11, "lng": -10 }, + "GP": { "lat": 16.25, "lng": -61.5833 }, + "GQ": { "lat": 2, "lng": 10 }, + "GR": { "lat": 39, "lng": 22 }, + "GS": { "lat": -54.5, "lng": -37 }, + "GT": { "lat": 15.5, "lng": -90.25 }, + "GU": { "lat": 13.4667, "lng": 144.7833 }, + "GW": { "lat": 12, "lng": -15 }, + "GY": { "lat": 5, "lng": -59 }, + "HK": { "lat": 22.25, "lng": 114.1667 }, + "HM": { "lat": -53.1, "lng": 72.5167 }, + "HN": { "lat": 15, "lng": -86.5 }, + "HR": { "lat": 45.1667, "lng": 15.5 }, + "HT": { "lat": 19, "lng": -72.4167 }, + "HU": { "lat": 47, "lng": 20 }, + "ID": { "lat": -5, "lng": 120 }, + "IE": { "lat": 53, "lng": -8 }, + "IL": { "lat": 31.5, "lng": 34.75 }, + "IM": { "lat": 54.23, "lng": -4.55 }, + "IN": { "lat": 20, "lng": 77 }, + "IO": { "lat": -6, "lng": 71.5 }, + "IQ": { "lat": 33, "lng": 44 }, + "IR": { "lat": 32, "lng": 53 }, + "IS": { "lat": 65, "lng": -18 }, + "IT": { "lat": 42.8333, "lng": 12.8333 }, + "JE": { "lat": 49.21, "lng": -2.13 }, + "JM": { "lat": 18.25, "lng": -77.5 }, + "JO": { "lat": 31, "lng": 36 }, + "JP": { "lat": 36, "lng": 138 }, + "KE": { "lat": 1, "lng": 38 }, + "KG": { "lat": 41, "lng": 75 }, + "KH": { "lat": 13, "lng": 105 }, + "KI": { "lat": 1.4167, "lng": 173 }, + "KM": { "lat": -12.1667, "lng": 44.25 }, + "KN": { "lat": 17.3333, "lng": -62.75 }, + "KP": { "lat": 40, "lng": 127 }, + "KR": { "lat": 37, "lng": 127.5 }, + "KW": { "lat": 29.3375, "lng": 47.6581 }, + "KY": { "lat": 19.5, "lng": -80.5 }, + "KZ": { "lat": 48, "lng": 68 }, + "LA": { "lat": 18, "lng": 105 }, + "LB": { "lat": 33.8333, "lng": 35.8333 }, + "LC": { "lat": 13.8833, "lng": -61.1333 }, + "LI": { "lat": 47.1667, "lng": 9.5333 }, + "LK": { "lat": 7, "lng": 81 }, + "LR": { "lat": 6.5, "lng": -9.5 }, + "LS": { "lat": -29.5, "lng": 28.5 }, + "LT": { "lat": 56, "lng": 24 }, + "LU": { "lat": 49.75, "lng": 6.1667 }, + "LV": { "lat": 57, "lng": 25 }, + "LY": { "lat": 25, "lng": 17 }, + "MA": { "lat": 32, "lng": -5 }, + "MC": { "lat": 43.7333, "lng": 7.4 }, + "MD": { "lat": 47, "lng": 29 }, + "ME": { "lat": 42, "lng": 19 }, + "MG": { "lat": -20, "lng": 47 }, + "MH": { "lat": 9, "lng": 168 }, + "MK": { "lat": 41.8333, "lng": 22 }, + "ML": { "lat": 17, "lng": -4 }, + "MM": { "lat": 22, "lng": 98 }, + "MN": { "lat": 46, "lng": 105 }, + "MO": { "lat": 22.1667, "lng": 113.55 }, + "MP": { "lat": 15.2, "lng": 145.75 }, + "MQ": { "lat": 14.6667, "lng": -61 }, + "MR": { "lat": 20, "lng": -12 }, + "MS": { "lat": 16.75, "lng": -62.2 }, + "MT": { "lat": 35.8333, "lng": 14.5833 }, + "MU": { "lat": -20.2833, "lng": 57.55 }, + "MV": { "lat": 3.25, "lng": 73 }, + "MW": { "lat": -13.5, "lng": 34 }, + "MX": { "lat": 23, "lng": -102 }, + "MY": { "lat": 2.5, "lng": 112.5 }, + "MZ": { "lat": -18.25, "lng": 35 }, + "NA": { "lat": -22, "lng": 17 }, + "NC": { "lat": -21.5, "lng": 165.5 }, + "NE": { "lat": 16, "lng": 8 }, + "NF": { "lat": -29.0333, "lng": 167.95 }, + "NG": { "lat": 10, "lng": 8 }, + "NI": { "lat": 13, "lng": -85 }, + "NL": { "lat": 52.5, "lng": 5.75 }, + "NO": { "lat": 62, "lng": 10 }, + "NP": { "lat": 28, "lng": 84 }, + "NR": { "lat": -0.5333, "lng": 166.9167 }, + "NU": { "lat": -19.0333, "lng": -169.8667 }, + "NZ": { "lat": -41, "lng": 174 }, + "OM": { "lat": 21, "lng": 57 }, + "PA": { "lat": 9, "lng": -80 }, + "PE": { "lat": -10, "lng": -76 }, + "PF": { "lat": -15, "lng": -140 }, + "PG": { "lat": -6, "lng": 147 }, + "PH": { "lat": 13, "lng": 122 }, + "PK": { "lat": 30, "lng": 70 }, + "PL": { "lat": 52, "lng": 20 }, + "PM": { "lat": 46.8333, "lng": -56.3333 }, + "PN": { "lat": -24.7, "lng": -127.4 }, + "PR": { "lat": 18.25, "lng": -66.5 }, + "PS": { "lat": 32, "lng": 35.25 }, + "PT": { "lat": 39.5, "lng": -8 }, + "PW": { "lat": 7.5, "lng": 134.5 }, + "PY": { "lat": -23, "lng": -58 }, + "QA": { "lat": 25.5, "lng": 51.25 }, + "RE": { "lat": -21.1, "lng": 55.6 }, + "RO": { "lat": 46, "lng": 25 }, + "RS": { "lat": 44, "lng": 21 }, + "RU": { "lat": 60, "lng": 100 }, + "RW": { "lat": -2, "lng": 30 }, + "SA": { "lat": 25, "lng": 45 }, + "SB": { "lat": -8, "lng": 159 }, + "SC": { "lat": -4.5833, "lng": 55.6667 }, + "SD": { "lat": 15, "lng": 30 }, + "SE": { "lat": 62, "lng": 15 }, + "SG": { "lat": 1.3667, "lng": 103.8 }, + "SH": { "lat": -15.9333, "lng": -5.7 }, + "SI": { "lat": 46, "lng": 15 }, + "SJ": { "lat": 78, "lng": 20 }, + "SK": { "lat": 48.6667, "lng": 19.5 }, + "SL": { "lat": 8.5, "lng": -11.5 }, + "SM": { "lat": 43.7667, "lng": 12.4167 }, + "SN": { "lat": 14, "lng": -14 }, + "SO": { "lat": 10, "lng": 49 }, + "SR": { "lat": 4, "lng": -56 }, + "ST": { "lat": 1, "lng": 7 }, + "SV": { "lat": 13.8333, "lng": -88.9167 }, + "SY": { "lat": 35, "lng": 38 }, + "SZ": { "lat": -26.5, "lng": 31.5 }, + "TC": { "lat": 21.75, "lng": -71.5833 }, + "TD": { "lat": 15, "lng": 19 }, + "TF": { "lat": -43, "lng": 67 }, + "TG": { "lat": 8, "lng": 1.1667 }, + "TH": { "lat": 15, "lng": 100 }, + "TJ": { "lat": 39, "lng": 71 }, + "TK": { "lat": -9, "lng": -172 }, + "TL": { "lat": -8.55, "lng": 125.5167 }, + "TM": { "lat": 40, "lng": 60 }, + "TN": { "lat": 34, "lng": 9 }, + "TO": { "lat": -20, "lng": -175 }, + "TR": { "lat": 39, "lng": 35 }, + "TT": { "lat": 11, "lng": -61 }, + "TV": { "lat": -8, "lng": 178 }, + "TW": { "lat": 23.5, "lng": 121 }, + "TZ": { "lat": -6, "lng": 35 }, + "UA": { "lat": 49, "lng": 32 }, + "UG": { "lat": 1, "lng": 32 }, + "UM": { "lat": 19.2833, "lng": 166.6 }, + "US": { "lat": 38, "lng": -97 }, + "UY": { "lat": -33, "lng": -56 }, + "UZ": { "lat": 41, "lng": 64 }, + "VA": { "lat": 41.9, "lng": 12.45 }, + "VC": { "lat": 13.25, "lng": -61.2 }, + "VE": { "lat": 8, "lng": -66 }, + "VG": { "lat": 18.5, "lng": -64.5 }, + "VI": { "lat": 18.3333, "lng": -64.8333 }, + "VN": { "lat": 16, "lng": 106 }, + "VU": { "lat": -16, "lng": 167 }, + "WF": { "lat": -13.3, "lng": -176.2 }, + "WS": { "lat": -13.5833, "lng": -172.3333 }, + "YE": { "lat": 15, "lng": 48 }, + "YT": { "lat": -12.8333, "lng": 45.1667 }, + "ZA": { "lat": -29, "lng": 24 }, + "ZM": { "lat": -15, "lng": 30 }, + "ZW": { "lat": -20, "lng": 30 } + } +} diff --git a/yarn.lock b/yarn.lock index 6819697273..7729cee1b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4091,6 +4091,11 @@ dependencies: invariant "^2.2.4" +"@react-native-community/geolocation@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@react-native-community/geolocation/-/geolocation-3.1.0.tgz#573881e9536d55cc2af74762183fa7ba575b2553" + integrity sha512-Aj76wMeCLz9kczpe7W2IXxSRNj+hRR7sqyfXq9ZaJIWNH23jeJfzMpovclg+e1uM8Vr8jCYEcZyD4rKub6Vc/Q== + "@react-native-community/slider@^4.4.2": version "4.4.3" resolved "https://registry.npmjs.org/@react-native-community/slider/-/slider-4.4.3.tgz#9b9dc639b88f5bfda72bd72a9dff55cbf9f777ed"