181 lines
5.8 KiB
TypeScript

import React, { useState, useEffect, useRef, useCallback } from 'react';
import {
View,
Text,
TouchableOpacity,
ActivityIndicator,
InteractionManager,
} from 'react-native';
import MapView, { Marker, Region } from 'react-native-maps';
import { useNavigation } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import { getStyles } from './setLocationScreen.styles';
import { PrimaryButton } from '@components';
import { useAppTheme } from '@theme';
import { useAppDispatch } from '@store';
import { setLocationData } from './reducer';
import { OnBoardingParamList } from '@navigation/navigationTypes';
import { RouteNames } from '@utils/constants';
import {
DEFAULT_LOCATION,
getCurrentLocationWithAddress,
reverseGeocode,
showPermissionDeniedAlert,
LatLng,
} from '@services/locationService';
type NavProp = StackNavigationProp<OnBoardingParamList, RouteNames.SetLocation>;
const DELTA = { latitudeDelta: 0.006, longitudeDelta: 0.006 };
export const SetLocationScreen: React.FC = () => {
const { colors } = useAppTheme();
const styles = getStyles(colors);
const navigation = useNavigation<NavProp>();
const dispatch = useAppDispatch();
const mapRef = useRef<MapView>(null);
// Guard: never call setState after the component has unmounted
const isMounted = useRef(true);
const [coords, setCoords] = useState<LatLng>(DEFAULT_LOCATION);
const [address, setAddress] = useState('Fetching your location…');
const [loading, setLoading] = useState(true);
// ------------------------------------------------------------------
// Fetch current location on mount
// ------------------------------------------------------------------
const fetchCurrentLocation = useCallback(async () => {
if (!isMounted.current) return;
setLoading(true);
try {
console.log('--------------');
const result = await getCurrentLocationWithAddress();
console.log('result', result);
if (!isMounted.current) return;
setCoords(result.coords);
setAddress(result.address);
// Animate the map to the user's location
mapRef.current?.animateToRegion({ ...result.coords, ...DELTA }, 600);
} catch (err: unknown) {
const message = err instanceof Error ? err.message : '';
if (message === 'Location permission denied') {
// Show the alert here — safe, outside the Promise chain
showPermissionDeniedAlert();
}
console.log('--------error------', err);
if (!isMounted.current) return;
setAddress('Could not determine location');
} finally {
if (isMounted.current) setLoading(false);
}
}, []);
useEffect(() => {
// Wait for the screen-transition animation to fully finish before
// calling PermissionsAndroid.request(). Calling it while the Activity
// is still transitioning causes a native crash on Android.
const task = InteractionManager.runAfterInteractions(() => {
fetchCurrentLocation();
});
return () => {
task.cancel();
isMounted.current = false;
};
}, [fetchCurrentLocation]);
// ------------------------------------------------------------------
// When user drags the map, update the marker + address
// ------------------------------------------------------------------
const handleRegionChangeComplete = useCallback(async (region: Region) => {
const newCoords: LatLng = {
latitude: region.latitude,
longitude: region.longitude,
};
setCoords(newCoords);
setAddress('Fetching address…');
const newAddress = await reverseGeocode(newCoords);
setAddress(newAddress);
}, []);
return (
<View style={styles.container}>
{/* ---------- Map ---------- */}
<MapView
ref={mapRef}
style={styles.map}
initialRegion={{ ...DEFAULT_LOCATION, ...DELTA }}
onRegionChangeComplete={handleRegionChangeComplete}
>
<Marker
coordinate={coords}
title="Your Location"
description={address}
/>
</MapView>
{/* ---------- "Locate me" floating button ---------- */}
<TouchableOpacity
style={styles.locateButton}
activeOpacity={0.8}
onPress={fetchCurrentLocation}
>
<Text style={styles.locateIcon}>📍</Text>
</TouchableOpacity>
{/* ---------- Bottom sheet ---------- */}
<View style={styles.bottomSheet}>
<View style={styles.sheetHandle} />
<View style={styles.card}>
<View style={styles.cardHeader}>
<Text style={styles.cardIcon}>📍</Text>
<Text style={styles.cardTitle}>My Location</Text>
</View>
{loading ? (
<ActivityIndicator
size="small"
color={colors.primary}
style={{ marginVertical: 12 }}
/>
) : (
<Text style={styles.cardAddress} numberOfLines={2}>
{address}
</Text>
)}
<TouchableOpacity style={styles.changeLink} activeOpacity={0.7}>
<Text style={styles.changeText}>Change</Text>
</TouchableOpacity>
</View>
<PrimaryButton
title="Confirm Location"
onPress={() => {
dispatch(
setLocationData({
latitude: coords.latitude,
longitude: coords.longitude,
mapAddress: address,
}),
);
navigation.navigate(RouteNames.CompleteProfile);
}}
style={styles.confirmButton}
/>
<TouchableOpacity
style={styles.manualButton}
onPress={() => navigation.navigate(RouteNames.CompleteProfile)}
activeOpacity={0.7}
>
<Text style={styles.manualButtonText}>Enter address manually</Text>
</TouchableOpacity>
</View>
</View>
);
};