mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-05-06 14:46:20 -04:00
Explore nearby and map area (#1655)
* Bugfix: map area search was broken after Nearby filter set * Made EXPLORE_ACTION.SET_MAP_BOUNDARIES remove lat, lng, and radius attributes so we don't end up applying both kinds of geographic filters at the same time * Renamed setExploreLocation to defaultExploreLocation because it doesn't set anything * Added a unit test fot the Explore reducer * Bugfix: zoom Explore to nearby after granting permission
This commit is contained in:
@@ -82,7 +82,7 @@ const FilterModal = ( {
|
||||
discardChanges,
|
||||
isNotInitialState,
|
||||
numberOfFilters,
|
||||
setExploreLocation
|
||||
defaultExploreLocation
|
||||
} = useExplore();
|
||||
const {
|
||||
taxon,
|
||||
@@ -661,7 +661,7 @@ const FilterModal = ( {
|
||||
<Body3
|
||||
accessibilityRole="button"
|
||||
onPress={async ( ) => {
|
||||
const exploreLocation = await setExploreLocation( );
|
||||
const exploreLocation = await defaultExploreLocation( );
|
||||
dispatch( { type: EXPLORE_ACTION.RESET, exploreLocation } );
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,11 @@ import {
|
||||
useExplore
|
||||
} from "providers/ExploreContext.tsx";
|
||||
import type { Node } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState
|
||||
} from "react";
|
||||
import { useCurrentUser, useIsConnected, useTranslation } from "sharedHooks";
|
||||
import useStore from "stores/useStore";
|
||||
|
||||
@@ -27,7 +31,7 @@ const RootExploreContainerWithContext = ( ): Node => {
|
||||
const worldwidePlaceText = t( "Worldwide" );
|
||||
|
||||
const {
|
||||
state, dispatch, makeSnapshot, setExploreLocation
|
||||
state, dispatch, makeSnapshot, defaultExploreLocation
|
||||
} = useExplore( );
|
||||
|
||||
const [showFiltersModal, setShowFiltersModal] = useState( false );
|
||||
@@ -95,30 +99,23 @@ const RootExploreContainerWithContext = ( ): Node => {
|
||||
makeSnapshot( );
|
||||
};
|
||||
|
||||
const onPermissionGranted = async ( ) => {
|
||||
if ( state.place_guess ) { return; }
|
||||
const exploreLocation = await setExploreLocation( );
|
||||
const onPermissionGranted = useCallback( async ( ) => {
|
||||
const exploreLocation = await defaultExploreLocation( );
|
||||
dispatch( {
|
||||
type: EXPLORE_ACTION.SET_EXPLORE_LOCATION,
|
||||
exploreLocation
|
||||
} );
|
||||
};
|
||||
}, [
|
||||
defaultExploreLocation,
|
||||
dispatch
|
||||
] );
|
||||
|
||||
const onPermissionDenied = ( ) => {
|
||||
if ( state.place_guess ) { return; }
|
||||
const resetToWorldWide = useCallback( ( ) => {
|
||||
dispatch( {
|
||||
type: EXPLORE_ACTION.SET_PLACE,
|
||||
placeGuess: t( "Worldwide" )
|
||||
placeGuess: worldwidePlaceText
|
||||
} );
|
||||
};
|
||||
|
||||
const onPermissionBlocked = ( ) => {
|
||||
if ( state.place_guess ) { return; }
|
||||
dispatch( {
|
||||
type: EXPLORE_ACTION.SET_PLACE,
|
||||
placeGuess: t( "Worldwide" )
|
||||
} );
|
||||
};
|
||||
}, [dispatch, worldwidePlaceText] );
|
||||
|
||||
useEffect( ( ) => {
|
||||
navigation.addListener( "focus", ( ) => {
|
||||
@@ -154,8 +151,8 @@ const RootExploreContainerWithContext = ( ): Node => {
|
||||
<LocationPermissionGate
|
||||
permissionNeeded
|
||||
onPermissionGranted={onPermissionGranted}
|
||||
onPermissionDenied={onPermissionDenied}
|
||||
onPermissionBlocked={onPermissionBlocked}
|
||||
onPermissionDenied={resetToWorldWide}
|
||||
onPermissionBlocked={resetToWorldWide}
|
||||
withoutNavigation
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -37,7 +37,7 @@ type Props = {
|
||||
|
||||
const ExploreLocationSearch = ( { closeModal, updateLocation }: Props ): Node => {
|
||||
const { t } = useTranslation( );
|
||||
const { dispatch, setExploreLocation } = useExplore( );
|
||||
const { dispatch, defaultExploreLocation } = useExplore( );
|
||||
|
||||
const [locationName, setLocationName] = useState( "" );
|
||||
const [permissionNeeded, setPermissionNeeded] = useState( false );
|
||||
@@ -146,7 +146,7 @@ const ExploreLocationSearch = ( { closeModal, updateLocation }: Props ): Node =>
|
||||
withoutNavigation
|
||||
onPermissionGranted={async ( ) => {
|
||||
setPermissionNeeded( false );
|
||||
const exploreLocation = await setExploreLocation( );
|
||||
const exploreLocation = await defaultExploreLocation( );
|
||||
dispatch( { type: EXPLORE_ACTION.SET_EXPLORE_LOCATION, exploreLocation } );
|
||||
closeModal();
|
||||
}}
|
||||
|
||||
@@ -50,13 +50,11 @@ const useMapLocation = ( ): Object => {
|
||||
};
|
||||
|
||||
setMapBoundaries( boundaryAPIParams );
|
||||
logger.info( "setting map region based on user pan/zoom" );
|
||||
setMapRegion( newRegion );
|
||||
return boundaryAPIParams;
|
||||
}, [t, setMapBoundaries, setMapRegion] );
|
||||
|
||||
const redoSearchInMapArea = ( ) => {
|
||||
logger.info( "searching for observations with map boundaries: ", mapBoundaries );
|
||||
setShowMapBoundaryButton( false );
|
||||
dispatch( { type: EXPLORE_ACTION.SET_MAP_BOUNDARIES, mapBoundaries } );
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import useStore from "stores/useStore";
|
||||
const useParams = ( ): Object => {
|
||||
const { t } = useTranslation( );
|
||||
const { params } = useRoute( );
|
||||
const { dispatch, setExploreLocation } = useExplore( );
|
||||
const { dispatch, defaultExploreLocation } = useExplore( );
|
||||
const storedParams = useStore( state => state.storedParams );
|
||||
|
||||
const worldwidePlaceText = t( "Worldwide" );
|
||||
@@ -32,7 +32,7 @@ const useParams = ( ): Object => {
|
||||
setWorldwide( );
|
||||
}
|
||||
if ( params?.nearby ) {
|
||||
const exploreLocation = await setExploreLocation( );
|
||||
const exploreLocation = await defaultExploreLocation( );
|
||||
dispatch( {
|
||||
type: EXPLORE_ACTION.SET_EXPLORE_LOCATION,
|
||||
exploreLocation
|
||||
@@ -75,7 +75,7 @@ const useParams = ( ): Object => {
|
||||
}, [
|
||||
dispatch,
|
||||
params,
|
||||
setExploreLocation,
|
||||
defaultExploreLocation,
|
||||
worldwidePlaceText
|
||||
] );
|
||||
|
||||
|
||||
@@ -6,34 +6,34 @@ import { LatLng } from "react-native-maps";
|
||||
import fetchUserLocation from "sharedHelpers/fetchUserLocation";
|
||||
|
||||
export enum EXPLORE_ACTION {
|
||||
CHANGE_SORT_BY = "CHANGE_SORT_BY",
|
||||
CHANGE_TAXON = "CHANGE_TAXON",
|
||||
DISCARD = "DISCARD",
|
||||
RESET = "RESET",
|
||||
CHANGE_TAXON = "CHANGE_TAXON",
|
||||
SET_TAXON_NAME = "SET_TAXON_NAME",
|
||||
SET_EXPLORE_LOCATION = "SET_EXPLORE_LOCATION",
|
||||
SET_PLACE = "SET_PLACE",
|
||||
SET_USER = "SET_USER",
|
||||
SET_PROJECT = "SET_PROJECT",
|
||||
CHANGE_SORT_BY = "CHANGE_SORT_BY",
|
||||
TOGGLE_RESEARCH_GRADE = "TOGGLE_RESEARCH_GRADE",
|
||||
TOGGLE_NEEDS_ID = "TOGGLE_NEEDS_ID",
|
||||
TOGGLE_CASUAL = "TOGGLE_CASUAL",
|
||||
SET_HIGHEST_TAXONOMIC_RANK = "SET_HIGHEST_TAXONOMIC_RANK",
|
||||
SET_LOWEST_TAXONOMIC_RANK = "SET_LOWEST_TAXONOMIC_RANK",
|
||||
SET_DATE_OBSERVED_MONTHS = "SET_DATE_OBSERVED_MONTHS",
|
||||
SET_DATE_OBSERVED_EXACT = "SET_DATE_OBSERVED_EXACT",
|
||||
SET_DATE_OBSERVED_RANGE = "SET_DATE_OBSERVED_RANGE",
|
||||
SET_DATE_OBSERVED_ALL = "SET_DATE_OBSERVED_ALL",
|
||||
SET_DATE_OBSERVED_EXACT = "SET_DATE_OBSERVED_EXACT",
|
||||
SET_DATE_OBSERVED_MONTHS = "SET_DATE_OBSERVED_MONTHS",
|
||||
SET_DATE_OBSERVED_RANGE = "SET_DATE_OBSERVED_RANGE",
|
||||
SET_DATE_UPLOADED_ALL = "SET_DATE_UPLOADED_ALL",
|
||||
SET_DATE_UPLOADED_EXACT = "SET_DATE_UPLOADED_EXACT",
|
||||
SET_DATE_UPLOADED_RANGE = "SET_DATE_UPLOADED_RANGE",
|
||||
SET_DATE_UPLOADED_ALL = "SET_DATE_UPLOADED_ALL",
|
||||
SET_MEDIA = "SET_MEDIA",
|
||||
SET_ESTABLISHMENT_MEAN = "SET_ESTABLISHMENT_MEAN",
|
||||
SET_WILD_STATUS = "SET_WILD_STATUS",
|
||||
SET_REVIEWED = "SET_REVIEWED",
|
||||
SET_PHOTO_LICENSE = "SET_PHOTO_LICENSE",
|
||||
SET_EXPLORE_LOCATION = "SET_EXPLORE_LOCATION",
|
||||
SET_HIGHEST_TAXONOMIC_RANK = "SET_HIGHEST_TAXONOMIC_RANK",
|
||||
SET_LOWEST_TAXONOMIC_RANK = "SET_LOWEST_TAXONOMIC_RANK",
|
||||
SET_MAP_BOUNDARIES = "SET_MAP_BOUNDARIES",
|
||||
USE_STORED_STATE = "USE_STORED_STATE",
|
||||
SET_MEDIA = "SET_MEDIA",
|
||||
SET_PHOTO_LICENSE = "SET_PHOTO_LICENSE",
|
||||
SET_PLACE = "SET_PLACE",
|
||||
SET_PROJECT = "SET_PROJECT",
|
||||
SET_REVIEWED = "SET_REVIEWED",
|
||||
SET_TAXON_NAME = "SET_TAXON_NAME",
|
||||
SET_USER = "SET_USER",
|
||||
SET_WILD_STATUS = "SET_WILD_STATUS",
|
||||
TOGGLE_CASUAL = "TOGGLE_CASUAL",
|
||||
TOGGLE_NEEDS_ID = "TOGGLE_NEEDS_ID",
|
||||
TOGGLE_RESEARCH_GRADE = "TOGGLE_RESEARCH_GRADE",
|
||||
USE_STORED_STATE = "USE_STORED_STATE"
|
||||
}
|
||||
|
||||
export enum SORT_BY {
|
||||
@@ -293,7 +293,7 @@ function isValidDateFormat( date: string ): boolean {
|
||||
return regex.test( date );
|
||||
}
|
||||
|
||||
async function setExploreLocation( ) {
|
||||
async function defaultExploreLocation( ) {
|
||||
const location = await fetchUserLocation( );
|
||||
if ( !location || !location.latitude ) {
|
||||
return {
|
||||
@@ -496,14 +496,17 @@ function exploreReducer( state: State, action: Action ) {
|
||||
...state,
|
||||
reviewedFilter: action.reviewedFilter
|
||||
};
|
||||
case EXPLORE_ACTION.SET_MAP_BOUNDARIES:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const boundState = {
|
||||
case EXPLORE_ACTION.SET_MAP_BOUNDARIES: {
|
||||
const newState = {
|
||||
...state,
|
||||
...action.mapBoundaries
|
||||
};
|
||||
delete boundState.place_id;
|
||||
return boundState;
|
||||
delete newState.place_id;
|
||||
delete newState.lat;
|
||||
delete newState.lng;
|
||||
delete newState.radius;
|
||||
return newState;
|
||||
}
|
||||
case EXPLORE_ACTION.USE_STORED_STATE:
|
||||
return {
|
||||
...action.storedState
|
||||
@@ -559,7 +562,7 @@ const ExploreProvider = ( { children }: ExploreProviderProps ) => {
|
||||
const value = {
|
||||
state,
|
||||
dispatch,
|
||||
setExploreLocation,
|
||||
defaultExploreLocation,
|
||||
isNotInitialState,
|
||||
numberOfFilters,
|
||||
makeSnapshot,
|
||||
@@ -579,4 +582,8 @@ function useExplore() {
|
||||
return context;
|
||||
}
|
||||
|
||||
export { ExploreProvider, useExplore };
|
||||
export {
|
||||
ExploreProvider,
|
||||
exploreReducer,
|
||||
useExplore
|
||||
};
|
||||
|
||||
44
tests/unit/providers/ExploreContext.test.js
Normal file
44
tests/unit/providers/ExploreContext.test.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
EXPLORE_ACTION,
|
||||
exploreReducer
|
||||
} from "providers/ExploreContext.tsx";
|
||||
import factory from "tests/factory";
|
||||
|
||||
describe( "ExploreContext", ( ) => {
|
||||
describe( "exploreReducer", ( ) => {
|
||||
describe( EXPLORE_ACTION.SET_PLACE, ( ) => {
|
||||
it( "should remove lat and lng when place set", ( ) => {
|
||||
const initialState = { lat: 1, lng: 1 };
|
||||
const reducedState = exploreReducer( initialState, {
|
||||
type: EXPLORE_ACTION.SET_PLACE,
|
||||
place: factory( "RemotePlace" )
|
||||
} );
|
||||
expect( initialState.lat ).not.toBeUndefined( );
|
||||
expect( initialState.lng ).not.toBeUndefined( );
|
||||
expect( reducedState.lat ).toBeUndefined( );
|
||||
expect( reducedState.lng ).toBeUndefined( );
|
||||
} );
|
||||
} );
|
||||
describe( EXPLORE_ACTION.SET_MAP_BOUNDARIES, ( ) => {
|
||||
it( "should remove lat, lng, and radius", ( ) => {
|
||||
const initialState = { lat: 1, lng: 1, radius: 50 };
|
||||
const reducedState = exploreReducer( initialState, {
|
||||
type: EXPLORE_ACTION.SET_MAP_BOUNDARIES,
|
||||
mapBoundaries: {
|
||||
swlat: 0,
|
||||
swlng: 0,
|
||||
nelat: 1,
|
||||
nelng: 1,
|
||||
place_guess: "somwhere"
|
||||
}
|
||||
} );
|
||||
expect( initialState.lat ).not.toBeUndefined( );
|
||||
expect( initialState.lng ).not.toBeUndefined( );
|
||||
expect( initialState.radius ).not.toBeUndefined( );
|
||||
expect( reducedState.lat ).toBeUndefined( );
|
||||
expect( reducedState.lng ).toBeUndefined( );
|
||||
expect( reducedState.radius ).toBeUndefined( );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
Reference in New Issue
Block a user