API integration for displaying map data

This commit is contained in:
2026-01-05 23:24:26 +05:30
parent c1030874e3
commit 69f9d49cf1
2 changed files with 388 additions and 90 deletions

View File

@@ -5,65 +5,52 @@ import {
PermissionsAndroid, PermissionsAndroid,
Platform, Platform,
Text, Text,
TouchableOpacity,
FlatList, FlatList,
Image, Image,
Dimensions, Dimensions,
Animated, Animated,
ActivityIndicator,
} from 'react-native'; } from 'react-native';
import MapView from 'react-native-map-clustering'; import MapView from 'react-native-map-clustering';
import { Marker, MapType, Region } from 'react-native-maps'; import { Marker, MapType, Region } from 'react-native-maps';
import Geolocation from '@react-native-community/geolocation'; import Geolocation from '@react-native-community/geolocation';
const { width } = Dimensions.get('window'); const { width } = Dimensions.get('window');
/* ---------------- CONSTANTS ---------------- */ /* ---------------- DEFAULT REGION (MUST EXIST) ---------------- */
const DEFAULT_REGION: Region = { const DEFAULT_REGION: Region = {
latitude: 47.6062, latitude: 25.48131,
longitude: -122.3321, longitude: 81.854249,
latitudeDelta: 0.15, latitudeDelta: 0.02,
longitudeDelta: 0.15, longitudeDelta: 0.02,
}; };
/* ---------------- MOCK DATA ---------------- */
const generateProperties = (count = 150) =>
Array.from({ length: count }).map((_, i) => ({
id: `${i}`,
lat: 47.5 + Math.random() * 0.25,
lng: -122.45 + Math.random() * 0.3,
price: Math.floor(300000 + Math.random() * 4000000),
beds: Math.floor(1 + Math.random() * 5),
baths: Math.floor(1 + Math.random() * 4),
image: `https://picsum.photos/300/200?random=${i}`,
}));
const properties = generateProperties();
/* ---------------- HELPERS ---------------- */ /* ---------------- HELPERS ---------------- */
const formatPrice = (value: number) => { const formatPrice = (value: number) => {
if (!value) return '';
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`; if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
if (value >= 1000) return `${Math.round(value / 1000)}K`; if (value >= 1000) return `${Math.round(value / 1000)}K`;
return value.toString(); return value.toString();
}; };
/* ---------------- PRICE MARKER ---------------- */ /* ---------------- PRICE MARKER ---------------- */
const PriceMarker = ({ item, onPress, selected }: any) => {
const [tracks, setTracks] = useState(true);
useEffect(() => { const PriceMarker = ({ item, onPress, selected }: any) => {
const t = setTimeout(() => setTracks(false), 300); if (!item?.location) return null;
return () => clearTimeout(t);
}, []);
return ( return (
<Marker <Marker
coordinate={{ latitude: item.lat, longitude: item.lng }} coordinate={{
tracksViewChanges={tracks} latitude: item.location.lat,
longitude: item.location.lng,
}}
onPress={(e) => { onPress={(e) => {
e.stopPropagation(); e.stopPropagation();
onPress(item); onPress(item);
}} }}
pinColor={selected ? 'gold' : '#8B0000'}
> >
<View style={[styles.pin, selected && styles.selectedPin]}> <View style={[styles.pin, selected && styles.selectedPin]}>
<Text style={styles.pinText}>{formatPrice(item.price)}</Text> <Text style={styles.pinText}>{formatPrice(item.price)}</Text>
@@ -73,25 +60,36 @@ const PriceMarker = ({ item, onPress, selected }: any) => {
}; };
/* ---------------- MAIN SCREEN ---------------- */ /* ---------------- MAIN SCREEN ---------------- */
const MapScreen: React.FC = () => { const MapScreen: React.FC = () => {
const mapRef = useRef<MapView>(null); const mapRef = useRef<MapView>(null);
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 [selectedItem, setSelectedItem] = useState<any>(null); const [selectedItem, setSelectedItem] = useState<any>(null);
const [popupAnim] = useState(new Animated.Value(0)); const [loading, setLoading] = useState(true);
const popupAnim = useRef(new Animated.Value(0)).current;
/* ---------------- LIFE CYCLE ---------------- */
useEffect(() => { useEffect(() => {
locateUser(); requestLocation();
fetchProperties();
}, []); }, []);
useEffect(() => { useEffect(() => {
Animated.timing(popupAnim, { Animated.timing(popupAnim, {
toValue: selectedItem ? 1 : 0, toValue: selectedItem ? 1 : 0,
duration: 300, duration: 250,
useNativeDriver: true, useNativeDriver: true,
}).start(); }).start();
}, [selectedItem]); }, [selectedItem]);
const locateUser = async () => { /* ---------------- LOCATION ---------------- */
const requestLocation = async () => {
if (Platform.OS === 'android') { if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request( const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
@@ -101,64 +99,101 @@ const MapScreen: React.FC = () => {
Geolocation.getCurrentPosition((pos) => { Geolocation.getCurrentPosition((pos) => {
const { latitude, longitude } = pos.coords; const { latitude, longitude } = pos.coords;
mapRef.current?.animateToRegion(
{ const newRegion = {
latitude, latitude,
longitude, longitude,
latitudeDelta: 0.05, latitudeDelta: 0.02,
longitudeDelta: 0.05, longitudeDelta: 0.02,
}, };
1000
); setRegion(newRegion);
mapRef.current?.animateToRegion(newRegion, 1000);
}); });
}; };
/* ---------------- API CALL ---------------- */
const fetchProperties = async () => {
try {
setLoading(true);
const response = await fetch(
'http://128.199.25.149:5000/property-search',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
location: 'Lucknow, Uttar Pradesh, India',
radius: 500,
}),
}
);
const json = await response.json();
if (json?.success && json?.location?.coordinates) {
setProperties(json.properties || []);
const apiRegion = {
latitude: json.location.coordinates.lat,
longitude: json.location.coordinates.lng,
latitudeDelta: 0.02,
longitudeDelta: 0.02,
};
setRegion(apiRegion);
mapRef.current?.animateToRegion(apiRegion, 1000);
}
} catch (error) {
console.log('API Error:', error);
} finally {
setLoading(false);
}
};
const popupTranslateY = popupAnim.interpolate({ const popupTranslateY = popupAnim.interpolate({
inputRange: [0, 1], inputRange: [0, 1],
outputRange: [200, 0], outputRange: [200, 0],
}); });
/* ---------------- RENDER ---------------- */
return ( return (
<View style={styles.container}> <View style={styles.container}>
<MapView <MapView
ref={mapRef} ref={mapRef}
style={StyleSheet.absoluteFillObject} style={StyleSheet.absoluteFillObject}
initialRegion={DEFAULT_REGION} region={region} // ✅ REQUIRED
onRegionChangeComplete={(r) => {
if (!r?.longitudeDelta) return;
setRegion(r);
setMapType(r.latitudeDelta < 0.02 ? 'satellite' : 'standard');
}}
showsUserLocation showsUserLocation
animationEnabled animationEnabled
clusterPressMaxZoom={16} clusterPressMaxZoom={16}
spiralEnabled={false} spiralEnabled={false}
mapType={mapType} mapType={mapType}
onRegionChangeComplete={(r) => setMapType(r.latitudeDelta < 0.02 ? 'satellite' : 'standard')}
renderCluster={(cluster, onPress) => {
const { geometry, properties } = cluster;
return (
<Marker
key={`cluster-${properties.cluster_id}`}
coordinate={{
latitude: geometry.coordinates[1],
longitude: geometry.coordinates[0],
}}
onPress={onPress}
>
<View style={styles.clusterBubble}>
<Text style={styles.clusterText}>{properties.point_count}</Text>
</View>
</Marker>
);
}}
> >
{properties.map((item) => ( {properties.map((item) => (
<PriceMarker <PriceMarker
key={item.id} key={item.id}
item={item} item={item}
selected={selectedItem?.id === item.id} selected={selectedItem?.id === item.id}
onPress={(item) => setSelectedItem(item)} onPress={setSelectedItem}
/> />
))} ))}
</MapView> </MapView>
{/* -------- HORIZONTAL ZILLOW-STYLE POPUP -------- */} {/* -------- LOADER -------- */}
{loading && (
<View style={styles.loader}>
<ActivityIndicator size="large" />
</View>
)}
{/* -------- ZILLOW STYLE POPUP -------- */}
{selectedItem && ( {selectedItem && (
<Animated.View <Animated.View
style={[ style={[
@@ -173,11 +208,19 @@ const MapScreen: React.FC = () => {
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
renderItem={({ item }) => ( renderItem={({ item }) => (
<View style={styles.card}> <View style={styles.card}>
<Image source={{ uri: item.image }} style={styles.cardImage} /> <Image
source={{ uri: 'https://picsum.photos/400/200' }}
style={styles.cardImage}
/>
<View style={styles.cardContent}> <View style={styles.cardContent}>
<Text style={styles.cardPrice}>${formatPrice(item.price)}</Text> <Text style={styles.cardPrice}>
{item.price_display}
</Text>
<Text style={styles.cardDetails}> <Text style={styles.cardDetails}>
{item.beds} Beds {item.baths} Baths {item.bedrooms} Beds {item.bathrooms} Baths
</Text>
<Text style={styles.cardAddress} numberOfLines={2}>
{item.address}
</Text> </Text>
</View> </View>
</View> </View>
@@ -190,6 +233,7 @@ const MapScreen: React.FC = () => {
}; };
/* ---------------- STYLES ---------------- */ /* ---------------- STYLES ---------------- */
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1 }, container: { flex: 1 },
@@ -201,48 +245,54 @@ const styles = StyleSheet.create({
borderWidth: 1, borderWidth: 1,
borderColor: '#fff', borderColor: '#fff',
}, },
selectedPin: { backgroundColor: 'gold', borderColor: '#333' }, selectedPin: {
pinText: { color: '#fff', fontWeight: '600', fontSize: 12 }, backgroundColor: '#F4C430',
borderColor: '#333',
clusterBubble: { },
backgroundColor: '#8B0000', pinText: {
paddingHorizontal: 16, color: '#fff',
paddingVertical: 10, fontWeight: '600',
borderRadius: 24, fontSize: 12,
borderWidth: 2,
borderColor: '#fff',
}, },
clusterText: { color: '#fff', fontWeight: 'bold', fontSize: 14 },
popupContainer: { popupContainer: {
position: 'absolute', position: 'absolute',
bottom: 20, bottom: 20,
left: 0,
right: 0,
}, },
card: { card: {
backgroundColor: '#fff', backgroundColor: '#fff',
width: width * 0.8, width: width * 0.8,
marginHorizontal: 10, marginHorizontal: 10,
borderRadius: 16, borderRadius: 16,
overflow: 'hidden', overflow: 'hidden',
elevation: 5, elevation: 6,
}, },
cardImage: { cardImage: {
width: '100%', width: '100%',
height: 140, height: 140,
}, },
cardContent: { padding: 12 }, cardContent: {
cardPrice: { fontSize: 18, fontWeight: 'bold' }, padding: 12,
cardDetails: { marginVertical: 4, color: '#666' }, },
cardBtn: { cardPrice: {
marginTop: 8, fontSize: 18,
backgroundColor: '#8B0000', fontWeight: 'bold',
padding: 10, },
borderRadius: 8, cardDetails: {
alignItems: 'center', marginVertical: 4,
color: '#555',
},
cardAddress: {
fontSize: 13,
color: '#777',
},
loader: {
position: 'absolute',
top: '50%',
alignSelf: 'center',
}, },
cardBtnText: { color: '#fff', fontWeight: '600' },
}); });
export default MapScreen; export default MapScreen;

248
src/screens/MapOld.tsx Normal file
View File

@@ -0,0 +1,248 @@
import React, { useEffect, useRef, useState } from 'react';
import {
View,
StyleSheet,
PermissionsAndroid,
Platform,
Text,
TouchableOpacity,
FlatList,
Image,
Dimensions,
Animated,
} from 'react-native';
import MapView from 'react-native-map-clustering';
import { Marker, MapType, Region } from 'react-native-maps';
import Geolocation from '@react-native-community/geolocation';
const { width } = Dimensions.get('window');
/* ---------------- CONSTANTS ---------------- */
const DEFAULT_REGION: Region = {
latitude: 47.6062,
longitude: -122.3321,
latitudeDelta: 0.15,
longitudeDelta: 0.15,
};
/* ---------------- MOCK DATA ---------------- */
const generateProperties = (count = 150) =>
Array.from({ length: count }).map((_, i) => ({
id: `${i}`,
lat: 47.5 + Math.random() * 0.25,
lng: -122.45 + Math.random() * 0.3,
price: Math.floor(300000 + Math.random() * 4000000),
beds: Math.floor(1 + Math.random() * 5),
baths: Math.floor(1 + Math.random() * 4),
image: `https://picsum.photos/300/200?random=${i}`,
}));
const properties = generateProperties();
/* ---------------- HELPERS ---------------- */
const formatPrice = (value: number) => {
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
if (value >= 1000) return `${Math.round(value / 1000)}K`;
return value.toString();
};
/* ---------------- PRICE MARKER ---------------- */
const PriceMarker = ({ item, onPress, selected }: any) => {
const [tracks, setTracks] = useState(true);
useEffect(() => {
const t = setTimeout(() => setTracks(false), 300);
return () => clearTimeout(t);
}, []);
return (
<Marker
coordinate={{ latitude: item.lat, longitude: item.lng }}
tracksViewChanges={tracks}
onPress={(e) => {
e.stopPropagation();
onPress(item);
}}
pinColor={selected ? 'gold' : '#8B0000'}
>
<View style={[styles.pin, selected && styles.selectedPin]}>
<Text style={styles.pinText}>{formatPrice(item.price)}</Text>
</View>
</Marker>
);
};
/* ---------------- MAIN SCREEN ---------------- */
const MapScreen: React.FC = () => {
const mapRef = useRef<MapView>(null);
const [mapType, setMapType] = useState<MapType>('standard');
const [selectedItem, setSelectedItem] = useState<any>(null);
const [popupAnim] = useState(new Animated.Value(0));
useEffect(() => {
locateUser();
}, []);
useEffect(() => {
Animated.timing(popupAnim, {
toValue: selectedItem ? 1 : 0,
duration: 300,
useNativeDriver: true,
}).start();
}, [selectedItem]);
const locateUser = async () => {
if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) return;
}
Geolocation.getCurrentPosition((pos) => {
const { latitude, longitude } = pos.coords;
mapRef.current?.animateToRegion(
{
latitude,
longitude,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
},
1000
);
});
};
const popupTranslateY = popupAnim.interpolate({
inputRange: [0, 1],
outputRange: [200, 0],
});
return (
<View style={styles.container}>
<MapView
ref={mapRef}
style={StyleSheet.absoluteFillObject}
initialRegion={DEFAULT_REGION}
showsUserLocation
animationEnabled
clusterPressMaxZoom={16}
spiralEnabled={false}
mapType={mapType}
onRegionChangeComplete={(r) => setMapType(r.latitudeDelta < 0.02 ? 'satellite' : 'standard')}
renderCluster={(cluster, onPress) => {
const { geometry, properties } = cluster;
return (
<Marker
key={`cluster-${properties.cluster_id}`}
coordinate={{
latitude: geometry.coordinates[1],
longitude: geometry.coordinates[0],
}}
onPress={onPress}
>
<View style={styles.clusterBubble}>
<Text style={styles.clusterText}>{properties.point_count}</Text>
</View>
</Marker>
);
}}
>
{properties.map((item) => (
<PriceMarker
key={item.id}
item={item}
selected={selectedItem?.id === item.id}
onPress={(item) => setSelectedItem(item)}
/>
))}
</MapView>
{/* -------- HORIZONTAL ZILLOW-STYLE POPUP -------- */}
{selectedItem && (
<Animated.View
style={[
styles.popupContainer,
{ transform: [{ translateY: popupTranslateY }] },
]}
>
<FlatList
horizontal
data={[selectedItem]}
keyExtractor={(item) => item.id}
showsHorizontalScrollIndicator={false}
renderItem={({ item }) => (
<View style={styles.card}>
<Image source={{ uri: item.image }} style={styles.cardImage} />
<View style={styles.cardContent}>
<Text style={styles.cardPrice}>${formatPrice(item.price)}</Text>
<Text style={styles.cardDetails}>
{item.beds} Beds {item.baths} Baths
</Text>
</View>
</View>
)}
/>
</Animated.View>
)}
</View>
);
};
/* ---------------- STYLES ---------------- */
const styles = StyleSheet.create({
container: { flex: 1 },
pin: {
backgroundColor: '#8B0000',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
borderWidth: 1,
borderColor: '#fff',
},
selectedPin: { backgroundColor: 'gold', borderColor: '#333' },
pinText: { color: '#fff', fontWeight: '600', fontSize: 12 },
clusterBubble: {
backgroundColor: '#8B0000',
paddingHorizontal: 16,
paddingVertical: 10,
borderRadius: 24,
borderWidth: 2,
borderColor: '#fff',
},
clusterText: { color: '#fff', fontWeight: 'bold', fontSize: 14 },
popupContainer: {
position: 'absolute',
bottom: 20,
left: 0,
right: 0,
},
card: {
backgroundColor: '#fff',
width: width * 0.8,
marginHorizontal: 10,
borderRadius: 16,
overflow: 'hidden',
elevation: 5,
},
cardImage: {
width: '100%',
height: 140,
},
cardContent: { padding: 12 },
cardPrice: { fontSize: 18, fontWeight: 'bold' },
cardDetails: { marginVertical: 4, color: '#666' },
cardBtn: {
marginTop: 8,
backgroundColor: '#8B0000',
padding: 10,
borderRadius: 8,
alignItems: 'center',
},
cardBtnText: { color: '#fff', fontWeight: '600' },
});
export default MapScreen;