Skip to content

Commit

Permalink
Explore filters and navigation entry points (#1306)
Browse files Browse the repository at this point in the history
* Add test for UserProfile species button from MyObs screen

* Finish initial tests for navigating to Explore from MyObs

* Check API calls are made with correct params

* Use RootExplore screen in CustomTabBar and write test for navigating cyclical Explore -> TaxonDetails

* Add navigation check for UserProfile from root explore screen

* Simplify RootExplore screen; show nearby data for species view

* Mock location permissions for Explore navigation tests

* Update snapshot

* Merge from main

* Fix tests for Explore navigation
  • Loading branch information
albullington authored Mar 26, 2024
1 parent 408115b commit b284bb2
Show file tree
Hide file tree
Showing 21 changed files with 786 additions and 190 deletions.
12 changes: 11 additions & 1 deletion src/components/Explore/Explore.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Props = {
closeFiltersModal: Function,
count: Object,
exploreView: string,
hideBackButton: boolean,
isOnline: boolean,
loadingStatus: boolean,
openFiltersModal: Function,
Expand All @@ -62,6 +63,7 @@ const Explore = ( {
closeFiltersModal,
count,
exploreView,
hideBackButton,
isOnline,
loadingStatus,
openFiltersModal,
Expand All @@ -76,11 +78,19 @@ const Explore = ( {
const { layout, writeLayoutToStorage } = useStoredLayout( "exploreObservationsLayout" );
const { isDebug } = useDebugMode( );

const exploreViewAccessibilityLabel = {
observations: t( "Observations-View" ),
species: t( "Species-View" ),
observers: t( "Observers-View" ),
identifiers: t( "Identifiers-View" )
};

const renderHeader = ( ) => (
<Header
count={count[exploreView]}
exploreView={exploreView}
exploreViewIcon={exploreViewIcon[exploreView]}
hideBackButton={hideBackButton}
loadingStatus={loadingStatus}
openFiltersModal={openFiltersModal}
onPressCount={( ) => setShowExploreBottomSheet( true )}
Expand Down Expand Up @@ -184,7 +194,7 @@ const Explore = ( {
grayCircleClass,
"absolute bottom-5 z-10 right-5"
)}
accessibilityLabel={t( "Explore-View" )}
accessibilityLabel={exploreViewAccessibilityLabel[exploreView]}
onPress={() => setShowExploreBottomSheet( true )}
style={getShadow( theme.colors.primary )}
/>
Expand Down
145 changes: 9 additions & 136 deletions src/components/Explore/ExploreContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,150 +2,20 @@

import { useRoute } from "@react-navigation/native";
import {
ESTABLISHMENT_MEAN,
EXPLORE_ACTION,
ExploreProvider,
MEDIA,
PHOTO_LICENSE,
REVIEWED,
SORT_BY,
useExplore,
WILD_STATUS
useExplore
} from "providers/ExploreContext.tsx";
import type { Node } from "react";
import React, { useEffect, useState } from "react";
import { useCurrentUser, useIsConnected } from "sharedHooks";
import { useCurrentUser, useIsConnected, useTranslation } from "sharedHooks";

import Explore from "./Explore";
import mapParamsToAPI from "./helpers/mapParamsToAPI";
import useHeaderCount from "./hooks/useHeaderCount";

const mapParamsToAPI = ( params, currentUser ) => {
const RESEARCH = "research";
const NEEDS_ID = "needs_id";
const CASUAL = "casual";

const CREATED_AT = "created_at"; // = date uploaded at
const OBSERVED_ON = "observed_on";
const VOTES = "votes";

const DESC = "desc";
const ASC = "asc";

// Remove all params that are falsy
const filteredParams = Object.entries( params ).reduce(
( newParams, [key, value] ) => {
if ( value ) {
newParams[key] = value;
}
return newParams;
},
{}
);

// DATE_UPLOADED_NEWEST is the default sort order
filteredParams.order_by = CREATED_AT;
filteredParams.order = DESC;
if ( params.sortBy === SORT_BY.DATE_UPLOADED_OLDEST ) {
filteredParams.order_by = CREATED_AT;
filteredParams.order = ASC;
}
if ( params.sortBy === SORT_BY.DATE_OBSERVED_NEWEST ) {
filteredParams.order_by = OBSERVED_ON;
filteredParams.order = DESC;
}
if ( params.sortBy === SORT_BY.DATE_OBSERVED_OLDEST ) {
filteredParams.order_by = OBSERVED_ON;
filteredParams.order = ASC;
}
if ( params.sortBy === SORT_BY.MOST_FAVED ) {
filteredParams.order_by = VOTES;
filteredParams.order = DESC;
}

filteredParams.quality_grade = [];
if ( params.researchGrade ) {
filteredParams.quality_grade.push( RESEARCH );
}
if ( params.needsID ) {
filteredParams.quality_grade.push( NEEDS_ID );
}
if ( params.casual ) {
filteredParams.quality_grade.push( CASUAL );
delete filteredParams.verifiable;
}

if ( filteredParams.months ) {
filteredParams.month = filteredParams.months;
delete filteredParams.months;
}

// MEDIA.ALL is the default media filter and for it we don't need to pass any params
if ( params.media === MEDIA.PHOTOS ) {
filteredParams.photos = true;
} else if ( params.media === MEDIA.SOUNDS ) {
filteredParams.sounds = true;
} else if ( params.media === MEDIA.NONE ) {
filteredParams.photos = false;
filteredParams.sounds = false;
}

// ESTABLISHMENT_MEAN.ANY is the default here and for it we don't need to pass any params
if ( params.establishmentMean === ESTABLISHMENT_MEAN.NATIVE ) {
filteredParams.native = true;
} else if ( params.establishmentMean === ESTABLISHMENT_MEAN.INTRODUCED ) {
filteredParams.introduced = true;
} else if ( params.establishmentMean === ESTABLISHMENT_MEAN.ENDEMIC ) {
filteredParams.endemic = true;
}

if ( params.wildStatus === WILD_STATUS.WILD ) {
filteredParams.captive = false;
} else if ( params.wildStatus === WILD_STATUS.CAPTIVE ) {
filteredParams.captive = true;
}

if ( params.reviewedFilter === REVIEWED.REVIEWED ) {
filteredParams.reviewed = true;
filteredParams.viewer_id = currentUser?.id;
} else if ( params.reviewedFilter === REVIEWED.UNREVIEWED ) {
filteredParams.reviewed = false;
filteredParams.viewer_id = currentUser?.id;
}

if ( params.photoLicense !== PHOTO_LICENSE.ALL ) {
// How license filter maps to the API
const licenseParams = {
[PHOTO_LICENSE.CC0]: "cc0",
[PHOTO_LICENSE.CCBY]: "cc-by",
[PHOTO_LICENSE.CCBYNC]: "cc-by-nc",
[PHOTO_LICENSE.CCBYSA]: "cc-by-sa",
[PHOTO_LICENSE.CCBYND]: "cc-by-nd",
[PHOTO_LICENSE.CCBYNCSA]: "cc-by-nc-sa",
[PHOTO_LICENSE.CCBYNCND]: "cc-by-nc-nd"
};
filteredParams.photo_license = licenseParams[params.photoLicense];
}

delete filteredParams.taxon;
delete filteredParams.place_guess;
delete filteredParams.user;
delete filteredParams.project;
delete filteredParams.sortBy;
delete filteredParams.researchGrade;
delete filteredParams.needsID;
delete filteredParams.casual;
delete filteredParams.dateObserved;
delete filteredParams.dateUploaded;
delete filteredParams.media;
delete filteredParams.establishmentMean;
delete filteredParams.wildStatus;
delete filteredParams.reviewedFilter;
delete filteredParams.photoLicense;

return filteredParams;
};

const ExploreContainerWithContext = ( ): Node => {
const { t } = useTranslation( );
const { params } = useRoute( );
const isOnline = useIsConnected( );

Expand All @@ -156,6 +26,8 @@ const ExploreContainerWithContext = ( ): Node => {
const [showFiltersModal, setShowFiltersModal] = useState( false );
const [exploreView, setExploreView] = useState( "observations" );

const worldwidePlaceText = t( "Worldwide" );

useEffect( ( ) => {
if ( params?.viewSpecies ) {
setExploreView( "species" );
Expand All @@ -164,7 +36,7 @@ const ExploreContainerWithContext = ( ): Node => {
dispatch( {
type: EXPLORE_ACTION.SET_PLACE,
placeId: null,
placeName: ""
placeName: worldwidePlaceText
} );
}
if ( params?.taxon ) {
Expand Down Expand Up @@ -196,7 +68,7 @@ const ExploreContainerWithContext = ( ): Node => {
projectId: params.project.id
} );
}
}, [params, dispatch] );
}, [params, dispatch, worldwidePlaceText] );

const changeExploreView = newView => {
setExploreView( newView );
Expand Down Expand Up @@ -240,6 +112,7 @@ const ExploreContainerWithContext = ( ): Node => {
closeFiltersModal={closeFiltersModal}
count={count}
exploreView={exploreView}
hideBackButton={false}
isOnline={isOnline}
loadingStatus={loadingStatus}
openFiltersModal={openFiltersModal}
Expand Down
67 changes: 40 additions & 27 deletions src/components/Explore/Header/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useNavigation } from "@react-navigation/native";
import classNames from "classnames";
import NumberBadge from "components/Explore/NumberBadge.tsx";
import {
BackButton,
Body3,
INatIcon,
INatIconButton,
Expand All @@ -23,6 +24,7 @@ type Props = {
count: ?number,
exploreView: string,
exploreViewIcon: string,
hideBackButton: boolean,
loadingStatus: boolean,
onPressCount?: Function,
openFiltersModal: Function
Expand All @@ -32,6 +34,7 @@ const Header = ( {
count,
exploreView,
exploreViewIcon,
hideBackButton,
loadingStatus,
onPressCount,
openFiltersModal
Expand All @@ -41,7 +44,7 @@ const Header = ( {
const theme = useTheme( );
const { state, numberOfFilters } = useExplore( );
const { taxon } = state;
const placeName = state.place_guess || t( "Worldwide" );
const placeName = state.place_guess;

const surfaceStyle = {
backgroundColor: theme.colors.primary,
Expand All @@ -54,35 +57,45 @@ const Header = ( {
<View className="z-10">
<Surface style={surfaceStyle} elevation={5}>
<View className="bg-white px-6 py-4 flex-row justify-between items-center">
<View>
{taxon
<View className="flex-row">
{!hideBackButton
? (
<TaxonResult
asListItem={false}
taxon={taxon}
showInfoButton={false}
showCheckmark={false}
handlePress={() => navigation.navigate( "ExploreTaxonSearch" )}
<BackButton
inCustomHeader
testID="Explore.BackButton"
/>
)
: (
<Pressable
accessibilityRole="button"
className="flex-row items-center"
onPress={() => navigation.navigate( "ExploreTaxonSearch" )}
>
<INatIcon name="label-outline" size={15} />
<Body3 className="ml-3">{t( "All-organisms" )}</Body3>
</Pressable>
)}
<Pressable
accessibilityRole="button"
onPress={( ) => navigation.navigate( "ExploreLocationSearch" )}
className="flex-row items-center pt-3"
>
<INatIcon name="location" size={15} />
<Body3 className="ml-3">{placeName}</Body3>
</Pressable>
: <View className="w-[44px]" />}
<View>
{taxon
? (
<TaxonResult
asListItem={false}
taxon={taxon}
showInfoButton={false}
showCheckmark={false}
handlePress={() => navigation.navigate( "ExploreTaxonSearch" )}
/>
)
: (
<Pressable
accessibilityRole="button"
className="flex-row items-center"
onPress={() => navigation.navigate( "ExploreTaxonSearch" )}
>
<INatIcon name="label-outline" size={15} />
<Body3 className="ml-3">{t( "All-organisms" )}</Body3>
</Pressable>
)}
<Pressable
accessibilityRole="button"
onPress={( ) => navigation.navigate( "ExploreLocationSearch" )}
className="flex-row items-center pt-3"
>
<INatIcon name="location" size={15} />
<Body3 className="ml-3">{placeName}</Body3>
</Pressable>
</View>
</View>
<View>
<INatIconButton
Expand Down
Loading

0 comments on commit b284bb2

Please sign in to comment.