172 lines
5.4 KiB
TypeScript
172 lines
5.4 KiB
TypeScript
// SearchComponent.tsx
|
|
import React, { useState, useRef, useCallback } from 'react';
|
|
import { View, TextInput, FlatList, TouchableOpacity, Text, StyleSheet, Alert, Keyboard } from 'react-native'; // Added Keyboard import
|
|
|
|
const GOOGLE_API_KEY = 'AIzaSyB1WZHDqjGk696AmVw7tA2sMAuOurt552Q'; // Your Google Places API Key
|
|
|
|
const TARGET_LATITUDE_DELTA = 0.005;
|
|
const TARGET_LONGITUDE_DELTA = 0.005;
|
|
|
|
interface SearchComponentProps {
|
|
onSelectPlace: (lat: number, lng: number, name: string) => void;
|
|
}
|
|
|
|
const SearchComponent: React.FC<SearchComponentProps> = ({ onSelectPlace }) => {
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [addressSuggestions, setAddressSuggestions] = useState<any[]>([]);
|
|
const debounceTimeout = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
// Ref to track if a suggestion is currently being pressed
|
|
const isPressingSuggestion = useRef(false);
|
|
|
|
const handleSearch = useCallback(async (text: string) => {
|
|
setSearchQuery(text);
|
|
if (debounceTimeout.current) {
|
|
clearTimeout(debounceTimeout.current);
|
|
}
|
|
|
|
if (text.length < 3) {
|
|
setAddressSuggestions([]);
|
|
return;
|
|
}
|
|
|
|
debounceTimeout.current = setTimeout(async () => {
|
|
try {
|
|
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${encodeURIComponent(text)}&key=${GOOGLE_API_KEY}&components=country:gb&location=51.5074,-0.1278&radius=20000`;
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data && data.predictions) {
|
|
setAddressSuggestions(data.predictions);
|
|
} else {
|
|
setAddressSuggestions([]);
|
|
}
|
|
} catch (error) {
|
|
console.error('Google Autocomplete search error:', error);
|
|
setAddressSuggestions([]);
|
|
}
|
|
}, 300);
|
|
}, []);
|
|
|
|
const handleSuggestionPress = useCallback(async (item: any) => {
|
|
console.log("Autocomplete suggestion clicked:", item.description);
|
|
|
|
// NEW: Dismiss the keyboard immediately after a suggestion is selected
|
|
Keyboard.dismiss();
|
|
|
|
setSearchQuery(item.description);
|
|
setAddressSuggestions([]); // Hide suggestions after selection
|
|
|
|
try {
|
|
const url = `https://maps.googleapis.com/maps/api/place/details/json?place_id=${item.place_id}&fields=geometry,name&key=${GOOGLE_API_KEY}`;
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data && data.result && data.result.geometry) {
|
|
const { lat, lng } = data.result.geometry.location;
|
|
const name = data.result.name || item.description;
|
|
onSelectPlace(lat, lng, name);
|
|
} else {
|
|
Alert.alert('Location Error', 'Could not get precise coordinates for selected place.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Google Place Details error:', error);
|
|
Alert.alert('Network Error', 'Failed to fetch location details.');
|
|
}
|
|
}, [onSelectPlace]);
|
|
|
|
const handleEnterPress = useCallback(() => {
|
|
if (addressSuggestions.length > 0) {
|
|
handleSuggestionPress(addressSuggestions[0]); // Take the first suggestion
|
|
}
|
|
}, [addressSuggestions, handleSuggestionPress]);
|
|
|
|
|
|
return (
|
|
<View style={styles.searchBarContainer}>
|
|
<TextInput
|
|
style={styles.searchBar}
|
|
placeholder="Search for a location (e.g., Oxford Street)"
|
|
placeholderTextColor="black" // Force dark placeholder for all platforms
|
|
value={searchQuery}
|
|
onChangeText={handleSearch}
|
|
onBlur={() => {
|
|
setTimeout(() => {
|
|
if (!isPressingSuggestion.current) {
|
|
setAddressSuggestions([]);
|
|
}
|
|
}, 100); // Small delay
|
|
}}
|
|
onSubmitEditing={handleEnterPress}
|
|
/>
|
|
{addressSuggestions.length > 0 && (
|
|
<FlatList
|
|
style={styles.suggestionsList}
|
|
data={addressSuggestions}
|
|
keyExtractor={(item) => item.place_id}
|
|
keyboardShouldPersistTaps="handled"
|
|
renderItem={({ item }) => (
|
|
<TouchableOpacity
|
|
style={styles.suggestionItem}
|
|
onPressIn={() => { isPressingSuggestion.current = true; }}
|
|
onPressOut={() => { isPressingSuggestion.current = false; }}
|
|
onPress={() => handleSuggestionPress(item)}
|
|
>
|
|
<Text style={styles.suggestionText}>{item.description}</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
initialNumToRender={5}
|
|
maxToRenderPerBatch={5}
|
|
windowSize={10}
|
|
/>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
searchBarContainer: {
|
|
position: 'absolute',
|
|
top: 20,
|
|
left: 10,
|
|
right: 10,
|
|
zIndex: 10,
|
|
backgroundColor: 'white',
|
|
borderRadius: 8,
|
|
padding: 10,
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 2 },
|
|
shadowOpacity: 0.25,
|
|
shadowRadius: 3.84,
|
|
elevation: 5,
|
|
},
|
|
searchBar: {
|
|
height: 40,
|
|
borderColor: '#ccc',
|
|
borderWidth: 1,
|
|
borderRadius: 8,
|
|
paddingHorizontal: 10,
|
|
fontSize: 16,
|
|
color: '#222', // Ensure dark text for visibility
|
|
},
|
|
suggestionsList: {
|
|
maxHeight: 200,
|
|
marginTop: 5,
|
|
borderRadius: 8,
|
|
overflow: 'hidden',
|
|
backgroundColor: 'white',
|
|
borderWidth: 1,
|
|
borderColor: '#eee',
|
|
},
|
|
suggestionItem: {
|
|
padding: 10,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#eee',
|
|
},
|
|
suggestionText: {
|
|
fontSize: 16,
|
|
color: '#222', // Ensure dark text for visibility
|
|
},
|
|
});
|
|
|
|
export default SearchComponent; |