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:
bobbert committed 2026-04-09 09:09:08 +01:00
1 parent e60c6181f1
commit fc378ae016
4 files changed
+27 -31

No files matched your search

+6 -12
View File
@@ -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,11 +26,12 @@ 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}}
@@ -40,13 +41,6 @@ const PlayerMapView = ({userLocation, visibleCaches, heading, claimDistance}) =>
<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 && (
+2 -9
View File
@@ -107,10 +107,10 @@ 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}}
@@ -120,13 +120,6 @@ const ExpandedMapScreen = ({route}) => {
<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 && (
+5 -4
View File
@@ -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>
+14 -6
View File
@@ -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};