From a0fd52ca274599f30540a70d7911027f35d50e4f Mon Sep 17 00:00:00 2001
From: Amanda Bullington <35536439+albullington@users.noreply.github.com>
Date: Fri, 16 Aug 2024 13:12:24 -0700
Subject: [PATCH] Standardize offline notice, empty results, and loading for
search screens; closes #1925 (#1982)
---
.../SearchScreens/EmptySearchResults.tsx | 45 +++++++++++++
.../SearchScreens/ExploreLocationSearch.js | 38 ++++++-----
.../SearchScreens/ExploreProjectSearch.js | 58 ++++++++--------
.../SearchScreens/ExploreUserSearch.js | 48 ++++++-------
.../SharedComponents/TaxaList/TaxaList.tsx | 67 ++++++-------------
src/i18n/l10n/en.ftl | 1 +
src/i18n/l10n/en.ftl.json | 1 +
src/i18n/strings.ftl | 1 +
8 files changed, 144 insertions(+), 115 deletions(-)
create mode 100644 src/components/Explore/SearchScreens/EmptySearchResults.tsx
diff --git a/src/components/Explore/SearchScreens/EmptySearchResults.tsx b/src/components/Explore/SearchScreens/EmptySearchResults.tsx
new file mode 100644
index 000000000..2c413ab7e
--- /dev/null
+++ b/src/components/Explore/SearchScreens/EmptySearchResults.tsx
@@ -0,0 +1,45 @@
+import { useNetInfo } from "@react-native-community/netinfo";
+import {
+ ActivityIndicator,
+ Body2,
+ OfflineNotice
+} from "components/SharedComponents";
+import { View } from "components/styledComponents";
+import React from "react";
+import {
+ useTranslation
+} from "sharedHooks";
+
+interface Props {
+ isLoading: boolean,
+ searchQuery: string,
+ refetch: Function
+}
+
+const EmptySearchResults = ( { isLoading, searchQuery, refetch }: Props ) => {
+ const { t } = useTranslation( );
+ const { isConnected } = useNetInfo( );
+
+ if ( searchQuery === "" ) {
+ return null;
+ }
+ if ( isConnected === false ) {
+ return (
+
+
+
+ );
+ }
+ if ( isLoading ) {
+ return (
+
+
+
+ );
+ }
+ return (
+ {t( "No-results-found-for-that-search" )}
+ );
+};
+
+export default EmptySearchResults;
diff --git a/src/components/Explore/SearchScreens/ExploreLocationSearch.js b/src/components/Explore/SearchScreens/ExploreLocationSearch.js
index 31ee2cc6f..3bdd4abc4 100644
--- a/src/components/Explore/SearchScreens/ExploreLocationSearch.js
+++ b/src/components/Explore/SearchScreens/ExploreLocationSearch.js
@@ -2,7 +2,6 @@
import fetchSearchResults from "api/search";
import {
- ActivityIndicator,
Body3,
Button,
Heading4,
@@ -26,6 +25,8 @@ import { useAuthenticatedQuery, useTranslation } from "sharedHooks";
import useLocationPermission from "sharedHooks/useLocationPermission.tsx";
import { getShadow } from "styles/global";
+import EmptySearchResults from "./EmptySearchResults";
+
const DROP_SHADOW = getShadow( {
offsetHeight: 4
} );
@@ -51,7 +52,7 @@ const ExploreLocationSearch = ( { closeModal, updateLocation }: Props ): Node =>
[updateLocation, closeModal]
);
- const { data: placeResults, isLoading } = useAuthenticatedQuery(
+ const { data: placeResults, isLoading, refetch } = useAuthenticatedQuery(
["fetchSearchResults", locationName],
optsWithAuth => fetchSearchResults(
{
@@ -114,11 +115,19 @@ const ExploreLocationSearch = ( { closeModal, updateLocation }: Props ): Node =>
}
};
+ const renderEmptyList = ( ) => (
+
+ );
+
return (
setLocationName( locationText )}
value={locationName}
- testID="LocationPicker.locationSearch"
+ testID="ExploreLocationSearch.locationSearch"
/>
@@ -155,20 +164,13 @@ const ExploreLocationSearch = ( { closeModal, updateLocation }: Props ): Node =>
/>
- {isLoading
- ? (
-
-
-
- )
- : (
- item.id}
- />
- )}
+ item.id}
+ ListEmptyComponent={renderEmptyList}
+ />
{renderPermissionsGate( { onPermissionGranted: setNearbyLocation } )}
);
diff --git a/src/components/Explore/SearchScreens/ExploreProjectSearch.js b/src/components/Explore/SearchScreens/ExploreProjectSearch.js
index a53b61efe..6b4e63f4d 100644
--- a/src/components/Explore/SearchScreens/ExploreProjectSearch.js
+++ b/src/components/Explore/SearchScreens/ExploreProjectSearch.js
@@ -3,7 +3,6 @@
import { FlashList } from "@shopify/flash-list";
import { searchProjects } from "api/projects";
import {
- ActivityIndicator,
Body3,
Heading4,
INatIconButton,
@@ -20,6 +19,8 @@ import React, {
import { useAuthenticatedQuery, useTranslation } from "sharedHooks";
import { getShadow } from "styles/global";
+import EmptySearchResults from "./EmptySearchResults";
+
const DROP_SHADOW = getShadow( {
offsetHeight: 4
} );
@@ -30,12 +31,12 @@ type Props = {
};
const ExploreProjectSearch = ( { closeModal, updateProject }: Props ): Node => {
- const [userQuery, setUserQuery] = useState( "" );
+ const [projectQuery, setProjectQuery] = useState( "" );
const { t } = useTranslation();
- const { data: projects, isLoading } = useAuthenticatedQuery(
- ["searchProjects", userQuery],
- optsWithAuth => searchProjects( { q: userQuery }, optsWithAuth )
+ const { data: projects, isLoading, refetch } = useAuthenticatedQuery(
+ ["searchProjects", projectQuery],
+ optsWithAuth => searchProjects( { q: projectQuery }, optsWithAuth )
);
const onProjectSelected = useCallback( async project => {
@@ -77,11 +78,19 @@ const ExploreProjectSearch = ( { closeModal, updateProject }: Props ): Node => {
);
+ const renderEmptyList = ( ) => (
+
+ );
+
return (
{
style={DROP_SHADOW}
>
- {isLoading
- ? (
-
-
-
- )
- : (
- item.id}
- renderItem={renderItem}
- ListHeaderComponent={renderItemSeparator}
- ItemSeparatorComponent={renderItemSeparator}
- accessible
- />
- )}
+ item.id}
+ renderItem={renderItem}
+ ListEmptyComponent={renderEmptyList}
+ ListHeaderComponent={renderItemSeparator}
+ ItemSeparatorComponent={renderItemSeparator}
+ accessible
+ />
);
};
diff --git a/src/components/Explore/SearchScreens/ExploreUserSearch.js b/src/components/Explore/SearchScreens/ExploreUserSearch.js
index 2330b564f..17cfe84fe 100644
--- a/src/components/Explore/SearchScreens/ExploreUserSearch.js
+++ b/src/components/Explore/SearchScreens/ExploreUserSearch.js
@@ -3,7 +3,6 @@
import { FlashList } from "@shopify/flash-list";
import fetchSearchResults from "api/search";
import {
- ActivityIndicator,
Body3,
Heading4,
INatIconButton,
@@ -20,6 +19,8 @@ import React, {
import { useAuthenticatedQuery, useTranslation } from "sharedHooks";
import { getShadow } from "styles/global";
+import EmptySearchResults from "./EmptySearchResults";
+
const DROP_SHADOW = getShadow( {
offsetHeight: 4
} );
@@ -34,7 +35,7 @@ const ExploreUserSearch = ( { closeModal, updateUser }: Props ): Node => {
const { t } = useTranslation();
// TODO: replace this with infinite scroll like ExploreFlashList
- const { data: userList, isLoading } = useAuthenticatedQuery(
+ const { data: userList, isLoading, refetch } = useAuthenticatedQuery(
["fetchSearchResults", userQuery],
optsWithAuth => fetchSearchResults(
{
@@ -82,11 +83,19 @@ const ExploreUserSearch = ( { closeModal, updateUser }: Props ): Node => {
);
+ const renderEmptyList = ( ) => (
+
+ );
+
return (
{
testID="SearchUser"
/>
- {isLoading
- ? (
-
-
-
- )
- : (
- item.id}
- keyboardShouldPersistTaps="handled"
- renderItem={renderItem}
- testID="SearchUserList"
- />
- )}
+ item.id}
+ keyboardShouldPersistTaps="handled"
+ renderItem={renderItem}
+ testID="SearchUserList"
+ />
);
};
diff --git a/src/components/SharedComponents/TaxaList/TaxaList.tsx b/src/components/SharedComponents/TaxaList/TaxaList.tsx
index 1cd34f9c5..b90d338f8 100644
--- a/src/components/SharedComponents/TaxaList/TaxaList.tsx
+++ b/src/components/SharedComponents/TaxaList/TaxaList.tsx
@@ -1,6 +1,4 @@
-import { refresh, useNetInfo } from "@react-native-community/netinfo";
-import { ActivityIndicator, OfflineNotice } from "components/SharedComponents";
-import { View } from "components/styledComponents";
+import EmptySearchResults from "components/Explore/SearchScreens/EmptySearchResults.tsx";
import React from "react";
import { FlatList } from "react-native";
import { useIconicTaxa } from "sharedHooks";
@@ -24,53 +22,30 @@ const TaxaList = ( {
}: Props ) => {
// TODO: how to use Realm with TS
const iconicTaxa = useIconicTaxa( { reload: false } );
- const { isConnected } = useNetInfo( );
- let data = iconicTaxa;
- if ( taxa && taxa.length > 0 ) {
- // TODO: how to use Realm with TS
- data = taxa;
- }
+ // 20240816 amanda - afaik we only want to seed the initial screen
+ // with iconic taxon data, and we still want to be able to show the empty screen
+ // and offline state when a user has typed in a query
+ const data = taxonQuery === ""
+ ? iconicTaxa
+ : taxa;
- const renderMainContent = () => {
- if ( isLoading ) {
- return (
-
-
-
- );
- }
-
- const showIfOffline = taxonQuery.length > 0 && (
- !taxa || ( taxa instanceof Array && taxa.length === 0 )
- );
- if ( showIfOffline && !isConnected ) {
- return (
-
- {
- refresh();
- refetch();
- }}
- />
-
- );
- }
-
- return (
- item.id}
- />
- );
- };
+ const renderEmptyList = ( ) => (
+
+ );
return (
-
- {renderMainContent()}
-
+ item.id}
+ ListEmptyComponent={renderEmptyList}
+ />
);
};
diff --git a/src/i18n/l10n/en.ftl b/src/i18n/l10n/en.ftl
index 82150695e..d92237069 100644
--- a/src/i18n/l10n/en.ftl
+++ b/src/i18n/l10n/en.ftl
@@ -603,6 +603,7 @@ No-Notifications-Found = You have no notifications! Get started by creating your
No-projects-match-that-search = No projects match that search
# Used for explore screen when search params lead to a search with no data
No-results-found = No results found
+No-results-found-for-that-search = No results found for that search.
# license code
no-rights-reserved-cc0 = no rights reserved (CC0)
NONE = NONE
diff --git a/src/i18n/l10n/en.ftl.json b/src/i18n/l10n/en.ftl.json
index 3334bfd5e..927c34926 100644
--- a/src/i18n/l10n/en.ftl.json
+++ b/src/i18n/l10n/en.ftl.json
@@ -812,6 +812,7 @@
"comment": "Used for explore screen when search params lead to a search with no data",
"val": "No results found"
},
+ "No-results-found-for-that-search": "No results found for that search.",
"no-rights-reserved-cc0": {
"comment": "license code",
"val": "no rights reserved (CC0)"
diff --git a/src/i18n/strings.ftl b/src/i18n/strings.ftl
index 82150695e..d92237069 100644
--- a/src/i18n/strings.ftl
+++ b/src/i18n/strings.ftl
@@ -603,6 +603,7 @@ No-Notifications-Found = You have no notifications! Get started by creating your
No-projects-match-that-search = No projects match that search
# Used for explore screen when search params lead to a search with no data
No-results-found = No results found
+No-results-found-for-that-search = No results found for that search.
# license code
no-rights-reserved-cc0 = no rights reserved (CC0)
NONE = NONE