Skip to content

Commit

Permalink
feat: offline map search (#2638)
Browse files Browse the repository at this point in the history
* refactor: break apart MapSearch file into components

* feat: offline indicator for map search

* feat: don't hit suggestions API when offline

* refactor: give better names to search box components that get shown/hidden
  • Loading branch information
tm-ruxandra authored Dec 16, 2024
1 parent b5f4e85 commit 922e94f
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 56 deletions.
9 changes: 7 additions & 2 deletions dev-client/src/hooks/useMapSuggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Coords | undefined>(undefined);
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);

Expand Down Expand Up @@ -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 {
Expand All @@ -91,9 +93,12 @@ export const useMapSuggestions = () => {
});
setSuggestions([]);
}
} else {
setCoords(undefined);
setSuggestions([]);
}
},
[makeSuggestionsApiCall],
[makeSuggestionsApiCall, isOffline],
);
const debouncedQuerySuggestions = useDebouncedCallback(
querySuggestions,
Expand Down
2 changes: 1 addition & 1 deletion dev-client/src/screens/SitesScreen/SitesScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {SitesScreenContext} from 'terraso-mobile-client/context/SitesScreenConte
import {fetchSoilDataForUser} from 'terraso-mobile-client/model/soilData/soilDataGlobalReducer';
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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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';
Expand All @@ -32,49 +30,29 @@ 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 {useIsOffline} from 'terraso-mobile-client/hooks/connectivityHooks';
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 (
<Pressable w="100%" py={1} px={3} onPress={selectSuggestion}>
<Column>
<Text>{name}</Text>
<Text>{address}</Text>
</Column>
</Pressable>
);
}
import {MapSearchOfflineAlertBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchOfflineAlertBox';
import {MapSearchSuggestionBox} from 'terraso-mobile-client/screens/SitesScreen/components/search/MapSearchSuggestionBox';

type Props = {
zoomTo?: (site: Coords) => void;
zoomToUser?: () => void;
toggleMapLayer?: () => void;
};

export default function MapSearch({zoomTo, zoomToUser, toggleMapLayer}: Props) {
export const MapSearch = ({zoomTo, zoomToUser, toggleMapLayer}: Props) => {
const isOffline = useIsOffline();
const {t} = useTranslation();
const [query, setQuery] = useState('');
const {coords, suggestions, querySuggestions, lookupFeature} =
useMapSuggestions();
const [hideResults, setHideResults] = useState(false);
const [showAutocomplete, setShowAutocomplete] = useState(false);
const sitesScreen = useSitesScreenContext();

useEffect(() => {
Expand All @@ -92,7 +70,7 @@ export default function MapSearch({zoomTo, zoomToUser, toggleMapLayer}: Props) {
const selectQuery = useCallback(
(name: string, mapboxId: string) => {
setQuery(name);
setHideResults(true);
setShowAutocomplete(false);
lookupFeature(mapboxId);
Keyboard.dismiss();
},
Expand All @@ -114,12 +92,12 @@ export default function MapSearch({zoomTo, zoomToUser, toggleMapLayer}: Props) {
<View flex={1} pointerEvents="box-none">
<Autocomplete
data={suggestions}
hideResults={hideResults}
hideResults={!showAutocomplete || isOffline}
flatListProps={{
keyboardShouldPersistTaps: 'always',
keyExtractor: suggestion => suggestion.mapbox_id,
renderItem: ({item}) => (
<SuggestionBox
<MapSearchSuggestionBox
name={item.name}
address={item.place_formatted}
mapboxId={item.mapbox_id}
Expand All @@ -129,26 +107,33 @@ export default function MapSearch({zoomTo, zoomToUser, toggleMapLayer}: Props) {
}}
inputContainerStyle={{borderWidth: 0}} // eslint-disable-line react-native/no-inline-styles
renderTextInput={() => (
<Searchbar
onChangeText={newText => {
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}
/>
<>
<Searchbar
onChangeText={newText => {
setQuery(newText);
querySuggestions(newText);
}}
onFocus={() => {
setShowAutocomplete(true);
querySuggestions(query);
}}
onEndEditing={() => {
setShowAutocomplete(false);
}}
value={query}
placeholder={t('search.placeholder')}
style={{
...searchBarStyles.search,
...searchBarStyles.wideSearchOverride,
}}
inputStyle={searchBarStyles.input}
/>
{isOffline && showAutocomplete ? (
<MapSearchOfflineAlertBox />
) : (
<></>
)}
</>
)}
/>
</View>
Expand Down Expand Up @@ -177,4 +162,4 @@ export default function MapSearch({zoomTo, zoomToUser, toggleMapLayer}: Props) {
</Row>
</Box>
);
}
};
Original file line number Diff line number Diff line change
@@ -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 MapSearchOfflineAlertBox = () => {
return (
<View style={styles.container}>
<DisableableText disabled={true}>
<TranslatedContent i18nKey="site.search.offline" />
</DisableableText>
</View>
);
};

const styles = StyleSheet.create({
container: {
backgroundColor: convertColorProp('background.default'),
borderWidth: 1,
borderColor: convertColorProp('grey.300'),
paddingHorizontal: 12,
paddingVertical: 4,
marginHorizontal: 12,
width: '92%',
},
});
Original file line number Diff line number Diff line change
@@ -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 (
<Pressable w="100%" py={1} px={3} onPress={selectSuggestion}>
<Column>
<Text>{name}</Text>
<Text>{address}</Text>
</Column>
</Pressable>
);
};
1 change: 1 addition & 0 deletions dev-client/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,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)",
Expand Down
1 change: 1 addition & 0 deletions dev-client/src/translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 922e94f

Please sign in to comment.