From 69f9d49cf14b0cb117285542f20c3e337892a349 Mon Sep 17 00:00:00 2001 From: Mansi Date: Mon, 5 Jan 2026 23:24:26 +0530 Subject: [PATCH] API integration for displaying map data --- src/screens/Map.tsx | 230 +++++++++++++++++++++++--------------- src/screens/MapOld.tsx | 248 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 388 insertions(+), 90 deletions(-) create mode 100644 src/screens/MapOld.tsx diff --git a/src/screens/Map.tsx b/src/screens/Map.tsx index 2f5e427..1f7cc5a 100644 --- a/src/screens/Map.tsx +++ b/src/screens/Map.tsx @@ -5,65 +5,52 @@ import { PermissionsAndroid, Platform, Text, - TouchableOpacity, FlatList, Image, Dimensions, Animated, + ActivityIndicator, } 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 ---------------- */ +/* ---------------- DEFAULT REGION (MUST EXIST) ---------------- */ + const DEFAULT_REGION: Region = { - latitude: 47.6062, - longitude: -122.3321, - latitudeDelta: 0.15, - longitudeDelta: 0.15, + latitude: 25.48131, + longitude: 81.854249, + latitudeDelta: 0.02, + 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 ---------------- */ + const formatPrice = (value: number) => { + if (!value) return ''; 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); - }, []); +const PriceMarker = ({ item, onPress, selected }: any) => { + if (!item?.location) return null; return ( { e.stopPropagation(); onPress(item); }} - pinColor={selected ? 'gold' : '#8B0000'} > {formatPrice(item.price)} @@ -73,25 +60,36 @@ const PriceMarker = ({ item, onPress, selected }: any) => { }; /* ---------------- MAIN SCREEN ---------------- */ + const MapScreen: React.FC = () => { const mapRef = useRef(null); + + const [region, setRegion] = useState(DEFAULT_REGION); const [mapType, setMapType] = useState('standard'); + const [properties, setProperties] = useState([]); const [selectedItem, setSelectedItem] = useState(null); - const [popupAnim] = useState(new Animated.Value(0)); + const [loading, setLoading] = useState(true); + + const popupAnim = useRef(new Animated.Value(0)).current; + + /* ---------------- LIFE CYCLE ---------------- */ useEffect(() => { - locateUser(); + requestLocation(); + fetchProperties(); }, []); useEffect(() => { Animated.timing(popupAnim, { toValue: selectedItem ? 1 : 0, - duration: 300, + duration: 250, useNativeDriver: true, }).start(); }, [selectedItem]); - const locateUser = async () => { + /* ---------------- LOCATION ---------------- */ + + const requestLocation = async () => { if (Platform.OS === 'android') { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION @@ -101,64 +99,101 @@ const MapScreen: React.FC = () => { Geolocation.getCurrentPosition((pos) => { const { latitude, longitude } = pos.coords; - mapRef.current?.animateToRegion( - { - latitude, - longitude, - latitudeDelta: 0.05, - longitudeDelta: 0.05, - }, - 1000 - ); + + const newRegion = { + latitude, + longitude, + latitudeDelta: 0.02, + longitudeDelta: 0.02, + }; + + 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({ inputRange: [0, 1], outputRange: [200, 0], }); + /* ---------------- RENDER ---------------- */ + return ( { + if (!r?.longitudeDelta) return; + setRegion(r); + setMapType(r.latitudeDelta < 0.02 ? 'satellite' : 'standard'); + }} 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 ( - - - {properties.point_count} - - - ); - }} > {properties.map((item) => ( setSelectedItem(item)} + onPress={setSelectedItem} /> ))} - {/* -------- HORIZONTAL ZILLOW-STYLE POPUP -------- */} + {/* -------- LOADER -------- */} + {loading && ( + + + + )} + + {/* -------- ZILLOW STYLE POPUP -------- */} {selectedItem && ( { showsHorizontalScrollIndicator={false} renderItem={({ item }) => ( - + - ${formatPrice(item.price)} + + ₹{item.price_display} + - {item.beds} Beds • {item.baths} Baths + {item.bedrooms} Beds • {item.bathrooms} Baths + + + {item.address} @@ -190,6 +233,7 @@ const MapScreen: React.FC = () => { }; /* ---------------- STYLES ---------------- */ + const styles = StyleSheet.create({ container: { flex: 1 }, @@ -201,48 +245,54 @@ const styles = StyleSheet.create({ 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', + selectedPin: { + backgroundColor: '#F4C430', + borderColor: '#333', + }, + pinText: { + color: '#fff', + fontWeight: '600', + fontSize: 12, }, - 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, + elevation: 6, }, 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', + cardContent: { + padding: 12, + }, + cardPrice: { + fontSize: 18, + fontWeight: 'bold', + }, + cardDetails: { + marginVertical: 4, + color: '#555', + }, + cardAddress: { + fontSize: 13, + color: '#777', + }, + + loader: { + position: 'absolute', + top: '50%', + alignSelf: 'center', }, - cardBtnText: { color: '#fff', fontWeight: '600' }, }); export default MapScreen; diff --git a/src/screens/MapOld.tsx b/src/screens/MapOld.tsx new file mode 100644 index 0000000..d276ab9 --- /dev/null +++ b/src/screens/MapOld.tsx @@ -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 ( + { + e.stopPropagation(); + onPress(item); + }} + pinColor={selected ? 'gold' : '#8B0000'} + > + + {formatPrice(item.price)} + + + ); +}; + +/* ---------------- MAIN SCREEN ---------------- */ +const MapScreen: React.FC = () => { + const mapRef = useRef(null); + const [mapType, setMapType] = useState('standard'); + const [selectedItem, setSelectedItem] = useState(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 ( + + setMapType(r.latitudeDelta < 0.02 ? 'satellite' : 'standard')} + renderCluster={(cluster, onPress) => { + const { geometry, properties } = cluster; + return ( + + + {properties.point_count} + + + ); + }} + > + {properties.map((item) => ( + setSelectedItem(item)} + /> + ))} + + + {/* -------- HORIZONTAL ZILLOW-STYLE POPUP -------- */} + {selectedItem && ( + + item.id} + showsHorizontalScrollIndicator={false} + renderItem={({ item }) => ( + + + + ${formatPrice(item.price)} + + {item.beds} Beds • {item.baths} Baths + + + + )} + /> + + )} + + ); +}; + +/* ---------------- 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;