mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-08 03:00:20 +00:00
Update API base URL for local development; implement fetch timeout handling for improved request management
This commit is contained in:
1 parent
da7d5ea48d
commit
a326c784a5
4 files changed
+362
-38
No files matched your search
@@ -1,49 +1,70 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {ActivityIndicator, StyleSheet, Text, View} from 'react-native';
|
||||
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} from '../../hooks/SessionStore';
|
||||
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 = () => {
|
||||
const MapScreen = ({navigation}) => {
|
||||
// Initialisation ------------
|
||||
|
||||
const session = getSession();
|
||||
const {getCaches, claimCache} = useGameHook();
|
||||
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 subgroupFilter = session.currentSGid;
|
||||
const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame(userLocation, heading, cacheRecords);
|
||||
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) {
|
||||
if (!session.currentGid || isAdmin) {
|
||||
setCacheRecords([]);
|
||||
return;
|
||||
}
|
||||
const rows = await getCaches(session.currentGid, subgroupFilter);
|
||||
const rows = await getCaches(session.currentGid, session.currentSGid);
|
||||
setCacheRecords(rows || []);
|
||||
}, [session.currentGid, subgroupFilter]);
|
||||
}, [inGame, isAdmin]);
|
||||
|
||||
useEffect(() => { loadCaches(); }, [loadCaches]);
|
||||
|
||||
// Location + compass tracking for players only
|
||||
useEffect(() => {
|
||||
if (!isPlayer) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
let locationSub;
|
||||
let headingSub;
|
||||
|
||||
@@ -59,23 +80,46 @@ const MapScreen = () => {
|
||||
|
||||
locationSub = await Location.watchPositionAsync(
|
||||
{accuracy: Location.Accuracy.Balanced, distanceInterval: 1, timeInterval: 1000},
|
||||
(next) => {
|
||||
setUserLocation({latitude: next.coords.latitude, longitude: next.coords.longitude});
|
||||
},
|
||||
(next) => setUserLocation({latitude: next.coords.latitude, longitude: next.coords.longitude}),
|
||||
);
|
||||
|
||||
Magnetometer.setUpdateInterval(500);
|
||||
headingSub = Magnetometer.addListener((data) => { setHeading(toHeading(data)); });
|
||||
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;
|
||||
@@ -91,15 +135,39 @@ const MapScreen = () => {
|
||||
|
||||
// View -----------------------
|
||||
|
||||
if (!session.currentGid) {
|
||||
// Not in a game
|
||||
if (!inGame) {
|
||||
return (
|
||||
<Screen style={styles.center}>
|
||||
<Text style={styles.body}>Join or create a game to use map features.</Text>
|
||||
<View style={styles.row}>
|
||||
<TextInput
|
||||
style={styles.codeInput}
|
||||
placeholder="Enter Game Code"
|
||||
value={gameCode}
|
||||
onChangeText={setGameCode}
|
||||
autoCapitalize="characters"
|
||||
/>
|
||||
<Button
|
||||
label="Join"
|
||||
onClick={handleJoinGame}
|
||||
styleButton={styles.joinButton}
|
||||
styleLabel={styles.joinLabel}
|
||||
/>
|
||||
</View>
|
||||
<ButtonTray>
|
||||
<Button
|
||||
label="Create a Game"
|
||||
onClick={handleCreateGame}
|
||||
styleButton={styles.createButton}
|
||||
styleLabel={styles.createLabel}
|
||||
/>
|
||||
</ButtonTray>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
|
||||
if (session.isAcceptedAdmin) {
|
||||
// Admin view
|
||||
if (isAdmin) {
|
||||
return (
|
||||
<Screen style={styles.center}>
|
||||
<Text style={styles.body}>Admins manage caches from Manage Game.</Text>
|
||||
@@ -107,6 +175,7 @@ const MapScreen = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Loading
|
||||
if (loading) {
|
||||
return (
|
||||
<Screen style={styles.center}>
|
||||
@@ -115,6 +184,7 @@ const MapScreen = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Error
|
||||
if (error) {
|
||||
return (
|
||||
<Screen style={styles.center}>
|
||||
@@ -123,8 +193,14 @@ const MapScreen = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Player in game
|
||||
const teamsWarning = groupInfo?.TeamsEnabled && !session.currentTid;
|
||||
|
||||
return (
|
||||
<Screen style={styles.container}>
|
||||
<Screen style={styles.containerMap}>
|
||||
{teamsWarning && (
|
||||
<Text style={styles.warning}>You are not in a team. Teams are required for this game!</Text>
|
||||
)}
|
||||
<View style={styles.mapWrap}>
|
||||
<PlayerMapView
|
||||
userLocation={userLocation}
|
||||
@@ -133,16 +209,73 @@ const MapScreen = () => {
|
||||
onClaimSuccess={handleClaim}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.cacheSection}>
|
||||
<ScrollView>
|
||||
{cacheRecords.map((cache) => (
|
||||
<View key={cache.id} style={styles.cacheCard}>
|
||||
<Text style={styles.cacheClue}>{cache.clue}</Text>
|
||||
<Text style={[styles.cacheStatus, cache.ClaimedByUid && styles.cacheStatusClaimed]}>
|
||||
{cache.ClaimedByUid ? 'Claimed' : 'Available'}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
{cacheRecords.length === 0 && (
|
||||
<Text style={styles.emptyText}>No caches available yet.</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Screen>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {padding: 0},
|
||||
mapWrap: {flex: 1},
|
||||
center: {justifyContent: 'center', alignItems: 'center'},
|
||||
containerMap: {padding: 0},
|
||||
mapWrap: {flex: 2},
|
||||
body: {color: '#4b5563', fontSize: 15},
|
||||
error: {color: '#dc2626', fontSize: 15},
|
||||
row: {flexDirection: 'row', gap: 10, marginBottom: 15, width: '100%', paddingHorizontal: 20},
|
||||
codeInput: {
|
||||
flex: 1,
|
||||
borderWidth: 1,
|
||||
borderColor: '#d1d5db',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
fontSize: 16,
|
||||
},
|
||||
joinButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', flex: 0, paddingHorizontal: 20},
|
||||
joinLabel: {color: '#ffffff', fontWeight: '600'},
|
||||
createButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'},
|
||||
createLabel: {color: '#ffffff', fontWeight: '600'},
|
||||
warning: {
|
||||
color: '#dc2626',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
paddingVertical: 8,
|
||||
fontSize: 14,
|
||||
},
|
||||
cacheSection: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f3f4f6',
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 10,
|
||||
},
|
||||
cacheCard: {
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
marginBottom: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
borderWidth: 1,
|
||||
borderColor: '#e5e7eb',
|
||||
},
|
||||
cacheClue: {fontSize: 15, fontWeight: '600', color: '#1f2937'},
|
||||
cacheStatus: {fontSize: 13, color: '#16a34a', fontWeight: '600'},
|
||||
cacheStatusClaimed: {color: '#9ca3af'},
|
||||
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 20, fontSize: 14},
|
||||
});
|
||||
|
||||
export default MapScreen;
|
||||
Reference in new issue
Block a user