diff --git a/src/components/gameplay/ClaimTimerView.js b/src/components/gameplay/ClaimTimerView.js
index 45b8c0f..f390a46 100644
--- a/src/components/gameplay/ClaimTimerView.js
+++ b/src/components/gameplay/ClaimTimerView.js
@@ -2,36 +2,62 @@ import React, {useEffect, useRef, useState} from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {CACHE_CLAIM_TIMER} from '../../utils/geoMath';
-const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming, showClaimedPopup }) => {
- const [timeLeft, setTimeLeft] = useState(CACHE_CLAIM_TIMER);
- // Use a ref for the callback so it does not appear in the dependency array
+const ClaimTimerView = ({
+ cache,
+ onClaimSuccess,
+ isClaiming,
+ showClaimedPopup,
+ claimDurationSeconds = CACHE_CLAIM_TIMER,
+ claimedPopupMessage = 'Cache Claimed!',
+}) => {
+ const [timeLeft, setTimeLeft] = useState(claimDurationSeconds);
const onClaimRef = useRef(onClaimSuccess);
+ const hasClaimedRef = useRef(false);
onClaimRef.current = onClaimSuccess;
+ useEffect(() => {
+ setTimeLeft(claimDurationSeconds);
+ hasClaimedRef.current = false;
+ }, [cache?.id, claimDurationSeconds]);
useEffect(() => {
let timer;
- if (isClaiming && cache && timeLeft > 0) {
- timer = setTimeout(() => setTimeLeft((prev) => prev - 1), 1000);
- } else if (isClaiming && timeLeft === 0 && cache) {
- onClaimRef.current(cache.id);
- } else if (!isClaiming) {
- setTimeLeft(CACHE_CLAIM_TIMER);
+
+ if (!isClaiming || !cache) {
+ if (!isClaiming) {
+ setTimeLeft(claimDurationSeconds);
+ hasClaimedRef.current = false;
+ }
+ return () => clearTimeout(timer);
}
+
+ if (claimDurationSeconds <= 0) {
+ if (!hasClaimedRef.current) {
+ hasClaimedRef.current = true;
+ onClaimRef.current(cache.id);
+ }
+ return () => clearTimeout(timer);
+ }
+
+ if (timeLeft > 0) {
+ timer = setTimeout(() => setTimeLeft((prev) => prev - 1), 1000);
+ } else if (!hasClaimedRef.current) {
+ hasClaimedRef.current = true;
+ onClaimRef.current(cache.id);
+ }
+
return () => clearTimeout(timer);
- }, [isClaiming, timeLeft, cache]);
+ }, [claimDurationSeconds, cache, isClaiming, timeLeft]);
-
- // Show "Cache Claimed!" popup after a successful claim
if (showClaimedPopup) {
return (
- Cache Claimed!
+ {claimedPopupMessage}
);
}
- if (!isClaiming || !cache) {
+ if (!isClaiming || !cache || claimDurationSeconds <= 0) {
return null;
}
diff --git a/src/components/screens/global/GlobalCacheViewScreen.js b/src/components/screens/global/GlobalCacheViewScreen.js
index bd8d58a..9834a4c 100644
--- a/src/components/screens/global/GlobalCacheViewScreen.js
+++ b/src/components/screens/global/GlobalCacheViewScreen.js
@@ -1,10 +1,10 @@
-import { useEffect } from "react";
-import { StyleSheet, Text, View } from "react-native";
+import {useEffect} from "react";
+import {StyleSheet, Text, View} from "react-native";
import Screen from "../../layout/Screen";
-import { Button, ButtonTray } from "../../UI/Button";
+import {Button, ButtonTray} from "../../UI/Button";
import Card from "../../UI/Card";
-import { getSession } from "../../../hooks/SessionStore";
-import { GAME_MODE } from "../../../utils/gameConstants";
+import {getSession} from "../../../hooks/SessionStore";
+import {GAME_MODE} from "../../../utils/gameConstants";
const GlobalCacheViewScreen = ({ navigation, route }) => {
// Initialisations ---------------------
@@ -23,6 +23,8 @@ const GlobalCacheViewScreen = ({ navigation, route }) => {
const handleBackToMap = () => {
navigation.navigate("GlobalMapScreen", {
eventId: cache.CacheEventID ?? session.currentGlobalEventId,
+ event: route.params?.event ?? null,
+ cache,
selectedCacheId: cache.CacheID,
});
};
diff --git a/src/components/screens/global/GlobalEventsScreen.js b/src/components/screens/global/GlobalEventsScreen.js
index 6ae1e3d..3206505 100644
--- a/src/components/screens/global/GlobalEventsScreen.js
+++ b/src/components/screens/global/GlobalEventsScreen.js
@@ -1,11 +1,11 @@
-import { useCallback, useEffect, useRef, useState } from "react";
-import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
+import {useCallback, useEffect, useRef, useState} from "react";
+import {ActivityIndicator, StyleSheet, Text, View} from "react-native";
import Screen from "../../layout/Screen";
-import { Button, ButtonTray } from "../../UI/Button";
+import {Button, ButtonTray} from "../../UI/Button";
import EventList from "../../../entity/event/EventList";
import useGlobalHook from "../../../hooks/useGlobalHook";
-import { getSession, setGlobalSession } from "../../../hooks/SessionStore";
-import { GAME_MODE } from "../../../utils/gameConstants";
+import {getSession, setGlobalSession} from "../../../hooks/SessionStore";
+import {GAME_MODE} from "../../../utils/gameConstants";
const GlobalEventsScreen = ({ navigation }) => {
// Initialisations ---------------------
@@ -93,7 +93,10 @@ const GlobalEventsScreen = ({ navigation }) => {
const existingPlayerId = pickPlayerId(existing);
if (existingPlayerId !== null) {
setGlobalSession(event.EventID, existingPlayerId);
- navigation.navigate("GlobalMapScreen", { eventId: event.EventID });
+ navigation.navigate("GlobalMapScreen", {
+ eventId: event.EventID,
+ event,
+ });
setJoining(null);
return;
}
@@ -107,7 +110,10 @@ const GlobalEventsScreen = ({ navigation }) => {
const joinedPlayerId = pickPlayerId(player);
if (joinedPlayerId !== null) {
setGlobalSession(event.EventID, joinedPlayerId);
- navigation.navigate("GlobalMapScreen", { eventId: event.EventID });
+ navigation.navigate("GlobalMapScreen", {
+ eventId: event.EventID,
+ event,
+ });
setJoining(null);
return;
}
@@ -123,7 +129,10 @@ const GlobalEventsScreen = ({ navigation }) => {
const recoveredPlayerId = pickPlayerId(recovered);
if (recoveredPlayerId !== null) {
setGlobalSession(event.EventID, recoveredPlayerId);
- navigation.navigate("GlobalMapScreen", { eventId: event.EventID });
+ navigation.navigate("GlobalMapScreen", {
+ eventId: event.EventID,
+ event,
+ });
} else {
setError("Failed to join event. Please try again.");
}
diff --git a/src/components/screens/global/GlobalMapScreen.js b/src/components/screens/global/GlobalMapScreen.js
index 5f6e4d1..b796c3c 100644
--- a/src/components/screens/global/GlobalMapScreen.js
+++ b/src/components/screens/global/GlobalMapScreen.js
@@ -1,16 +1,16 @@
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
-import MapView, { Circle, Marker, Polygon } from "react-native-maps";
+import {useCallback, useEffect, useMemo, useRef, useState} from "react";
+import {ActivityIndicator, StyleSheet, Text, View} from "react-native";
+import MapView, {Circle, Marker, Polygon} from "react-native-maps";
import * as Location from "expo-location";
import Screen from "../../layout/Screen";
-import { Button, ButtonTray } from "../../UI/Button";
+import {Button, ButtonTray} from "../../UI/Button";
import ClaimTimerView from "../../gameplay/ClaimTimerView";
import CacheList from "../../../entity/cache/CacheList";
import useGlobalHook from "../../../hooks/useGlobalHook";
import usePlayerGame from "../../../hooks/usePlayerGame";
-import { getSession } from "../../../hooks/SessionStore";
-import { getFovCone } from "../../../utils/geoMath";
-import { GAME_MODE } from "../../../utils/gameConstants";
+import {getSession} from "../../../hooks/SessionStore";
+import {getFovCone} from "../../../utils/geoMath";
+import {GAME_MODE} from "../../../utils/gameConstants";
const DEFAULT_REGION = {
latitude: 51.5074,
@@ -25,7 +25,9 @@ const GlobalMapScreen = ({ navigation, route }) => {
// Initialisations ---------------------
const session = getSession();
- const eventId = route?.params?.eventId ?? session.currentGlobalEventId;
+ const routeEvent = route?.params?.event ?? null;
+ const eventId =
+ route?.params?.eventId ?? routeEvent?.EventID ?? session.currentGlobalEventId;
const { getCachesByEvent, getFindsByPlayer, logFind } = useGlobalHook();
const globalApiRef = useRef({
getCachesByEvent,
@@ -47,8 +49,11 @@ const GlobalMapScreen = ({ navigation, route }) => {
const [isLoading, setIsLoading] = useState(true);
const [activeTab, setActiveTab] = useState("map");
const [error, setError] = useState("");
- const [selectedCacheId, setSelectedCacheId] = useState(null);
+ const [selectedCacheId, setSelectedCacheId] = useState(
+ route?.params?.selectedCacheId ?? route?.params?.cache?.CacheID ?? null,
+ );
const [claimedPopupVisible, setClaimedPopupVisible] = useState(false);
+ const [claimedPopupMessage, setClaimedPopupMessage] = useState("Cache Claimed!");
const claimableCaches = useMemo(
() =>
@@ -114,6 +119,10 @@ const GlobalMapScreen = ({ navigation, route }) => {
return;
}
+ const claimedCache = (caches || []).find(
+ (cache) => String(cache.CacheID) === String(cacheId),
+ );
+
const result = await globalApiRef.current.logFind({
FindPlayerID: session.currentGlobalPlayerId,
FindCacheID: cacheId,
@@ -123,12 +132,19 @@ const GlobalMapScreen = ({ navigation, route }) => {
setIsClaiming(false);
if (result) {
+ const points = Number(claimedCache?.CachePoints ?? 0);
+ const cacheName = claimedCache?.CacheName || "Cache";
+ setFoundCacheIds((prev) =>
+ prev.includes(cacheId) ? prev : [...prev, cacheId],
+ );
+ setClaimedPopupMessage(
+ `${cacheName} claimed! You received ${points} points.`,
+ );
setClaimedPopupVisible(true);
setTimeout(() => setClaimedPopupVisible(false), 3000);
- await loadData({ forceRefresh: true });
}
},
- [foundCacheIds, loadData, session.currentGlobalPlayerId, setIsClaiming],
+ [caches, foundCacheIds, session.currentGlobalPlayerId, setIsClaiming],
);
const handleCacheSelect = (cache) => {
@@ -143,6 +159,8 @@ const GlobalMapScreen = ({ navigation, route }) => {
if (!selected) return;
navigation.navigate("GlobalCacheViewScreen", {
cache: selected,
+ event: routeEvent,
+ eventId,
isFound: foundCacheIds.includes(selected.CacheID),
});
};
@@ -197,14 +215,15 @@ const GlobalMapScreen = ({ navigation, route }) => {
}, [route?.params?.selectedCacheId]);
useEffect(() => {
- if (
- selectedCacheId &&
- claimableCaches.some((cache) => cache.id === selectedCacheId)
- ) {
- return;
+ if (route?.params?.cache?.CacheID) {
+ setSelectedCacheId(route.params.cache.CacheID);
}
- setSelectedCacheId(claimableCaches[0]?.id || null);
- }, [claimableCaches, selectedCacheId]);
+ }, [route?.params?.cache?.CacheID]);
+
+ useEffect(() => {
+ if (selectedCacheId) return;
+ setSelectedCacheId(route?.params?.cache?.CacheID ?? claimableCaches[0]?.id ?? null);
+ }, [claimableCaches, route?.params?.cache?.CacheID, selectedCacheId]);
useEffect(() => {
let locationSub;
@@ -318,6 +337,8 @@ const GlobalMapScreen = ({ navigation, route }) => {
onCalloutPress={() =>
navigation.navigate("GlobalCacheViewScreen", {
cache,
+ event: routeEvent,
+ eventId,
isFound: found,
})
}
@@ -356,6 +377,8 @@ const GlobalMapScreen = ({ navigation, route }) => {
isClaiming={isClaiming}
onClaimSuccess={handleClaim}
showClaimedPopup={claimedPopupVisible}
+ claimDurationSeconds={0}
+ claimedPopupMessage={claimedPopupMessage}
/>