diff --git a/src/components/LocationPicker/CrosshairCircle.js b/src/components/LocationPicker/CrosshairCircle.js
index 959d17bc4..bee6df471 100644
--- a/src/components/LocationPicker/CrosshairCircle.js
+++ b/src/components/LocationPicker/CrosshairCircle.js
@@ -7,18 +7,19 @@ import type { Node } from "react";
import React from "react";
import { useTheme } from "react-native-paper";
+import WarningText from "./WarningText";
+
type Props = {
accuracyTest: string,
- containerStyle: Object
+ getShadow: Function
};
-const CrosshairCircle = ( { accuracyTest, containerStyle }: Props ): Node => {
+const CrosshairCircle = ( { accuracyTest, getShadow }: Props ): Node => {
const theme = useTheme( );
return (
{
/>
)}
+
+
+
);
};
diff --git a/src/components/LocationPicker/Footer.js b/src/components/LocationPicker/Footer.js
index e66cd3e9d..3d6228871 100644
--- a/src/components/LocationPicker/Footer.js
+++ b/src/components/LocationPicker/Footer.js
@@ -1,14 +1,11 @@
// @flow
import { useNavigation } from "@react-navigation/native";
-import {
- Button, StickyToolbar
-} from "components/SharedComponents";
+import { Button } from "components/SharedComponents";
+import { View } from "components/styledComponents";
import { ObsEditContext } from "providers/contexts";
import type { Node } from "react";
-import React, {
- useContext
-} from "react";
+import React, { useContext } from "react";
import useTranslation from "sharedHooks/useTranslation";
type Props = {
@@ -24,9 +21,9 @@ const Footer = ( { keysToUpdate, goBackOnSave }: Props ): Node => {
} = useContext( ObsEditContext );
return (
-
+
+
);
};
diff --git a/src/components/LocationPicker/LoadingIndicator.js b/src/components/LocationPicker/LoadingIndicator.js
new file mode 100644
index 000000000..4127a7c11
--- /dev/null
+++ b/src/components/LocationPicker/LoadingIndicator.js
@@ -0,0 +1,14 @@
+// @flow
+
+import { View } from "components/styledComponents";
+import type { Node } from "react";
+import React from "react";
+import { ActivityIndicator } from "react-native-paper";
+
+const LoadingIndicator = ( ): Node => (
+
+
+
+);
+
+export default LoadingIndicator;
diff --git a/src/components/LocationPicker/LocationPicker.js b/src/components/LocationPicker/LocationPicker.js
index 7c489482b..5cee5d16c 100644
--- a/src/components/LocationPicker/LocationPicker.js
+++ b/src/components/LocationPicker/LocationPicker.js
@@ -1,30 +1,27 @@
// @flow
-import { useNavigation } from "@react-navigation/native";
import {
+ Body3,
CloseButton, Heading4,
+ INatIconButton,
ViewWrapper
} from "components/SharedComponents";
import { View } from "components/styledComponents";
-import { ObsEditContext } from "providers/contexts";
import type { Node } from "react";
-import React, {
- useCallback,
- useContext, useEffect, useRef, useState
-} from "react";
-import { Dimensions } from "react-native";
+import React from "react";
import MapView from "react-native-maps";
-import { IconButton, useTheme } from "react-native-paper";
-import fetchPlaceName from "sharedHelpers/fetchPlaceName";
-import fetchUserLocation from "sharedHelpers/fetchUserLocation";
+import { useTheme } from "react-native-paper";
import useTranslation from "sharedHooks/useTranslation";
import { getShadowStyle } from "styles/global";
import CrosshairCircle from "./CrosshairCircle";
import DisplayLatLng from "./DisplayLatLng";
import Footer from "./Footer";
+import LoadingIndicator from "./LoadingIndicator";
import LocationSearch from "./LocationSearch";
-import WarningText from "./WarningText";
+
+export const DESIRED_LOCATION_ACCURACY = 100;
+export const REQUIRED_LOCATION_ACCURACY = 500000;
const getShadow = shadowColor => getShadowStyle( {
shadowColor,
@@ -35,152 +32,131 @@ const getShadow = shadowColor => getShadowStyle( {
elevation: 5
} );
-const { width, height } = Dimensions.get( "screen" );
-
-const DELTA = 0.2;
-const CROSSHAIRLENGTH = 254;
-export const DESIRED_LOCATION_ACCURACY = 100;
-export const REQUIRED_LOCATION_ACCURACY = 500000;
-
type Props = {
- route: {
- params: {
- goBackOnSave: boolean
- },
- },
+ showMap: boolean,
+ loading: boolean,
+ accuracyTest: string,
+ region: Object,
+ mapView: any,
+ updateRegion: Function,
+ locationName: ?string,
+ updateLocationName: Function,
+ accuracy: number,
+ returnToUserLocation: Function,
+ keysToUpdate: Object,
+ goBackOnSave: Function,
+ toggleMapLayer: Function,
+ mapType: string,
+ setMapReady: Function,
+ selectPlaceResult: Function,
+ hidePlaceResults: boolean
};
-const centerCrosshair = ( height / 2 ) - CROSSHAIRLENGTH + 30;
-
-const LocationPicker = ( { route }: Props ): Node => {
+const LocationPicker = ( {
+ showMap,
+ loading,
+ accuracyTest,
+ region,
+ mapView,
+ updateRegion,
+ locationName,
+ updateLocationName,
+ accuracy,
+ returnToUserLocation,
+ keysToUpdate,
+ goBackOnSave,
+ toggleMapLayer,
+ mapType,
+ setMapReady,
+ selectPlaceResult,
+ hidePlaceResults
+}: Props ): Node => {
const theme = useTheme( );
const { t } = useTranslation( );
- const mapView = useRef( );
- const { currentObservation } = useContext( ObsEditContext );
- const navigation = useNavigation( );
- const { goBackOnSave } = route.params;
- const [mapType, setMapType] = useState( "standard" );
- const [locationName, setLocationName] = useState( currentObservation?.place_guess );
- const [accuracy, setAccuracy] = useState( currentObservation?.positional_accuracy );
- const [accuracyTest, setAccuracyTest] = useState( "pass" );
- const [region, setRegion] = useState( {
- latitude: currentObservation?.latitude || 0.0,
- longitude: currentObservation?.longitude || 0.0,
- latitudeDelta: DELTA,
- longitudeDelta: DELTA
- } );
-
- const keysToUpdate = {
- latitude: region.latitude,
- longitude: region.longitude,
- positional_accuracy: accuracy,
- place_guess: locationName
- };
-
- useEffect( ( ) => {
- if ( accuracy < DESIRED_LOCATION_ACCURACY ) {
- setAccuracyTest( "pass" );
- } else if ( accuracy < REQUIRED_LOCATION_ACCURACY ) {
- setAccuracyTest( "acceptable" );
- } else {
- setAccuracyTest( "fail" );
- }
- }, [accuracy] );
-
- const updateRegion = async newRegion => {
- const estimatedAccuracy = newRegion.longitudeDelta * 1000 * (
- ( CROSSHAIRLENGTH / width ) * 100
- );
-
- const placeName = await fetchPlaceName( newRegion.latitude, newRegion.longitude );
- if ( placeName ) {
- setLocationName( placeName );
- }
- setRegion( newRegion );
- setAccuracy( estimatedAccuracy );
- };
-
- const renderBackButton = useCallback(
- ( ) => ,
- []
- );
-
- useEffect( ( ) => {
- const renderHeaderTitle = ( ) => {t( "EDIT-LOCATION" )};
-
- const headerOptions = {
- headerRight: renderBackButton,
- headerTitle: renderHeaderTitle
- };
-
- navigation.setOptions( headerOptions );
- }, [renderBackButton, navigation, t] );
-
- const toggleMapLayer = ( ) => {
- if ( mapType === "standard" ) {
- setMapType( "satellite" );
- } else {
- setMapType( "standard" );
- }
- };
-
- const returnToUserLocation = async ( ) => {
- const userLocation = await fetchUserLocation( );
- setRegion( {
- ...region,
- latitude: userLocation?.latitude,
- longitude: userLocation?.longitude
- } );
- };
return (
-
- {
- updateRegion( newRegion );
- // console.log( await mapView?.current?.getMapBoundaries( ) );
- }}
- />
-
-
-
-
-
-
+
+ {t( "EDIT-LOCATION" )}
+
+
+
+
+
+
-
-
+
+
+ {showMap && (
+
+ )}
+
+
+ {loading && }
+
+
+ {showMap
+ ? (
+ {
+ updateRegion( newRegion );
+ }}
+ onMapReady={setMapReady}
+ />
+ )
+ : (
+
+ {t( "Try-searching-for-a-location-name" )}
+
+ )}
+
+
+
+
+
+
+
-
+
{comment
? (
diff --git a/src/components/SharedComponents/Buttons/CloseButton.js b/src/components/SharedComponents/Buttons/CloseButton.js
index 857d7196e..9dd2b7c35 100644
--- a/src/components/SharedComponents/Buttons/CloseButton.js
+++ b/src/components/SharedComponents/Buttons/CloseButton.js
@@ -8,7 +8,6 @@ import React from "react";
import { useTheme } from "react-native-paper";
type Props = {
- className?: string,
handleClose?: Function,
black?: boolean,
size?: number,
@@ -18,7 +17,7 @@ type Props = {
}
const CloseButton = ( {
- className, handleClose, black, size, icon,
+ handleClose, black, size, icon,
width, height
}: Props ): Node => {
const navigation = useNavigation( );
@@ -28,7 +27,6 @@ const CloseButton = ( {
+
{t( "unknown" )}
-
+
);
}
@@ -88,7 +88,7 @@ const DisplayTaxonName = ( {
: "";
const text = piece + spaceChar;
const TextComponent = scientificNameFirst || !commonName
- ? Body1
+ ? Body1Bold
: Body3;
return (
isItalics
@@ -110,7 +110,7 @@ const DisplayTaxonName = ( {
}
const TopTextComponent = !small
- ? Body1
+ ? Body1Bold
: Body3;
const BottomTextComponent = !small
? Body3
diff --git a/src/components/SharedComponents/PhotoCount.js b/src/components/SharedComponents/PhotoCount.js
index 32d9ce0c5..ba84f02f8 100644
--- a/src/components/SharedComponents/PhotoCount.js
+++ b/src/components/SharedComponents/PhotoCount.js
@@ -1,9 +1,10 @@
// @flow
import { useIsFocused } from "@react-navigation/native";
-import { Body3 } from "components/SharedComponents";
+import { Body3, Body3Bold } from "components/SharedComponents";
import { View } from "components/styledComponents";
import * as React from "react";
+import { Platform } from "react-native";
import { useTheme } from "react-native-paper";
import Svg, { ForeignObject, Path } from "react-native-svg";
import { dropShadow } from "styles/global";
@@ -34,6 +35,10 @@ const PhotoCount = ( { count, size, shadow }: Props ): React.Node => {
photoCount = 99;
}
+ const TextComponent = Platform.OS === "ios"
+ ? Body3
+ : Body3Bold;
+
return (
{
clipRule="evenodd"
fillRule="nonzero"
/>
-
-
+
+
{photoCount}
-
+
diff --git a/src/components/SharedComponents/SearchBar.js b/src/components/SharedComponents/SearchBar.js
index 9dfb14a29..c4ba529cb 100644
--- a/src/components/SharedComponents/SearchBar.js
+++ b/src/components/SharedComponents/SearchBar.js
@@ -1,7 +1,8 @@
// @flow
import { INatIcon } from "components/SharedComponents";
import { View } from "components/styledComponents";
-import * as React from "react";
+import type { Node } from "react";
+import React from "react";
import { Platform } from "react-native";
import { TextInput, useTheme } from "react-native-paper";
import { getShadowStyle } from "styles/global";
@@ -20,7 +21,8 @@ type Props = {
handleTextChange: Function,
value: string,
testID?: string,
- hasShadow?: boolean
+ hasShadow?: boolean,
+ input?: any
}
// Ensure this component is placed outside of scroll views
@@ -30,13 +32,15 @@ const SearchBar = ( {
testID,
handleTextChange,
value,
- hasShadow
-}: Props ): React.Node => {
+ hasShadow,
+ input
+}: Props ): Node => {
const theme = useTheme( );
return (
(
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+);
+
+export default Body1Bold;
diff --git a/src/components/SharedComponents/Typography/Body3Bold.js b/src/components/SharedComponents/Typography/Body3Bold.js
new file mode 100644
index 000000000..47db5b591
--- /dev/null
+++ b/src/components/SharedComponents/Typography/Body3Bold.js
@@ -0,0 +1,13 @@
+// @flow
+
+import type { Node } from "react";
+import React from "react";
+
+import INatText from "./INatText";
+
+const Body3Bold = ( props: any ): Node => (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+);
+
+export default Body3Bold;
diff --git a/src/components/SharedComponents/index.js b/src/components/SharedComponents/index.js
index 5a27c5fc6..8c7466462 100644
--- a/src/components/SharedComponents/index.js
+++ b/src/components/SharedComponents/index.js
@@ -31,8 +31,10 @@ export { default as StickyToolbar } from "./StickyToolbar";
export { default as Tabs } from "./Tabs/Tabs";
export { default as TaxonResult } from "./TaxonResult";
export { default as Body1 } from "./Typography/Body1";
+export { default as Body1Bold } from "./Typography/Body1Bold";
export { default as Body2 } from "./Typography/Body2";
export { default as Body3 } from "./Typography/Body3";
+export { default as Body3Bold } from "./Typography/Body3Bold";
export { default as Body4 } from "./Typography/Body4";
export { default as Heading1 } from "./Typography/Heading1";
export { default as Heading2 } from "./Typography/Heading2";
diff --git a/src/i18n/l10n/en.ftl b/src/i18n/l10n/en.ftl
index e47968376..0347bc3a5 100644
--- a/src/i18n/l10n/en.ftl
+++ b/src/i18n/l10n/en.ftl
@@ -1192,5 +1192,6 @@ SETTINGS = SETTINGS
LOG-OUT = LOG OUT
Log-in-to-iNaturalist = Log in to iNaturalist
+Try-searching-for-a-location-name = Try searching for a location name to see the map
Scan-the-area-around-you-for-organisms = Scan the area around you for organisms.
-Loading-iNaturalists-AR-Camera = Loading iNaturalist’s AR Camera
\ No newline at end of file
+Loading-iNaturalists-AR-Camera = Loading iNaturalist’s AR Camera
diff --git a/src/i18n/l10n/en.ftl.json b/src/i18n/l10n/en.ftl.json
index 87ea7324b..a3e51a093 100644
--- a/src/i18n/l10n/en.ftl.json
+++ b/src/i18n/l10n/en.ftl.json
@@ -822,6 +822,7 @@
"SETTINGS": "SETTINGS",
"LOG-OUT": "LOG OUT",
"Log-in-to-iNaturalist": "Log in to iNaturalist",
+ "Try-searching-for-a-location-name": "Try searching for a location name to see the map",
"Scan-the-area-around-you-for-organisms": "Scan the area around you for organisms.",
"Loading-iNaturalists-AR-Camera": "Loading iNaturalist’s AR Camera"
}
diff --git a/src/i18n/strings.ftl b/src/i18n/strings.ftl
index e47968376..0347bc3a5 100644
--- a/src/i18n/strings.ftl
+++ b/src/i18n/strings.ftl
@@ -1192,5 +1192,6 @@ SETTINGS = SETTINGS
LOG-OUT = LOG OUT
Log-in-to-iNaturalist = Log in to iNaturalist
+Try-searching-for-a-location-name = Try searching for a location name to see the map
Scan-the-area-around-you-for-organisms = Scan the area around you for organisms.
-Loading-iNaturalists-AR-Camera = Loading iNaturalist’s AR Camera
\ No newline at end of file
+Loading-iNaturalists-AR-Camera = Loading iNaturalist’s AR Camera
diff --git a/src/navigation/BottomTabNavigator/index.js b/src/navigation/BottomTabNavigator/index.js
index bc6c45515..71bda75f5 100644
--- a/src/navigation/BottomTabNavigator/index.js
+++ b/src/navigation/BottomTabNavigator/index.js
@@ -4,7 +4,7 @@ import AddID from "components/AddID/AddID";
import CameraContainer from "components/Camera/CameraContainer";
import Explore from "components/Explore/Explore";
import Identify from "components/Identify/Identify";
-import LocationPicker from "components/LocationPicker/LocationPicker";
+import LocationPickerContainer from "components/LocationPicker/LocationPickerContainer";
import ForgotPassword from "components/LoginSignUp/ForgotPassword";
import LicensePhotos from "components/LoginSignUp/LicensePhotos";
import Login from "components/LoginSignUp/Login";
@@ -336,11 +336,8 @@ const BottomTabs = ( ) => {
/>
{
- const actualNav = jest.requireActual( "@react-navigation/native" );
- return {
- ...actualNav,
- useNavigation: ( ) => ( {
- setOptions: jest.fn( )
- } )
- };
-} );
-
-// Mock ObservationProvider so it provides a specific array of observations
-// without any current observation or ability to update or fetch
-// observations
-jest.mock( "providers/ObsEditProvider" );
-const mockObsEditProviderWithObs = obs => ObsEditProvider.mockImplementation( ( { children } ) => (
- // eslint-disable-next-line react/jsx-no-constructed-context-values
-
- {children}
-
-) );
-
-const renderLocationPicker = ( ) => renderComponent(
-
-
-
-);
+const observations = [
+ factory( "RemoteObservation", {
+ // Oakland, CA latlng
+ latitude: 37.804855,
+ longitude: -122.272504
+ } )
+];
const mockPlaceResult = factory( "RemotePlace", {
display_name: "New York",
@@ -62,6 +33,31 @@ jest.mock( "sharedHooks/useAuthenticatedQuery", ( ) => ( {
} )
} ) );
+const mockSelectPlaceResult = jest.fn( );
+const mockRegion = {
+ latitude: observations[0].latitude,
+ longitude: observations[0].longitude,
+ latitudeDelta: 0.2,
+ longitudeDelta: 0.2
+};
+
+const renderLocationPicker = region => renderComponent(
+
+ jest.fn( location )}
+ hidePlaceResults={false}
+ selectPlaceResult={mockSelectPlaceResult}
+ mapType="standard"
+ loading={false}
+ />
+
+);
+
describe( "LocationPicker", () => {
beforeAll( async ( ) => {
await initI18next( );
@@ -70,67 +66,42 @@ describe( "LocationPicker", () => {
it(
"should display latitude corresponding with location name",
async ( ) => {
- const observations = [
- factory( "RemoteObservation", {
- // Oakland, CA latlng
- latitude: 37.804855,
- longitude: -122.272504
- } )
- ];
- mockObsEditProviderWithObs( observations );
- renderLocationPicker( );
- const initialLatitude = screen.getByText( new RegExp( observations[0].latitude ) );
- expect( initialLatitude ).toBeTruthy( );
+ renderLocationPicker( mockRegion );
+ await screen.findByText( new RegExp( observations[0].latitude ) );
}
);
it(
"should show search results when a user changes search text",
async ( ) => {
- const observations = [
- factory( "RemoteObservation", {
- // Oakland, CA latlng
- latitude: 37.804855,
- longitude: -122.272504
- } )
- ];
- mockObsEditProviderWithObs( observations );
- renderLocationPicker( );
+ renderLocationPicker( mockRegion );
const input = screen.getByTestId( "LocationPicker.locationSearch" );
- const initialLatitude = screen.getByText( new RegExp( observations[0].latitude ) );
- expect( initialLatitude ).toBeTruthy( );
+ expect( input ).toBeVisible( );
+ await screen.findByText( new RegExp( observations[0].latitude ) );
fireEvent.changeText( input, "New" );
- await waitFor( ( ) => {
- expect( screen.getByText( mockPlaceResult.display_name ) ).toBeTruthy( );
- } );
+ await screen.findByText( mockPlaceResult.display_name );
}
);
it(
- "should move map to new coordinates when a user presses place result",
+ "should update map with new place results when a user taps a place in dropdown",
async ( ) => {
- const observations = [
- factory( "RemoteObservation", {
- // Oakland, CA latlng
- latitude: 37.804855,
- longitude: -122.272504
- } )
- ];
- mockObsEditProviderWithObs( observations );
- renderLocationPicker( );
+ renderLocationPicker( mockRegion );
const input = screen.getByTestId( "LocationPicker.locationSearch" );
- const initialLatitude = screen.getByText( new RegExp( observations[0].latitude ) );
- expect( initialLatitude ).toBeTruthy( );
+ await screen.findByText( new RegExp( observations[0].latitude ) );
fireEvent.changeText( input, "New" );
- await waitFor( ( ) => {
- expect( screen.getByText( mockPlaceResult.display_name ) ).toBeTruthy( );
- } );
- fireEvent.press( screen.getByText( mockPlaceResult.display_name ) );
- await waitFor( ( ) => {
- expect( screen.getByText(
- new RegExp( mockPlaceResult.point_geojson.coordinates[0] )
- ) ).toBeTruthy( );
+ const placeResult = screen.getByText( mockPlaceResult.display_name );
+ expect( placeResult ).toBeVisible( );
+ fireEvent.press( placeResult );
+ expect( mockSelectPlaceResult ).toHaveBeenCalledTimes( 1 );
+ renderLocationPicker( {
+ ...mockRegion,
+ latitude: mockPlaceResult.point_geojson.coordinates[1],
+ longitude: mockPlaceResult.point_geojson.coordinates[0]
} );
+ await screen.findByText(
+ new RegExp( mockPlaceResult.point_geojson.coordinates[0] )
+ );
}
);
} );