From 51fa2ed7b4f019a726dcc2ccc7947e9034b7509f Mon Sep 17 00:00:00 2001 From: Ruxandra Machedon Date: Fri, 13 Dec 2024 13:35:36 -0500 Subject: [PATCH 1/4] refactor: break apart MapSearch file into components --- .../src/screens/SitesScreen/SitesScreen.tsx | 2 +- .../components/{ => search}/MapSearch.tsx | 35 ++---------- .../search/MapSearchSuggestionBox.tsx | 53 +++++++++++++++++++ 3 files changed, 59 insertions(+), 31 deletions(-) rename dev-client/src/screens/SitesScreen/components/{ => search}/MapSearch.tsx (87%) create mode 100644 dev-client/src/screens/SitesScreen/components/search/MapSearchSuggestionBox.tsx diff --git a/dev-client/src/screens/SitesScreen/SitesScreen.tsx b/dev-client/src/screens/SitesScreen/SitesScreen.tsx index 3c60aa71b..02d0aeb6c 100644 --- a/dev-client/src/screens/SitesScreen/SitesScreen.tsx +++ b/dev-client/src/screens/SitesScreen/SitesScreen.tsx @@ -40,7 +40,7 @@ import {SitesScreenContext} from 'terraso-mobile-client/context/SitesScreenConte import {fetchSoilDataForUser} from 'terraso-mobile-client/model/soilId/soilIdGlobalReducer'; import {AppBar} from 'terraso-mobile-client/navigation/components/AppBar'; import {ScreenScaffold} from 'terraso-mobile-client/screens/ScreenScaffold'; -import MapSearch from 'terraso-mobile-client/screens/SitesScreen/components/MapSearch'; +import {MapSearch} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearch'; import {SiteListBottomSheet} from 'terraso-mobile-client/screens/SitesScreen/components/SiteListBottomSheet'; import { MapRef, diff --git a/dev-client/src/screens/SitesScreen/components/MapSearch.tsx b/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx similarity index 87% rename from dev-client/src/screens/SitesScreen/components/MapSearch.tsx rename to dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx index aa17502dd..ddc2bfd51 100644 --- a/dev-client/src/screens/SitesScreen/components/MapSearch.tsx +++ b/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx @@ -1,5 +1,5 @@ /* - * Copyright © 2023 Technology Matters + * Copyright © 2024 Technology Matters * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published @@ -21,8 +21,6 @@ import {Keyboard} from 'react-native'; import Autocomplete from 'react-native-autocomplete-input'; import {Searchbar} from 'react-native-paper'; -import {Pressable} from 'native-base'; - import {Coords} from 'terraso-client-shared/types'; import {IconButton} from 'terraso-mobile-client/components/buttons/icons/IconButton'; @@ -32,36 +30,13 @@ import { Box, Column, Row, - Text, View, } from 'terraso-mobile-client/components/NativeBaseAdapters'; import {MAP_QUERY_MIN_LENGTH} from 'terraso-mobile-client/constants'; import {useSitesScreenContext} from 'terraso-mobile-client/context/SitesScreenContext'; import {useUpdatedForegroundPermissions} from 'terraso-mobile-client/hooks/appPermissionsHooks'; import {useMapSuggestions} from 'terraso-mobile-client/hooks/useMapSuggestions'; - -type SuggestionProps = { - name: string; - address: string; - mapboxId: string; - onPress: (name: string, mapboxId: string) => void; -}; - -function SuggestionBox({name, address, mapboxId, onPress}: SuggestionProps) { - const selectSuggestion = useCallback( - () => onPress(name, mapboxId), - [name, mapboxId, onPress], - ); - - return ( - - - {name} - {address} - - - ); -} +import {MapSearchSuggestionBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchSuggestionBox'; type Props = { zoomTo?: (site: Coords) => void; @@ -69,7 +44,7 @@ type Props = { toggleMapLayer?: () => void; }; -export default function MapSearch({zoomTo, zoomToUser, toggleMapLayer}: Props) { +export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { const {t} = useTranslation(); const [query, setQuery] = useState(''); const {coords, suggestions, querySuggestions, lookupFeature} = @@ -119,7 +94,7 @@ export default function MapSearch({zoomTo, zoomToUser, toggleMapLayer}: Props) { keyboardShouldPersistTaps: 'always', keyExtractor: suggestion => suggestion.mapbox_id, renderItem: ({item}) => ( - ); -} +}; diff --git a/dev-client/src/screens/SitesScreen/components/search/MapSearchSuggestionBox.tsx b/dev-client/src/screens/SitesScreen/components/search/MapSearchSuggestionBox.tsx new file mode 100644 index 000000000..24d523eab --- /dev/null +++ b/dev-client/src/screens/SitesScreen/components/search/MapSearchSuggestionBox.tsx @@ -0,0 +1,53 @@ +/* + * Copyright © 2024 Technology Matters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +import {useCallback} from 'react'; + +import {Pressable} from 'native-base'; + +import { + Column, + Text, +} from 'terraso-mobile-client/components/NativeBaseAdapters'; + +type Props = { + name: string; + address: string; + mapboxId: string; + onPress: (name: string, mapboxId: string) => void; +}; + +export const MapSearchSuggestionBox = ({ + name, + address, + mapboxId, + onPress, +}: Props) => { + const selectSuggestion = useCallback( + () => onPress(name, mapboxId), + [name, mapboxId, onPress], + ); + + return ( + + + {name} + {address} + + + ); +}; From d8516faf8e4533210bdb04cf5e5dff02cbf7fdb1 Mon Sep 17 00:00:00 2001 From: Ruxandra Machedon Date: Fri, 13 Dec 2024 14:02:05 -0500 Subject: [PATCH 2/4] feat: offline indicator for map search --- .../components/search/MapSearch.tsx | 48 +++++++++++-------- .../components/search/MapSearchOfflineBox.tsx | 44 +++++++++++++++++ dev-client/src/translations/en.json | 1 + dev-client/src/translations/es.json | 1 + 4 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineBox.tsx diff --git a/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx b/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx index ddc2bfd51..144b4a097 100644 --- a/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx +++ b/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx @@ -35,7 +35,9 @@ import { import {MAP_QUERY_MIN_LENGTH} from 'terraso-mobile-client/constants'; import {useSitesScreenContext} from 'terraso-mobile-client/context/SitesScreenContext'; import {useUpdatedForegroundPermissions} from 'terraso-mobile-client/hooks/appPermissionsHooks'; +import {useIsOffline} from 'terraso-mobile-client/hooks/connectivityHooks'; import {useMapSuggestions} from 'terraso-mobile-client/hooks/useMapSuggestions'; +import {MapSearchOfflineBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchOfflineBox'; import {MapSearchSuggestionBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchSuggestionBox'; type Props = { @@ -45,6 +47,7 @@ type Props = { }; export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { + const isOffline = useIsOffline(); const {t} = useTranslation(); const [query, setQuery] = useState(''); const {coords, suggestions, querySuggestions, lookupFeature} = @@ -89,7 +92,7 @@ export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { suggestion.mapbox_id, @@ -104,26 +107,29 @@ export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { }} inputContainerStyle={{borderWidth: 0}} // eslint-disable-line react-native/no-inline-styles renderTextInput={() => ( - { - setQuery(newText); - querySuggestions(newText); - }} - onFocus={() => { - setHideResults(false); - querySuggestions(query); - }} - onEndEditing={() => { - setHideResults(true); - }} - value={query} - placeholder={t('search.placeholder')} - style={{ - ...searchBarStyles.search, - ...searchBarStyles.wideSearchOverride, - }} - inputStyle={searchBarStyles.input} - /> + <> + { + setQuery(newText); + querySuggestions(newText); + }} + onFocus={() => { + setHideResults(false); + querySuggestions(query); + }} + onEndEditing={() => { + setHideResults(true); + }} + value={query} + placeholder={t('search.placeholder')} + style={{ + ...searchBarStyles.search, + ...searchBarStyles.wideSearchOverride, + }} + inputStyle={searchBarStyles.input} + /> + {isOffline && !hideResults ? : <>} + )} /> diff --git a/dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineBox.tsx b/dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineBox.tsx new file mode 100644 index 000000000..105542ade --- /dev/null +++ b/dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineBox.tsx @@ -0,0 +1,44 @@ +/* + * Copyright © 2024 Technology Matters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://www.gnu.org/licenses/. + */ + +import {StyleSheet, View} from 'react-native'; + +import {DisableableText} from 'terraso-mobile-client/components/content/typography/DisableableText'; +import {TranslatedContent} from 'terraso-mobile-client/components/content/typography/TranslatedContent'; +import {convertColorProp} from 'terraso-mobile-client/components/util/nativeBaseAdapters'; + +export const MapSearchOfflineBox = () => { + return ( + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + backgroundColor: convertColorProp('background.default'), + borderWidth: 1, + borderColor: convertColorProp('grey.300'), + paddingHorizontal: 12, + paddingVertical: 4, + marginHorizontal: 12, + width: '92%', + }, +}); diff --git a/dev-client/src/translations/en.json b/dev-client/src/translations/en.json index ede4bf373..a27eaa222 100644 --- a/dev-client/src/translations/en.json +++ b/dev-client/src/translations/en.json @@ -204,6 +204,7 @@ "filter_projects": "Filter sites by project", "filter_role": "Filter sites by role", "no_matches": "No sites match the current search and filter options.", + "offline": "You are offline. Search is not available, but you can enter exact coordinates.", "sort": { "label": "Sort sites by", "nameAsc": "Name (A to Z)", diff --git a/dev-client/src/translations/es.json b/dev-client/src/translations/es.json index 6435f3c83..6877a7cb9 100644 --- a/dev-client/src/translations/es.json +++ b/dev-client/src/translations/es.json @@ -94,6 +94,7 @@ "distanceAsc": "Distancia (más cercana)", "distanceDesc": "Distance (más lejana)" }, + "offline": "You are offline. Search is not available, but you can enter exact coordinates. TK", "accessibility_label": "Ordenar sitios por nombre", "filter_projects": "Ordenar sitios por proyecto", "filter_role": "Ordenar sitios por rol", From 76a3e22138a5a37456d030f17c3c83a7b0bc39a0 Mon Sep 17 00:00:00 2001 From: Ruxandra Machedon Date: Fri, 13 Dec 2024 16:29:28 -0500 Subject: [PATCH 3/4] feat: don't hit suggestions API when offline --- dev-client/src/hooks/useMapSuggestions.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dev-client/src/hooks/useMapSuggestions.ts b/dev-client/src/hooks/useMapSuggestions.ts index 3f33a0082..abf62d207 100644 --- a/dev-client/src/hooks/useMapSuggestions.ts +++ b/dev-client/src/hooks/useMapSuggestions.ts @@ -25,6 +25,7 @@ import { MAP_QUERY_MIN_LENGTH, SEARCH_DEBOUNCE_TIME_MS, } from 'terraso-mobile-client/constants'; +import {useIsOffline} from 'terraso-mobile-client/hooks/connectivityHooks'; import { initMapSearch, Suggestion, @@ -39,6 +40,7 @@ import {isValidCoordinates} from 'terraso-mobile-client/util'; const {getSuggestions, retrieveFeature} = initMapSearch(); export const useMapSuggestions = () => { + const isOffline = useIsOffline(); const [coords, setCoords] = useState(undefined); const [suggestions, setSuggestions] = useState([]); @@ -70,7 +72,7 @@ export const useMapSuggestions = () => { const [latitude, longitude] = queryText.split(',').map(Number); setCoords({latitude, longitude}); setSuggestions([]); - } else { + } else if (!isOffline) { setCoords(undefined); if (queryText.length >= MAP_QUERY_MIN_LENGTH) { try { @@ -91,9 +93,12 @@ export const useMapSuggestions = () => { }); setSuggestions([]); } + } else { + setCoords(undefined); + setSuggestions([]); } }, - [makeSuggestionsApiCall], + [makeSuggestionsApiCall, isOffline], ); const debouncedQuerySuggestions = useDebouncedCallback( querySuggestions, From 72e8ebc156c2f682a7c1a2a9d5161b7aeff76212 Mon Sep 17 00:00:00 2001 From: Ruxandra Machedon Date: Mon, 16 Dec 2024 10:44:12 -0500 Subject: [PATCH 4/4] refactor: give better names to search box components that get shown/hidden --- .../components/search/MapSearch.tsx | 18 +++++++++++------- ...ineBox.tsx => MapSearchOfflineAlertBox.tsx} | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) rename dev-client/src/screens/SitesScreen/components/search/{MapSearchOfflineBox.tsx => MapSearchOfflineAlertBox.tsx} (96%) diff --git a/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx b/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx index 144b4a097..d59db92dc 100644 --- a/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx +++ b/dev-client/src/screens/SitesScreen/components/search/MapSearch.tsx @@ -37,7 +37,7 @@ import {useSitesScreenContext} from 'terraso-mobile-client/context/SitesScreenCo import {useUpdatedForegroundPermissions} from 'terraso-mobile-client/hooks/appPermissionsHooks'; import {useIsOffline} from 'terraso-mobile-client/hooks/connectivityHooks'; import {useMapSuggestions} from 'terraso-mobile-client/hooks/useMapSuggestions'; -import {MapSearchOfflineBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchOfflineBox'; +import {MapSearchOfflineAlertBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchOfflineAlertBox'; import {MapSearchSuggestionBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchSuggestionBox'; type Props = { @@ -52,7 +52,7 @@ export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { const [query, setQuery] = useState(''); const {coords, suggestions, querySuggestions, lookupFeature} = useMapSuggestions(); - const [hideResults, setHideResults] = useState(false); + const [showAutocomplete, setShowAutocomplete] = useState(false); const sitesScreen = useSitesScreenContext(); useEffect(() => { @@ -70,7 +70,7 @@ export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { const selectQuery = useCallback( (name: string, mapboxId: string) => { setQuery(name); - setHideResults(true); + setShowAutocomplete(false); lookupFeature(mapboxId); Keyboard.dismiss(); }, @@ -92,7 +92,7 @@ export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { suggestion.mapbox_id, @@ -114,11 +114,11 @@ export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { querySuggestions(newText); }} onFocus={() => { - setHideResults(false); + setShowAutocomplete(true); querySuggestions(query); }} onEndEditing={() => { - setHideResults(true); + setShowAutocomplete(false); }} value={query} placeholder={t('search.placeholder')} @@ -128,7 +128,11 @@ export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => { }} inputStyle={searchBarStyles.input} /> - {isOffline && !hideResults ? : <>} + {isOffline && showAutocomplete ? ( + + ) : ( + <> + )} )} /> diff --git a/dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineBox.tsx b/dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineAlertBox.tsx similarity index 96% rename from dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineBox.tsx rename to dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineAlertBox.tsx index 105542ade..a7f204606 100644 --- a/dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineBox.tsx +++ b/dev-client/src/screens/SitesScreen/components/search/MapSearchOfflineAlertBox.tsx @@ -21,7 +21,7 @@ import {DisableableText} from 'terraso-mobile-client/components/content/typograp import {TranslatedContent} from 'terraso-mobile-client/components/content/typography/TranslatedContent'; import {convertColorProp} from 'terraso-mobile-client/components/util/nativeBaseAdapters'; -export const MapSearchOfflineBox = () => { +export const MapSearchOfflineAlertBox = () => { return (