import { useEffect, useRef, useState } from "react"; import { Image, Keyboard, Text, TextInput, TouchableOpacity, View, } from "react-native"; import { icons } from "@/constants"; import { useT } from "@/lib/i18n"; import { useTheme } from "@/lib/theme"; import type { GoogleInputProps } from "@/types/type"; const googleApiKey = process.env.EXPO_PUBLIC_GOOGLE_API_KEY!; interface Suggestion { placeId: string; text: string; } // Places API (New) — the legacy Places web service is unavailable to // newer Google Cloud projects. Results are restricted to Lebanon. const fetchSuggestions = async (input: string): Promise => { const res = await fetch( "https://places.googleapis.com/v1/places:autocomplete", { method: "POST", headers: { "Content-Type": "application/json", "X-Goog-Api-Key": googleApiKey, }, body: JSON.stringify({ input, languageCode: "en", includedRegionCodes: ["lb"], }), }, ); const data = await res.json(); return (data.suggestions ?? []) .map((s: any) => ({ placeId: s.placePrediction?.placeId as string, text: s.placePrediction?.text?.text as string, })) .filter((s: Suggestion) => s.placeId && s.text); }; const fetchPlaceDetails = async (placeId: string) => { const res = await fetch(`https://places.googleapis.com/v1/places/${placeId}`, { headers: { "X-Goog-Api-Key": googleApiKey, "X-Goog-FieldMask": "location,formattedAddress", }, }); const data = await res.json(); return { latitude: data.location?.latitude as number, longitude: data.location?.longitude as number, address: data.formattedAddress as string, }; }; export const GoogleTextInput = ({ icon, initialLocation, containerStyles, textInputBackgroundColor, handlePress, }: GoogleInputProps) => { const t = useT(); const { isDark } = useTheme(); const [query, setQuery] = useState(""); const [suggestions, setSuggestions] = useState([]); const debounceRef = useRef | null>(null); const inputBg = textInputBackgroundColor || (isDark ? "#1a1a1a" : "white"); const inputShadow = isDark ? "#000000" : "#d4d4d4"; useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); if (query.trim().length < 3) { setSuggestions([]); return; } debounceRef.current = setTimeout(async () => { try { setSuggestions(await fetchSuggestions(query)); } catch (err) { console.log("[PLACES_AUTOCOMPLETE]: ", err); } }, 200); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [query]); const onSelect = async (suggestion: Suggestion) => { setQuery(suggestion.text); setSuggestions([]); // The search is over the moment a place is picked. Left open, the keyboard // covers whatever the next tap was meant to be — and inside a bottom sheet // it holds the sheet in its extended state on top of it. Keyboard.dismiss(); try { const details = await fetchPlaceDetails(suggestion.placeId); handlePress({ latitude: details.latitude, longitude: details.longitude, address: suggestion.text, }); } catch (err) { console.log("[PLACE_DETAILS]: ", err); } }; return ( {t("components.googleTextInput.searchAlt")} {/* Rendered as plain rows, not a FlatList. Places never returns more than a handful of predictions, so there is nothing to virtualise — and a list that scrolls inside the home feed (or inside the ride sheet) fights its parent for the gesture and swallows taps meant for a suggestion. */} {suggestions.length > 0 && ( {suggestions.map((item) => ( onSelect(item)} className="p-3 border-b border-general-700 dark:border-neutral-700" > {item.text} ))} )} ); };