import React, {useCallback, useEffect, useState} from 'react'; import {ActivityIndicator, ScrollView, StyleSheet, Text, TextInput, View} from 'react-native'; import * as Location from 'expo-location'; import {Magnetometer} from 'expo-sensors'; import Screen from '../layout/Screen'; import {Button, ButtonTray} from '../UI/Button'; import PlayerMapView from '../gameplay/PlayerMapView'; import useGameHook from '../../hooks/useGameHook'; import usePlayerGame from '../../hooks/usePlayerGame'; import {getSession, setSessionGroup, setSessionUser} from '../../hooks/SessionStore'; const toHeading = ({x, y}) => { const angle = Math.atan2(y, x) * (180 / Math.PI); return angle < 0 ? angle + 360 : angle; }; const MapScreen = ({navigation}) => { // Initialisation ------------ const session = getSession(); const {getCaches, claimCache, joinPrivateGame, createPrivateGame, getLobby, getUser} = useGameHook(); // State ---------------------- const [inGame, setInGame] = useState(Boolean(session.currentGid)); const [isAdmin, setIsAdmin] = useState(session.isAcceptedAdmin); const [userLocation, setUserLocation] = useState(null); const [heading, setHeading] = useState(null); const [loading, setLoading] = useState(true); const [cacheRecords, setCacheRecords] = useState([]); const [error, setError] = useState(''); const [gameCode, setGameCode] = useState(''); const [groupInfo, setGroupInfo] = useState(null); const isPlayer = inGame && !isAdmin; const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame( isPlayer ? userLocation : null, isPlayer ? heading : null, isPlayer ? cacheRecords : [], ); // Handlers ------------------- // Load group info for TeamsEnabled check useEffect(() => { if (!session.currentGid) return; getLobby(session.currentGid).then(setGroupInfo); }, [inGame]); // Load caches for players only const loadCaches = useCallback(async () => { if (!session.currentGid || isAdmin) { setCacheRecords([]); return; } const rows = await getCaches(session.currentGid, session.currentSGid); setCacheRecords(rows || []); }, [inGame, isAdmin]); useEffect(() => { loadCaches(); }, [loadCaches]); // Location + compass tracking for players only useEffect(() => { if (!isPlayer) { setLoading(false); return; } let locationSub; let headingSub; const start = async () => { const {status} = await Location.requestForegroundPermissionsAsync(); if (status !== 'granted') { setError('Location permission denied'); setLoading(false); return; } const current = await Location.getCurrentPositionAsync({accuracy: Location.Accuracy.Balanced}); setUserLocation({latitude: current.coords.latitude, longitude: current.coords.longitude}); locationSub = await Location.watchPositionAsync( {accuracy: Location.Accuracy.Balanced, distanceInterval: 1, timeInterval: 1000}, (next) => setUserLocation({latitude: next.coords.latitude, longitude: next.coords.longitude}), ); Magnetometer.setUpdateInterval(500); headingSub = Magnetometer.addListener((data) => setHeading(toHeading(data))); setLoading(false); }; start(); return () => { if (locationSub) locationSub.remove(); if (headingSub) headingSub.remove(); }; }, [isPlayer]); const handleJoinGame = async () => { if (!gameCode.trim()) return; const result = await joinPrivateGame({JoinCode: gameCode.trim(), Uid: session.currentUid}); if (!result) return; setSessionGroup(result.Gid, result.SGid); setInGame(true); setIsAdmin(false); navigation.navigate('TeamScreen'); }; const handleCreateGame = async () => { const result = await createPrivateGame({ GroupName: 'My Game', CreatedByUid: session.currentUid, TeamsEnabled: false, MaxMemberSubgroups: 1, }); if (!result) return; const freshUser = await getUser(session.currentUid); if (freshUser) { setSessionUser(freshUser); setInGame(true); setIsAdmin(true); } }; const handleClaim = async (cacheId) => { if (!session.currentGid) return; await claimCache({ gid: session.currentGid, cacheId, uid: session.currentUid, tid: session.currentTid, }); setIsClaiming(false); await loadCaches(); }; // View ----------------------- // Not in a game if (!inGame) { return (