Enhanced map with search, auto-redirect to searched locations, and interactive property markers
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
|||||||
Dimensions,
|
Dimensions,
|
||||||
Animated,
|
Animated,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
import MapView from 'react-native-map-clustering';
|
import MapView from 'react-native-map-clustering';
|
||||||
@@ -18,7 +20,7 @@ import Geolocation from '@react-native-community/geolocation';
|
|||||||
|
|
||||||
const { width } = Dimensions.get('window');
|
const { width } = Dimensions.get('window');
|
||||||
|
|
||||||
/* ---------------- DEFAULT REGION (MUST EXIST) ---------------- */
|
/* ---------------- DEFAULT REGION ---------------- */
|
||||||
|
|
||||||
const DEFAULT_REGION: Region = {
|
const DEFAULT_REGION: Region = {
|
||||||
latitude: 25.48131,
|
latitude: 25.48131,
|
||||||
@@ -61,14 +63,15 @@ const PriceMarker = ({ item, onPress, selected }: any) => {
|
|||||||
|
|
||||||
/* ---------------- MAIN SCREEN ---------------- */
|
/* ---------------- MAIN SCREEN ---------------- */
|
||||||
|
|
||||||
const MapScreen: React.FC = () => {
|
const MapScreen = () => {
|
||||||
const mapRef = useRef<MapView>(null);
|
const mapRef = useRef<MapView>(null);
|
||||||
|
|
||||||
const [region, setRegion] = useState<Region>(DEFAULT_REGION);
|
const [region, setRegion] = useState<Region>(DEFAULT_REGION);
|
||||||
const [mapType, setMapType] = useState<MapType>('standard');
|
const [mapType, setMapType] = useState<MapType>('standard');
|
||||||
const [properties, setProperties] = useState<any[]>([]);
|
const [properties, setProperties] = useState<any[]>([]);
|
||||||
const [selectedItem, setSelectedItem] = useState<any>(null);
|
const [selectedItem, setSelectedItem] = useState<any>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
|
|
||||||
const popupAnim = useRef(new Animated.Value(0)).current;
|
const popupAnim = useRef(new Animated.Value(0)).current;
|
||||||
|
|
||||||
@@ -76,7 +79,6 @@ const MapScreen: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
requestLocation();
|
requestLocation();
|
||||||
fetchProperties();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -112,11 +114,14 @@ const MapScreen: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ---------------- API CALL ---------------- */
|
/* ---------------- SEARCH API ---------------- */
|
||||||
|
|
||||||
|
const fetchProperties = async (locationName: string) => {
|
||||||
|
if (!locationName) return;
|
||||||
|
|
||||||
const fetchProperties = async () => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setSelectedItem(null);
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
'http://64.227.108.180:5000/property-search',
|
'http://64.227.108.180:5000/property-search',
|
||||||
@@ -124,7 +129,7 @@ const MapScreen: React.FC = () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
location: 'Teliarganj',
|
location: locationName,
|
||||||
radius: 500,
|
radius: 500,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -132,18 +137,23 @@ const MapScreen: React.FC = () => {
|
|||||||
|
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
if (json?.success && json?.location?.coordinates) {
|
if (
|
||||||
setProperties(json.properties || []);
|
json?.success &&
|
||||||
|
json?.location?.coordinates?.lat &&
|
||||||
const apiRegion = {
|
json?.location?.coordinates?.lng
|
||||||
|
) {
|
||||||
|
const newRegion = {
|
||||||
latitude: json.location.coordinates.lat,
|
latitude: json.location.coordinates.lat,
|
||||||
longitude: json.location.coordinates.lng,
|
longitude: json.location.coordinates.lng,
|
||||||
latitudeDelta: 0.02,
|
latitudeDelta: 0.02,
|
||||||
longitudeDelta: 0.02,
|
longitudeDelta: 0.02,
|
||||||
};
|
};
|
||||||
|
|
||||||
setRegion(apiRegion);
|
setProperties(json.properties || []);
|
||||||
mapRef.current?.animateToRegion(apiRegion, 1000);
|
setRegion(newRegion);
|
||||||
|
|
||||||
|
// ✅ MOVE MAP TO SEARCH LOCATION
|
||||||
|
mapRef.current?.animateToRegion(newRegion, 1000);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('API Error:', error);
|
console.log('API Error:', error);
|
||||||
@@ -161,10 +171,29 @@ const MapScreen: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
{/* -------- SEARCH BAR -------- */}
|
||||||
|
<View style={styles.searchContainer}>
|
||||||
|
<TextInput
|
||||||
|
value={searchText}
|
||||||
|
onChangeText={setSearchText}
|
||||||
|
placeholder="Search area, locality..."
|
||||||
|
style={styles.searchInput}
|
||||||
|
returnKeyType="search"
|
||||||
|
onSubmitEditing={() => fetchProperties(searchText)}
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.searchButton}
|
||||||
|
onPress={() => fetchProperties(searchText)}
|
||||||
|
>
|
||||||
|
<Text style={styles.searchText}>Search</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* -------- MAP -------- */}
|
||||||
<MapView
|
<MapView
|
||||||
ref={mapRef}
|
ref={mapRef}
|
||||||
style={StyleSheet.absoluteFillObject}
|
style={StyleSheet.absoluteFillObject}
|
||||||
region={region} // ✅ REQUIRED
|
region={region}
|
||||||
onRegionChangeComplete={(r) => {
|
onRegionChangeComplete={(r) => {
|
||||||
if (!r?.longitudeDelta) return;
|
if (!r?.longitudeDelta) return;
|
||||||
setRegion(r);
|
setRegion(r);
|
||||||
@@ -193,7 +222,7 @@ const MapScreen: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* -------- ZILLOW STYLE POPUP -------- */}
|
{/* -------- BOTTOM POPUP -------- */}
|
||||||
{selectedItem && (
|
{selectedItem && (
|
||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
@@ -237,6 +266,33 @@ const MapScreen: React.FC = () => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { flex: 1 },
|
container: { flex: 1 },
|
||||||
|
|
||||||
|
searchContainer: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 40,
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
zIndex: 10,
|
||||||
|
flexDirection: 'row',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 8,
|
||||||
|
elevation: 6,
|
||||||
|
},
|
||||||
|
searchInput: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
|
searchButton: {
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#8B0000',
|
||||||
|
borderTopRightRadius: 8,
|
||||||
|
borderBottomRightRadius: 8,
|
||||||
|
},
|
||||||
|
searchText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
|
||||||
pin: {
|
pin: {
|
||||||
backgroundColor: '#8B0000',
|
backgroundColor: '#8B0000',
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
|
|||||||
Reference in New Issue
Block a user