mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-07 19:00:20 +00:00
Refactor map components: streamline player cache marker rendering and update claim logic to use selected cache ID for triggering countdown.
This commit is contained in:
1 parent
e60c6181f1
commit
fc378ae016
4 files changed
+47
-51
No files matched your search
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Platform, StyleSheet, View} from 'react-native';
|
import {Platform, StyleSheet, View} from 'react-native';
|
||||||
import MapView, {Circle, Marker, Polygon} from 'react-native-maps';
|
import MapView, {Marker, Polygon} from 'react-native-maps';
|
||||||
import {getFovCone} from '../../utils/geoMath';
|
import {getFovCone} from '../../utils/geoMath';
|
||||||
|
|
||||||
const PlayerMapView = ({userLocation, visibleCaches, heading, claimDistance}) => {
|
const PlayerMapView = ({userLocation, visibleCaches, heading}) => {
|
||||||
// Initialisation -------------
|
// Initialisation -------------
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
@@ -26,27 +26,21 @@ const PlayerMapView = ({userLocation, visibleCaches, heading, claimDistance}) =>
|
|||||||
pitchEnabled={false}
|
pitchEnabled={false}
|
||||||
showsUserLocation
|
showsUserLocation
|
||||||
>
|
>
|
||||||
{/* Cache markers — uses a custom View instead of pinColor so the
|
{/* Cache pin markers — appear temporarily when the cache is in the FOV
|
||||||
marker renders identically on Apple Maps (iOS) and Google Maps */}
|
cone AND within claim distance. Uses a custom View so the pin
|
||||||
|
renders identically on iOS (Google Maps) and Android */}
|
||||||
{(visibleCaches || []).map((cache) => (
|
{(visibleCaches || []).map((cache) => (
|
||||||
<React.Fragment key={cache.id}>
|
<Marker
|
||||||
<Marker
|
key={cache.id}
|
||||||
coordinate={cache.coordinates}
|
coordinate={cache.coordinates}
|
||||||
title={cache.clue}
|
title={cache.clue}
|
||||||
anchor={{x: 0.5, y: 0.5}}
|
anchor={{x: 0.5, y: 0.5}}
|
||||||
tracksViewChanges={Platform.OS === 'ios'}
|
tracksViewChanges={Platform.OS === 'ios'}
|
||||||
>
|
>
|
||||||
<View style={styles.markerOuter}>
|
<View style={styles.markerOuter}>
|
||||||
<View style={styles.markerInner} />
|
<View style={styles.markerInner} />
|
||||||
</View>
|
</View>
|
||||||
</Marker>
|
</Marker>
|
||||||
<Circle
|
|
||||||
center={cache.coordinates}
|
|
||||||
radius={claimDistance}
|
|
||||||
fillColor="rgba(250, 204, 21, 0.20)"
|
|
||||||
strokeColor="rgba(250, 204, 21, 0.90)"
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
))}
|
||||||
{/* FOV heading cone */}
|
{/* FOV heading cone */}
|
||||||
{coneCoords && (
|
{coneCoords && (
|
||||||
|
|||||||
@@ -107,26 +107,19 @@ const ExpandedMapScreen = ({route}) => {
|
|||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
{/* Players — custom View markers for Apple Maps compatibility */}
|
{/* Players — pin markers appear when cache is in FOV cone + within range */}
|
||||||
{!isAdmin && visiblePlayerCaches.map((cache) => (
|
{!isAdmin && visiblePlayerCaches.map((cache) => (
|
||||||
<React.Fragment key={cache.id}>
|
<Marker
|
||||||
<Marker
|
key={cache.id}
|
||||||
coordinate={cache.coordinates}
|
coordinate={cache.coordinates}
|
||||||
title={cache.clue}
|
title={cache.clue}
|
||||||
anchor={{x: 0.5, y: 0.5}}
|
anchor={{x: 0.5, y: 0.5}}
|
||||||
tracksViewChanges={Platform.OS === 'ios'}
|
tracksViewChanges={Platform.OS === 'ios'}
|
||||||
>
|
>
|
||||||
<View style={styles.cacheMarkerOuter}>
|
<View style={styles.cacheMarkerOuter}>
|
||||||
<View style={styles.cacheMarkerInner} />
|
<View style={styles.cacheMarkerInner} />
|
||||||
</View>
|
</View>
|
||||||
</Marker>
|
</Marker>
|
||||||
<Circle
|
|
||||||
center={cache.coordinates}
|
|
||||||
radius={claimDistance}
|
|
||||||
fillColor="rgba(250, 204, 21, 0.20)"
|
|
||||||
strokeColor="rgba(250, 204, 21, 0.90)"
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
))}
|
||||||
{/* Heading FOV cone */}
|
{/* Heading FOV cone */}
|
||||||
{coneCoords && (
|
{coneCoords && (
|
||||||
|
|||||||
@@ -73,12 +73,14 @@ const MapScreen = ({navigation}) => {
|
|||||||
});
|
});
|
||||||
}, [cacheRecords, isPlayer, session.currentTid, session.currentUid, EMPTY_CACHES]);
|
}, [cacheRecords, isPlayer, session.currentTid, session.currentUid, EMPTY_CACHES]);
|
||||||
|
|
||||||
// When requiresTeam is true pass null/empty so usePlayerGame is inert
|
// When requiresTeam is true pass null/empty so usePlayerGame is inert.
|
||||||
|
// selectedCacheId is the 5th param — only that cache can trigger the claim countdown.
|
||||||
const {visibleCaches, isClaiming, setIsClaiming} = usePlayerGame(
|
const {visibleCaches, isClaiming, setIsClaiming} = usePlayerGame(
|
||||||
(isPlayer && !requiresTeam) ? userLocation : null,
|
(isPlayer && !requiresTeam) ? userLocation : null,
|
||||||
(isPlayer && !requiresTeam) ? heading : null,
|
(isPlayer && !requiresTeam) ? heading : null,
|
||||||
requiresTeam ? EMPTY_CACHES : activeCachesForPlayer,
|
requiresTeam ? EMPTY_CACHES : activeCachesForPlayer,
|
||||||
claimDistance,
|
claimDistance,
|
||||||
|
selectedCacheId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapRegion = userLocation
|
const mapRegion = userLocation
|
||||||
@@ -521,8 +523,8 @@ const MapScreen = ({navigation}) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Player in game — use the first visible cache for the claim timer
|
// Player in game — the claim target is the selected cache (only it can trigger the countdown)
|
||||||
const claimTarget = visibleCaches.length > 0 ? visibleCaches[0] : null;
|
const claimTarget = visibleCaches.find((c) => c.id === selectedCacheId) || null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.containerMap}>
|
<Screen style={styles.containerMap}>
|
||||||
@@ -534,7 +536,6 @@ const MapScreen = ({navigation}) => {
|
|||||||
userLocation={userLocation}
|
userLocation={userLocation}
|
||||||
visibleCaches={visibleCaches}
|
visibleCaches={visibleCaches}
|
||||||
heading={heading}
|
heading={heading}
|
||||||
claimDistance={claimDistance}
|
|
||||||
/>
|
/>
|
||||||
<Pressable style={styles.expandButton} onPress={handleExpandMap}>
|
<Pressable style={styles.expandButton} onPress={handleExpandMap}>
|
||||||
<Text style={styles.expandIcon}>⛶</Text>
|
<Text style={styles.expandIcon}>⛶</Text>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {useEffect, useRef, useState} from 'react';
|
import {useEffect, useRef, useState} from 'react';
|
||||||
import {isInClaimCone} from '../utils/geoMath';
|
import {isInClaimCone} from '../utils/geoMath';
|
||||||
|
|
||||||
const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistance) => {
|
const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistance, selectedCacheId) => {
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
const [visibleCaches, setVisibleCaches] = useState([]);
|
const [visibleCaches, setVisibleCaches] = useState([]);
|
||||||
@@ -10,19 +10,16 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc
|
|||||||
|
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
|
|
||||||
|
// Determine which caches fall inside the FOV claim cone
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Both location AND heading are required — the player must physically
|
|
||||||
// point their device towards a cache to trigger the claim countdown
|
|
||||||
if (!playerLocation || playerHeading === null || playerHeading === undefined) {
|
if (!playerLocation || playerHeading === null || playerHeading === undefined) {
|
||||||
if (prevIdsRef.current !== '') {
|
if (prevIdsRef.current !== '') {
|
||||||
prevIdsRef.current = '';
|
prevIdsRef.current = '';
|
||||||
setVisibleCaches([]);
|
setVisibleCaches([]);
|
||||||
setIsClaiming(false);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all caches that fall inside the claim cone
|
|
||||||
const inCone = (activeCaches || []).filter((cache) =>
|
const inCone = (activeCaches || []).filter((cache) =>
|
||||||
isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance),
|
isInClaimCone(playerHeading, playerLocation, cache.coordinates, claimDistance),
|
||||||
);
|
);
|
||||||
@@ -32,10 +29,21 @@ const usePlayerGame = (playerLocation, playerHeading, activeCaches, claimDistanc
|
|||||||
if (newIds !== prevIdsRef.current) {
|
if (newIds !== prevIdsRef.current) {
|
||||||
prevIdsRef.current = newIds;
|
prevIdsRef.current = newIds;
|
||||||
setVisibleCaches(inCone);
|
setVisibleCaches(inCone);
|
||||||
setIsClaiming(inCone.length > 0);
|
|
||||||
}
|
}
|
||||||
}, [playerLocation, playerHeading, activeCaches, claimDistance]);
|
}, [playerLocation, playerHeading, activeCaches, claimDistance]);
|
||||||
|
|
||||||
|
// Claiming requires ALL four criteria:
|
||||||
|
// 1. Cache not already claimed by player's team (handled by activeCaches filter)
|
||||||
|
// 2. Cache within claim distance (handled by isInClaimCone)
|
||||||
|
// 3. Player looking at the cache (in FOV cone) (handled by isInClaimCone)
|
||||||
|
// 4. Cache is the one currently selected in list (checked here)
|
||||||
|
useEffect(() => {
|
||||||
|
const canClaim = Boolean(
|
||||||
|
selectedCacheId && visibleCaches.some((c) => c.id === selectedCacheId),
|
||||||
|
);
|
||||||
|
setIsClaiming(canClaim);
|
||||||
|
}, [visibleCaches, selectedCacheId]);
|
||||||
|
|
||||||
// Return ---------------------
|
// Return ---------------------
|
||||||
|
|
||||||
return {visibleCaches, isClaiming, setIsClaiming};
|
return {visibleCaches, isClaiming, setIsClaiming};
|
||||||
|
|||||||
Reference in new issue
Block a user