Skip to content

Commit

Permalink
Merge pull request #43011 from callstack-internal/issues/30123
Browse files Browse the repository at this point in the history
Reapply - Add offline search functionality for addresses
  • Loading branch information
neil-marcellini authored Jul 8, 2024
2 parents 4e1470b + 363ab6f commit 29e8df5
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 21 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const onboardingChoices = {
type OnboardingPurposeType = ValueOf<typeof onboardingChoices>;

const CONST = {
RECENT_WAYPOINTS_NUMBER: 20,
DEFAULT_DB_NAME: 'OnyxDB',
DEFAULT_TABLE_NAME: 'keyvaluepairs',
DEFAULT_ONYX_DUMP_FILE_NAME: 'onyx-state.txt',
Expand Down
60 changes: 44 additions & 16 deletions src/components/AddressSearch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,24 @@ import CONST from '@src/CONST';
import type {Address} from '@src/types/onyx/PrivatePersonalDetails';
import CurrentLocationButton from './CurrentLocationButton';
import isCurrentTargetInsideContainer from './isCurrentTargetInsideContainer';
import type {AddressSearchProps} from './types';
import type {AddressSearchProps, PredefinedPlace} from './types';

/**
* Check if the place matches the search by the place name or description.
* @param search The search string for a place
* @param place The place to check for a match on the search
* @returns true if search is related to place, otherwise it returns false.
*/
function isPlaceMatchForSearch(search: string, place: PredefinedPlace): boolean {
if (!search) {
return true;
}
if (!place) {
return false;
}
const fullSearchSentence = `${place.name ?? ''} ${place.description}`;
return search.split(' ').every((searchTerm) => !searchTerm || fullSearchSentence.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase()));
}

// The error that's being thrown below will be ignored until we fork the
// react-native-google-places-autocomplete repo and replace the
Expand All @@ -42,6 +59,7 @@ function AddressSearch(
isLimitedToUSA = false,
label,
maxInputLength,
onFocus,
onBlur,
onInputChange,
onPress,
Expand Down Expand Up @@ -72,7 +90,7 @@ function AddressSearch(
const [isTyping, setIsTyping] = useState(false);
const [isFocused, setIsFocused] = useState(false);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const [searchValue, setSearchValue] = useState(value || defaultValue || '');
const [searchValue, setSearchValue] = useState('');
const [locationErrorCode, setLocationErrorCode] = useState<GeolocationErrorCodeType>(null);
const [isFetchingCurrentLocation, setIsFetchingCurrentLocation] = useState(false);
const shouldTriggerGeolocationCallbacks = useRef(true);
Expand Down Expand Up @@ -282,7 +300,7 @@ function AddressSearch(
// eslint-disable-next-line react/jsx-no-useless-fragment
<>
{(predefinedPlaces?.length ?? 0) > 0 && (
<>
<View style={styles.overflowAuto}>
{/* This will show current location button in list if there are some recent destinations */}
{shouldShowCurrentLocationButton && (
<CurrentLocationButton
Expand All @@ -291,7 +309,7 @@ function AddressSearch(
/>
)}
{!value && <Text style={[styles.textLabel, styles.colorMuted, styles.pv2, styles.ph3, styles.overflowAuto]}>{translate('common.recentDestinations')}</Text>}
</>
</View>
)}
</>
);
Expand All @@ -304,10 +322,16 @@ function AddressSearch(
};
}, []);

const filteredPredefinedPlaces = useMemo(() => {
if (!searchValue) {
return predefinedPlaces ?? [];
}
return predefinedPlaces?.filter((predefinedPlace) => isPlaceMatchForSearch(searchValue, predefinedPlace)) ?? [];
}, [predefinedPlaces, searchValue]);

const listEmptyComponent = useCallback(
() =>
!!isOffline || !isTyping ? null : <Text style={[styles.textLabel, styles.colorMuted, styles.pv4, styles.ph3, styles.overflowAuto]}>{translate('common.noResultsFound')}</Text>,
[isOffline, isTyping, styles, translate],
() => (!isTyping ? null : <Text style={[styles.textLabel, styles.colorMuted, styles.pv4, styles.ph3, styles.overflowAuto]}>{translate('common.noResultsFound')}</Text>),
[isTyping, styles, translate],
);

const listLoader = useCallback(
Expand Down Expand Up @@ -348,7 +372,7 @@ function AddressSearch(
fetchDetails
suppressDefaultStyles
enablePoweredByContainer={false}
predefinedPlaces={predefinedPlaces ?? undefined}
predefinedPlaces={filteredPredefinedPlaces}
listEmptyComponent={listEmptyComponent}
listLoaderComponent={listLoader}
renderHeaderComponent={renderHeaderComponent}
Expand All @@ -357,7 +381,7 @@ function AddressSearch(
const subtitle = data.isPredefinedPlace ? data.description : data.structured_formatting.secondary_text;
return (
<View>
{!!title && <Text style={[styles.googleSearchText]}>{title}</Text>}
{!!title && <Text style={styles.googleSearchText}>{title}</Text>}
<Text style={[title ? styles.textLabelSupporting : styles.googleSearchText]}>{subtitle}</Text>
</View>
);
Expand Down Expand Up @@ -391,6 +415,7 @@ function AddressSearch(
shouldSaveDraft,
onFocus: () => {
setIsFocused(true);
onFocus?.();
},
onBlur: (event) => {
if (!isCurrentTargetInsideContainer(event, containerRef)) {
Expand Down Expand Up @@ -420,10 +445,11 @@ function AddressSearch(
}}
styles={{
textInputContainer: [styles.flexColumn],
listView: [StyleUtils.getGoogleListViewStyle(displayListViewBorder), styles.overflowAuto, styles.borderLeft, styles.borderRight, !isFocused && {height: 0}],
listView: [StyleUtils.getGoogleListViewStyle(displayListViewBorder), styles.borderLeft, styles.borderRight, !isFocused && styles.h0],
row: [styles.pv4, styles.ph3, styles.overflowAuto],
description: [styles.googleSearchText],
separator: [styles.googleSearchSeparator],
separator: [styles.googleSearchSeparator, styles.overflowAuto],
container: [styles.mh100],
}}
numberOfLines={2}
isRowScrollable={false}
Expand All @@ -447,11 +473,13 @@ function AddressSearch(
)
}
placeholder=""
/>
<LocationErrorMessage
onClose={() => setLocationErrorCode(null)}
locationErrorCode={locationErrorCode}
/>
listViewDisplayed
>
<LocationErrorMessage
onClose={() => setLocationErrorCode(null)}
locationErrorCode={locationErrorCode}
/>
</GooglePlacesAutocomplete>
</View>
</ScrollView>
{isFetchingCurrentLocation && <FullScreenLoadingIndicator />}
Expand Down
11 changes: 9 additions & 2 deletions src/components/AddressSearch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@ type StreetValue = {
street: string;
};

type PredefinedPlace = Place & {
name?: string;
};

type AddressSearchProps = {
/** The ID used to uniquely identify the input in a Form */
inputID?: string;

/** Saves a draft of the input value when used in a form */
shouldSaveDraft?: boolean;

/** Callback that is called when the text input is focused */
onFocus?: () => void;

/** Callback that is called when the text input is blurred */
onBlur?: () => void;

Expand Down Expand Up @@ -64,7 +71,7 @@ type AddressSearchProps = {
canUseCurrentLocation?: boolean;

/** A list of predefined places that can be shown when the user isn't searching for something */
predefinedPlaces?: Place[] | null;
predefinedPlaces?: PredefinedPlace[] | null;

/** A map of inputID key names */
renamedInputKeys?: Address;
Expand All @@ -84,4 +91,4 @@ type AddressSearchProps = {

type IsCurrentTargetInsideContainerType = (event: FocusEvent | NativeSyntheticEvent<TextInputFocusEventData>, containerRef: RefObject<View | HTMLElement>) => boolean;

export type {CurrentLocationButtonProps, AddressSearchProps, IsCurrentTargetInsideContainerType, StreetValue};
export type {CurrentLocationButtonProps, AddressSearchProps, IsCurrentTargetInsideContainerType, StreetValue, PredefinedPlace};
2 changes: 1 addition & 1 deletion src/libs/actions/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function saveWaypoint(transactionID: string, index: string, waypoint: RecentWayp
if (!recentWaypointAlreadyExists && waypoint !== null) {
const clonedWaypoints = lodashClone(recentWaypoints);
clonedWaypoints.unshift(waypoint);
Onyx.merge(ONYXKEYS.NVP_RECENT_WAYPOINTS, clonedWaypoints.slice(0, 5));
Onyx.merge(ONYXKEYS.NVP_RECENT_WAYPOINTS, clonedWaypoints.slice(0, CONST.RECENT_WAYPOINTS_NUMBER));
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/pages/iou/request/step/IOURequestStepWaypoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,10 @@ export default withWritableReportOrNotFound(
recentWaypoints: {
key: ONYXKEYS.NVP_RECENT_WAYPOINTS,

// Only grab the most recent 5 waypoints because that's all that is shown in the UI. This also puts them into the format of data
// Only grab the most recent 20 waypoints because that's all that is shown in the UI. This also puts them into the format of data
// that the google autocomplete component expects for it's "predefined places" feature.
selector: (waypoints) =>
(waypoints ? waypoints.slice(0, 5) : []).map((waypoint) => ({
(waypoints ? waypoints.slice(0, CONST.RECENT_WAYPOINTS_NUMBER as number) : []).map((waypoint) => ({
name: waypoint.name,
description: waypoint.address ?? '',
geometry: {
Expand Down
3 changes: 3 additions & 0 deletions src/styles/utils/sizing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import type {ViewStyle} from 'react-native';
* https://getbootstrap.com/docs/5.0/utilities/sizing/
*/
export default {
h0: {
height: 0,
},
h100: {
height: '100%',
},
Expand Down

0 comments on commit 29e8df5

Please sign in to comment.