mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-07 19:00:20 +00:00
Add comments for code structure clarity; enhance readability across multiple components
This commit is contained in:
1 parent
d67522a71d
commit
9b9f13eb6a
29 files changed
+1020
-846
No files matched your search
@@ -17,65 +17,56 @@ import ManageSubgroupsScreen from "./src/components/screens/ManageSubgroupsScree
|
|||||||
const Stack = createNativeStackNavigator();
|
const Stack = createNativeStackNavigator();
|
||||||
|
|
||||||
const AuthStack = () => (
|
const AuthStack = () => (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{headerShown: false}}
|
||||||
headerShown: false,
|
>
|
||||||
}}
|
<Stack.Screen name="LoginScreen" component={LoginScreen}/>
|
||||||
>
|
<Stack.Screen name="RegisterScreen" component={RegisterScreen}/>
|
||||||
<Stack.Screen name="LoginScreen" component={LoginScreen} />
|
</Stack.Navigator>
|
||||||
<Stack.Screen name="RegisterScreen" component={RegisterScreen} />
|
|
||||||
</Stack.Navigator>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const GameStack = () => (
|
const GameStack = () => (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{headerShown: false}}
|
||||||
headerShown: false,
|
>
|
||||||
}}
|
<Stack.Screen name="GameTypeScreen" component={GameTypeScreen}/>
|
||||||
>
|
<Stack.Screen name="JoinPrivateGameScreen" component={JoinPrivateGameScreen}/>
|
||||||
<Stack.Screen name="GameTypeScreen" component={GameTypeScreen} />
|
<Stack.Screen name="ManagePrivateGameScreen" component={ManagePrivateGameScreen}/>
|
||||||
<Stack.Screen name="JoinPrivateGameScreen" component={JoinPrivateGameScreen} />
|
<Stack.Screen name="CreateGameScreen" component={CreateGameScreen}/>
|
||||||
<Stack.Screen name="ManagePrivateGameScreen" component={ManagePrivateGameScreen} />
|
<Stack.Screen name="ManageGameScreen" component={ManageGameScreen}/>
|
||||||
<Stack.Screen name="CreateGameScreen" component={CreateGameScreen} />
|
<Stack.Screen name="GameLobbyScreen" component={GameLobbyScreen}/>
|
||||||
<Stack.Screen name="ManageGameScreen" component={ManageGameScreen} />
|
<Stack.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
|
||||||
<Stack.Screen name="GameLobbyScreen" component={GameLobbyScreen} />
|
<Stack.Screen name="TeamScreen" component={TeamScreen}/>
|
||||||
<Stack.Screen name="LeaderboardScreen" component={LeaderboardScreen} />
|
<Stack.Screen name="MapScreen" component={MapScreen}/>
|
||||||
<Stack.Screen name="TeamScreen" component={TeamScreen} />
|
</Stack.Navigator>
|
||||||
<Stack.Screen name="MapScreen" component={MapScreen} />
|
|
||||||
</Stack.Navigator>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const AdminStack = () => (
|
const AdminStack = () => (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{headerShown: false}}
|
||||||
headerShown: false,
|
>
|
||||||
}}
|
<Stack.Screen name="AdminHubScreen" component={AdminHubScreen}/>
|
||||||
>
|
<Stack.Screen name="ManageSubgroupsScreen" component={ManageSubgroupsScreen}/>
|
||||||
<Stack.Screen name="AdminHubScreen" component={AdminHubScreen} />
|
<Stack.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
|
||||||
<Stack.Screen name="ManageSubgroupsScreen" component={ManageSubgroupsScreen} />
|
<Stack.Screen name="MapScreen" component={MapScreen}/>
|
||||||
<Stack.Screen name="LeaderboardScreen" component={LeaderboardScreen} />
|
<Stack.Screen name="TeamScreen" component={TeamScreen}/>
|
||||||
<Stack.Screen name="MapScreen" component={MapScreen} />
|
<Stack.Screen name="CreateGameScreen" component={CreateGameScreen}/>
|
||||||
<Stack.Screen name="TeamScreen" component={TeamScreen} />
|
<Stack.Screen name="ManageGameScreen" component={ManageGameScreen}/>
|
||||||
<Stack.Screen name="CreateGameScreen" component={CreateGameScreen} />
|
</Stack.Navigator>
|
||||||
<Stack.Screen name="ManageGameScreen" component={ManageGameScreen} />
|
|
||||||
</Stack.Navigator>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
return (
|
return (
|
||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{headerShown: false}}
|
||||||
headerShown: false,
|
>
|
||||||
}}
|
<Stack.Screen name="Auth" component={AuthStack}/>
|
||||||
>
|
<Stack.Screen name="Game" component={GameStack}/>
|
||||||
<Stack.Screen name="Auth" component={AuthStack} />
|
<Stack.Screen name="Admin" component={AdminStack}/>
|
||||||
<Stack.Screen name="Game" component={GameStack} />
|
</Stack.Navigator>
|
||||||
<Stack.Screen name="Admin" component={AdminStack} />
|
</NavigationContainer>
|
||||||
</Stack.Navigator>
|
);
|
||||||
</NavigationContainer>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
@@ -3,10 +3,7 @@
|
|||||||
"main": "node_modules/expo/AppEntry.js",
|
"main": "node_modules/expo/AppEntry.js",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"api": "node server/server.js",
|
|
||||||
"api:standalone": "npm --prefix server start",
|
|
||||||
"start": "expo start",
|
"start": "expo start",
|
||||||
"start:api": "npm run api",
|
|
||||||
"android": "expo run:android",
|
"android": "expo run:android",
|
||||||
"ios": "expo run:ios",
|
"ios": "expo run:ios",
|
||||||
"web": "expo start --web",
|
"web": "expo start --web",
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
const API = {};
|
||||||
|
|
||||||
|
API.get = (endpoint) => callFetch(endpoint, 'GET');
|
||||||
|
API.post = (endpoint, data) => callFetch(endpoint, 'POST', data);
|
||||||
|
API.put = (endpoint, data) => callFetch(endpoint, 'PUT', data);
|
||||||
|
API.delete = (endpoint) => callFetch(endpoint, 'DELETE');
|
||||||
|
|
||||||
|
export default API;
|
||||||
|
|
||||||
|
const callFetch = async (endpoint, method, dataObj = null) => {
|
||||||
|
// Build request object
|
||||||
|
let requestObj = {method: method}; // GET, POST, PUT or DELETE
|
||||||
|
if (dataObj)
|
||||||
|
requestObj = {
|
||||||
|
...requestObj,
|
||||||
|
headers: {'Content-type': 'application/json'},
|
||||||
|
body: JSON.stringify(dataObj),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call the fetch and process the return
|
||||||
|
try {
|
||||||
|
let result = null;
|
||||||
|
const response = await fetch(endpoint, requestObj);
|
||||||
|
if (response.status !== 204) result = await response.json();
|
||||||
|
return response.status >= 200 && response.status < 300
|
||||||
|
? {isSuccess: true, result}
|
||||||
|
: {isSuccess: false, message: `${result.message}`};
|
||||||
|
} catch (error) {
|
||||||
|
return {isSuccess: false, message: error.message};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import API from "./API";
|
||||||
|
|
||||||
|
|
||||||
|
const useLoad = (loadEndpoint) => {
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
|
const [records, setRecords] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
// Loader ----------------------
|
||||||
|
const loadRecords = async (endpoint) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
const response = await API.get(endpoint);
|
||||||
|
if (response.isSuccess) setRecords(response.result);
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadRecords(loadEndpoint);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// return ----------------------
|
||||||
|
|
||||||
|
return [records, setRecords, isLoading, loadRecords];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useLoad;
|
||||||
|
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import {Pressable, StyleSheet, Text, View} from "react-native";
|
import {Pressable, StyleSheet, Text, View} from "react-native";
|
||||||
|
|
||||||
export const Button = ({ label, icon, onClick, styleLabel, styleButton }) => {
|
export const Button = ({ label, icon, onClick, styleLabel, styleButton }) => {
|
||||||
|
// Initialisation -------------
|
||||||
|
// State ----------------------
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable onPress={onClick} style={[styles.button, styleButton]}>
|
<Pressable onPress={onClick} style={[styles.button, styleButton]}>
|
||||||
{icon ? icon : null}
|
{icon ? icon : null}
|
||||||
@@ -10,6 +15,11 @@ export const Button = ({ label, icon, onClick, styleLabel, styleButton }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ButtonTray = ({ children }) => {
|
export const ButtonTray = ({ children }) => {
|
||||||
|
// Initialisation -------------
|
||||||
|
// State ----------------------
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
return <View style={styles.buttonTray}>{children}</View>;
|
return <View style={styles.buttonTray}>{children}</View>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import React from 'react';
|
|||||||
import MapView, {Circle, Marker} from 'react-native-maps';
|
import MapView, {Circle, Marker} from 'react-native-maps';
|
||||||
|
|
||||||
const AdminCacheEditorView = ({ userLocation, caches, onAddCache, onMoveCache }) => {
|
const AdminCacheEditorView = ({ userLocation, caches, onAddCache, onMoveCache }) => {
|
||||||
|
// Initialisation -------------
|
||||||
|
// State ----------------------
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
if (!userLocation) {
|
if (!userLocation) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ import React, {useEffect, useState} from 'react';
|
|||||||
import {StyleSheet, Text, View} from 'react-native';
|
import {StyleSheet, Text, View} from 'react-native';
|
||||||
|
|
||||||
const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => {
|
const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => {
|
||||||
|
// Initialisation -------------
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
const [timeLeft, setTimeLeft] = useState(5);
|
const [timeLeft, setTimeLeft] = useState(5);
|
||||||
|
|
||||||
|
// Handlers -------------------
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer;
|
let timer;
|
||||||
if (isClaiming && timeLeft > 0) {
|
if (isClaiming && timeLeft > 0) {
|
||||||
@@ -16,6 +21,8 @@ const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => {
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [isClaiming, timeLeft, cache, onClaimSuccess]);
|
}, [isClaiming, timeLeft, cache, onClaimSuccess]);
|
||||||
|
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
if (!isClaiming) {
|
if (!isClaiming) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -45,4 +52,3 @@ const styles = StyleSheet.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default ClaimTimerView;
|
export default ClaimTimerView;
|
||||||
|
|
||||||
@@ -3,9 +3,12 @@ import MapView, {Circle} from 'react-native-maps';
|
|||||||
import ClaimTimerView from './ClaimTimerView';
|
import ClaimTimerView from './ClaimTimerView';
|
||||||
|
|
||||||
const PlayerMapView = ({ userLocation, visibleCache, isClaiming, onClaimSuccess }) => {
|
const PlayerMapView = ({ userLocation, visibleCache, isClaiming, onClaimSuccess }) => {
|
||||||
if (!userLocation) {
|
// Initialisation -------------
|
||||||
return null;
|
// State ----------------------
|
||||||
}
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
|
if (!userLocation) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,74 +1,79 @@
|
|||||||
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
||||||
|
|
||||||
const BottomNavbar = ({ navigation, routeName }) => {
|
const BottomNavbar = ({ navigation, routeName }) => {
|
||||||
const tabs = [
|
// Initialisation ------------
|
||||||
{
|
|
||||||
label: 'Map',
|
|
||||||
route: 'MapScreen',
|
|
||||||
onPress: () => navigation.navigate('Game', { screen: 'MapScreen' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Leaderboard',
|
|
||||||
route: 'LeaderboardScreen',
|
|
||||||
onPress: () => navigation.navigate('Game', { screen: 'LeaderboardScreen' }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Current Team',
|
|
||||||
route: 'TeamScreen',
|
|
||||||
onPress: () => navigation.navigate('Game', { screen: 'TeamScreen' }),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
const tabs = [
|
||||||
<View style={styles.container}>
|
{
|
||||||
{tabs.map((tab) => {
|
label: 'Map',
|
||||||
const isActive = routeName === tab.route;
|
route: 'MapScreen',
|
||||||
return (
|
onPress: () => navigation.navigate('Game', { screen: 'MapScreen' }),
|
||||||
<Pressable
|
},
|
||||||
key={tab.route}
|
{
|
||||||
onPress={tab.onPress}
|
label: 'Leaderboard',
|
||||||
style={[styles.tab, isActive && styles.tabActive]}
|
route: 'LeaderboardScreen',
|
||||||
>
|
onPress: () => navigation.navigate('Game', { screen: 'LeaderboardScreen' }),
|
||||||
<Text style={[styles.tabText, isActive && styles.tabTextActive]}>{tab.label}</Text>
|
},
|
||||||
</Pressable>
|
{
|
||||||
);
|
label: 'Current Team',
|
||||||
})}
|
route: 'TeamScreen',
|
||||||
</View>
|
onPress: () => navigation.navigate('Game', { screen: 'TeamScreen' }),
|
||||||
);
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
{tabs.map((tab) => {
|
||||||
|
const isActive = routeName === tab.route;
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
key={tab.route}
|
||||||
|
onPress={tab.onPress}
|
||||||
|
style={[styles.tab, isActive && styles.tabActive]}
|
||||||
|
>
|
||||||
|
<Text style={[styles.tabText, isActive && styles.tabTextActive]}>{tab.label}</Text>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
minHeight: 64,
|
minHeight: 64,
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: '#e5e7eb',
|
borderTopColor: '#e5e7eb',
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-around',
|
justifyContent: 'space-around',
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
marginHorizontal: 4,
|
marginHorizontal: 4,
|
||||||
},
|
},
|
||||||
tabActive: {
|
tabActive: {
|
||||||
backgroundColor: '#eff6ff',
|
backgroundColor: '#eff6ff',
|
||||||
},
|
},
|
||||||
tabText: {
|
tabText: {
|
||||||
color: '#374151',
|
color: '#374151',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
tabTextActive: {
|
tabTextActive: {
|
||||||
color: '#2563eb',
|
color: '#2563eb',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default BottomNavbar;
|
export default BottomNavbar;
|
||||||
|
|
||||||
@@ -5,18 +5,18 @@ import TopNavbar from "./TopNavbar";
|
|||||||
import BottomNavbar from "./BottomNavbar";
|
import BottomNavbar from "./BottomNavbar";
|
||||||
|
|
||||||
const Screen = ({ children, style }) => {
|
const Screen = ({ children, style }) => {
|
||||||
|
// Initialisation -------------
|
||||||
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const titleMap = {
|
const titleMap = {
|
||||||
LoginScreen: "Login",
|
|
||||||
RegisterScreen: "Register",
|
|
||||||
GameTypeScreen: "Game Type",
|
GameTypeScreen: "Game Type",
|
||||||
JoinPrivateGameScreen: "Join Private Game",
|
JoinPrivateGameScreen: "Join Private Game",
|
||||||
ManagePrivateGameScreen: "Manage Private Game",
|
ManagePrivateGameScreen: "Manage Private Game",
|
||||||
CreateGameScreen: "Create Game",
|
CreateGameScreen: "Create Game",
|
||||||
ManageGameScreen: "Manage Game",
|
ManageGameScreen: "Manage Game",
|
||||||
GameLobbyScreen: "Leaderboard",
|
GameLobbyScreen: "Game Lobby",
|
||||||
LeaderboardScreen: "Leaderboard",
|
LeaderboardScreen: "Leaderboard",
|
||||||
TeamScreen: "Current Team",
|
TeamScreen: "Current Team",
|
||||||
MapScreen: "Map",
|
MapScreen: "Map",
|
||||||
@@ -24,15 +24,21 @@ const Screen = ({ children, style }) => {
|
|||||||
ManageSubgroupsScreen: "Manage Subgroups",
|
ManageSubgroupsScreen: "Manage Subgroups",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
const routeName = route.name;
|
const routeName = route.name;
|
||||||
const showBack = routeName !== "LoginScreen" && navigation.canGoBack();
|
const isAuthScreen = routeName === "LoginScreen" || routeName === "RegisterScreen";
|
||||||
|
const showBack = !isAuthScreen && navigation.canGoBack();
|
||||||
const pageTitle = titleMap[routeName] || "Treasure Hunt";
|
const pageTitle = titleMap[routeName] || "Treasure Hunt";
|
||||||
|
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.screen, style]}>
|
<View style={[styles.screen, style]}>
|
||||||
<TopNavbar title={pageTitle} showBack={showBack} navigation={navigation} />
|
{!isAuthScreen && <TopNavbar title={pageTitle} showBack={showBack} navigation={navigation} />}
|
||||||
<View style={styles.content}>{children}</View>
|
<View style={styles.content}>{children}</View>
|
||||||
<BottomNavbar navigation={navigation} routeName={routeName} />
|
{!isAuthScreen && <BottomNavbar navigation={navigation} routeName={routeName} />}
|
||||||
<StatusBar style="light" />
|
<StatusBar style="light" />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,90 +1,94 @@
|
|||||||
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
||||||
import {clearSession} from '../../hooks/SessionStore';
|
import {clearSession} from '../../hooks/SessionStore';
|
||||||
|
|
||||||
const TopNavbar = ({ title, showBack, navigation }) => {
|
const TopNavbar = ({title, showBack, navigation}) => {
|
||||||
const routeName = navigation.getState()?.routes?.[navigation.getState().index]?.name;
|
// Initialisation ------------
|
||||||
const showLogout = routeName !== 'LoginScreen' && routeName !== 'RegisterScreen';
|
|
||||||
|
|
||||||
const handleBack = () => {
|
const routeName = navigation.getState()?.routes?.[navigation.getState().index]?.name;
|
||||||
if (navigation.canGoBack()) {
|
const showLogout = routeName !== 'LoginScreen' && routeName !== 'RegisterScreen';
|
||||||
navigation.goBack();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
// State ----------------------
|
||||||
clearSession();
|
// Handlers -------------------
|
||||||
navigation.reset({
|
|
||||||
index: 0,
|
|
||||||
routes: [{ name: 'Auth', params: { screen: 'LoginScreen' } }],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const handleBack = () => {
|
||||||
<View style={styles.container}>
|
if (navigation.canGoBack()) navigation.goBack();
|
||||||
<View style={styles.leftSlot}>
|
};
|
||||||
{showBack ? (
|
|
||||||
<Pressable onPress={handleBack} style={styles.iconButton}>
|
const handleLogout = () => {
|
||||||
<Text style={styles.iconText}>{'<'}</Text>
|
clearSession();
|
||||||
</Pressable>
|
navigation.reset({
|
||||||
) : null}
|
index: 0,
|
||||||
</View>
|
routes: [{name: 'Auth', params: {screen: 'LoginScreen'}}],
|
||||||
<Text style={styles.title}>{title}</Text>
|
});
|
||||||
<View style={styles.rightSlot}>
|
};
|
||||||
{showLogout ? (
|
|
||||||
<Pressable onPress={handleLogout} style={styles.logoutButton}>
|
// View -----------------------
|
||||||
<Text style={styles.logoutText}>Logout</Text>
|
|
||||||
</Pressable>
|
return (
|
||||||
) : null}
|
<View style={styles.container}>
|
||||||
</View>
|
<View style={styles.leftSlot}>
|
||||||
</View>
|
{showBack ? (
|
||||||
);
|
<Pressable onPress={handleBack} style={styles.iconButton}>
|
||||||
|
<Text style={styles.iconText}>{'<'}</Text>
|
||||||
|
</Pressable>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
<Text style={styles.title}>{title}</Text>
|
||||||
|
<View style={styles.rightSlot}>
|
||||||
|
{showLogout ? (
|
||||||
|
<Pressable onPress={handleLogout} style={styles.logoutButton}>
|
||||||
|
<Text style={styles.logoutText}>Logout</Text>
|
||||||
|
</Pressable>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
minHeight: 56,
|
minHeight: 56,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: '#e5e7eb',
|
borderBottomColor: '#e5e7eb',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
},
|
},
|
||||||
leftSlot: {
|
leftSlot: {
|
||||||
width: 72,
|
width: 72,
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
},
|
},
|
||||||
rightSlot: {
|
rightSlot: {
|
||||||
width: 72,
|
width: 72,
|
||||||
alignItems: 'flex-end',
|
alignItems: 'flex-end',
|
||||||
},
|
},
|
||||||
iconButton: {
|
iconButton: {
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
},
|
},
|
||||||
iconText: {
|
iconText: {
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
color: '#111827',
|
color: '#111827',
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: '#111827',
|
color: '#111827',
|
||||||
},
|
},
|
||||||
logoutButton: {
|
logoutButton: {
|
||||||
paddingVertical: 6,
|
paddingVertical: 6,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
},
|
},
|
||||||
logoutText: {
|
logoutText: {
|
||||||
color: '#2563eb',
|
color: '#2563eb',
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TopNavbar;
|
export default TopNavbar;
|
||||||
|
|
||||||
@@ -2,17 +2,24 @@ import React from 'react';
|
|||||||
import {StyleSheet, Text} from 'react-native';
|
import {StyleSheet, Text} from 'react-native';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const AdminHubScreen = () => (
|
const AdminHubScreen = () => {
|
||||||
<Screen style={styles.container}>
|
// Initialisation -------------
|
||||||
<Text style={styles.title}>Admin Hub</Text>
|
// State ----------------------
|
||||||
<Text style={styles.body}>Admin hub setup goes here.</Text>
|
// Handlers -------------------
|
||||||
</Screen>
|
// View -----------------------
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Admin Hub</Text>
|
||||||
|
<Text style={styles.body}>Admin hub setup goes here.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { justifyContent: 'center', alignItems: 'center' },
|
container: {justifyContent: 'center', alignItems: 'center'},
|
||||||
title: { fontSize: 24, fontWeight: 'bold' },
|
title: {fontSize: 24, fontWeight: 'bold'},
|
||||||
body: { color: '#4b5563', fontSize: 15, marginTop: 10 },
|
body: {color: '#4b5563', fontSize: 15, marginTop: 10},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default AdminHubScreen;
|
export default AdminHubScreen;
|
||||||
@@ -2,17 +2,24 @@ import React from 'react';
|
|||||||
import {StyleSheet, Text} from 'react-native';
|
import {StyleSheet, Text} from 'react-native';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const CreateGameScreen = () => (
|
const CreateGameScreen = () => {
|
||||||
<Screen style={styles.container}>
|
// Initialisation -------------
|
||||||
<Text style={styles.title}>Create Game</Text>
|
// State ----------------------
|
||||||
<Text style={styles.body}>Create game setup goes here.</Text>
|
// Handlers -------------------
|
||||||
</Screen>
|
// View -----------------------
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Create Game</Text>
|
||||||
|
<Text style={styles.body}>Create game setup goes here.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { justifyContent: 'center', alignItems: 'center' },
|
container: {justifyContent: 'center', alignItems: 'center'},
|
||||||
title: { fontSize: 24, fontWeight: 'bold' },
|
title: {fontSize: 24, fontWeight: 'bold'},
|
||||||
body: { color: '#4b5563', fontSize: 15, marginTop: 10 },
|
body: {color: '#4b5563', fontSize: 15, marginTop: 10},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default CreateGameScreen;
|
export default CreateGameScreen;
|
||||||
@@ -2,17 +2,24 @@ import React from 'react';
|
|||||||
import {StyleSheet, Text} from 'react-native';
|
import {StyleSheet, Text} from 'react-native';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const GameLobbyScreen = () => (
|
const GameLobbyScreen = () => {
|
||||||
<Screen style={styles.container}>
|
// Initialisation -------------
|
||||||
<Text style={styles.title}>Game Lobby</Text>
|
// State ----------------------
|
||||||
<Text style={styles.body}>Game lobby setup goes here.</Text>
|
// Handlers -------------------
|
||||||
</Screen>
|
// View -----------------------
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Game Lobby</Text>
|
||||||
|
<Text style={styles.body}>Game lobby setup goes here.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { justifyContent: 'center', alignItems: 'center' },
|
container: {justifyContent: 'center', alignItems: 'center'},
|
||||||
title: { fontSize: 24, fontWeight: 'bold' },
|
title: {fontSize: 24, fontWeight: 'bold'},
|
||||||
body: { color: '#4b5563', fontSize: 15, marginTop: 10 },
|
body: {color: '#4b5563', fontSize: 15, marginTop: 10},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default GameLobbyScreen;
|
export default GameLobbyScreen;
|
||||||
@@ -3,66 +3,71 @@ import {Pressable, StyleSheet, Text, View} from 'react-native';
|
|||||||
import useGameHook from '../../hooks/useGameHook';
|
import useGameHook from '../../hooks/useGameHook';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const GameTypeScreen = ({ navigation }) => {
|
const GameTypeScreen = ({navigation}) => {
|
||||||
const { getGameTypes } = useGameHook();
|
// Initialisation ------------
|
||||||
const [gameTypes, setGameTypes] = useState([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const {getGameTypes} = useGameHook();
|
||||||
let isMounted = true;
|
|
||||||
|
|
||||||
const load = async () => {
|
// State ----------------------
|
||||||
const types = await getGameTypes();
|
|
||||||
if (isMounted) {
|
|
||||||
setGameTypes(types || []);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
load();
|
const [gameTypes, setGameTypes] = useState([]);
|
||||||
|
|
||||||
return () => {
|
// Handlers -------------------
|
||||||
isMounted = false;
|
|
||||||
};
|
|
||||||
}, [getGameTypes]);
|
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<Screen style={styles.container}>
|
let isMounted = true;
|
||||||
<Text style={styles.title}>Select Game Type</Text>
|
const load = async () => {
|
||||||
<View style={styles.content}>
|
const types = await getGameTypes();
|
||||||
{gameTypes && gameTypes.map((type) => (
|
if (isMounted) setGameTypes(types || []);
|
||||||
<Pressable key={type} style={styles.button} onPress={() => navigation.navigate('GameLobbyScreen')}>
|
};
|
||||||
<Text style={styles.buttonText}>{type}</Text>
|
load();
|
||||||
</Pressable>
|
return () => { isMounted = false; };
|
||||||
))}
|
}, []);
|
||||||
</View>
|
|
||||||
</Screen>
|
// View -----------------------
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Select Game Type</Text>
|
||||||
|
<View style={styles.content}>
|
||||||
|
{gameTypes && gameTypes.map((type) => (
|
||||||
|
<Pressable
|
||||||
|
key={type}
|
||||||
|
style={styles.button}
|
||||||
|
onPress={() => navigation.navigate('GameLobbyScreen')}
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>{type}</Text>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
gap: 12,
|
gap: 12,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: '#2563eb',
|
backgroundColor: '#2563eb',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
buttonText: {
|
buttonText: {
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default GameTypeScreen;
|
export default GameTypeScreen;
|
||||||
|
|
||||||
@@ -3,6 +3,11 @@ import {StyleSheet, Text} from 'react-native';
|
|||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const JoinPrivateGameScreen = () => {
|
const JoinPrivateGameScreen = () => {
|
||||||
|
// Initialisation -------------
|
||||||
|
// State ----------------------
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen style={styles.container}>
|
||||||
<Text style={styles.title}>Join Private Game</Text>
|
<Text style={styles.title}>Join Private Game</Text>
|
||||||
|
|||||||
@@ -2,18 +2,24 @@ import React from 'react';
|
|||||||
import {StyleSheet, Text} from 'react-native';
|
import {StyleSheet, Text} from 'react-native';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const LeaderboardScreen = () => (
|
const LeaderboardScreen = () => {
|
||||||
<Screen style={styles.container}>
|
// Initialisation -------------
|
||||||
<Text style={styles.title}>Leaderboard</Text>
|
// State ----------------------
|
||||||
<Text style={styles.body}>Leaderboard view goes here.</Text>
|
// Handlers -------------------
|
||||||
</Screen>
|
// View -----------------------
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Leaderboard</Text>
|
||||||
|
<Text style={styles.body}>Leaderboard view goes here.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { justifyContent: 'center', alignItems: 'center' },
|
container: {justifyContent: 'center', alignItems: 'center'},
|
||||||
title: { fontSize: 24, fontWeight: 'bold' },
|
title: {fontSize: 24, fontWeight: 'bold'},
|
||||||
body: { color: '#4b5563', fontSize: 15, marginTop: 10 },
|
body: {color: '#4b5563', fontSize: 15, marginTop: 10},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default LeaderboardScreen;
|
export default LeaderboardScreen;
|
||||||
|
|
||||||
@@ -5,9 +5,16 @@ import Screen from '../layout/Screen';
|
|||||||
import {Button, ButtonTray} from '../UI/Button';
|
import {Button, ButtonTray} from '../UI/Button';
|
||||||
|
|
||||||
const LoginScreen = ({ navigation }) => {
|
const LoginScreen = ({ navigation }) => {
|
||||||
|
// Initialisation ------------->
|
||||||
|
|
||||||
const { email, password, error, isLoading, setEmail, setPassword, login } = useAuthHook();
|
const { email, password, error, isLoading, setEmail, setPassword, login } = useAuthHook();
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
// Handlers -------------------
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
const user = await login();
|
const user = await login();
|
||||||
@@ -22,6 +29,8 @@ const LoginScreen = ({ navigation }) => {
|
|||||||
navigation.reset({ index: 0, routes: [{ name: 'Game' }] });
|
navigation.reset({ index: 0, routes: [{ name: 'Game' }] });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen style={styles.container}>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
@@ -107,4 +116,3 @@ const styles = StyleSheet.create({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default LoginScreen;
|
export default LoginScreen;
|
||||||
|
|
||||||
@@ -7,163 +7,138 @@ import useGameHook from '../../hooks/useGameHook';
|
|||||||
import {getSession} from '../../hooks/SessionStore';
|
import {getSession} from '../../hooks/SessionStore';
|
||||||
|
|
||||||
const ManageGameScreen = () => {
|
const ManageGameScreen = () => {
|
||||||
const session = getSession();
|
// Initialisation ------------
|
||||||
const { getCaches, upsertCache } = useGameHook();
|
|
||||||
const [userLocation, setUserLocation] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
const [cacheRecords, setCacheRecords] = useState([]);
|
|
||||||
|
|
||||||
const loadCaches = useCallback(async () => {
|
const session = getSession();
|
||||||
if (!session.currentGid) {
|
const {getCaches, upsertCache} = useGameHook();
|
||||||
setCacheRecords([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rows = await getCaches(session.currentGid, null);
|
|
||||||
setCacheRecords(rows || []);
|
|
||||||
}, [getCaches, session.currentGid]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// State ----------------------
|
||||||
loadCaches();
|
|
||||||
}, [loadCaches]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const [userLocation, setUserLocation] = useState(null);
|
||||||
let locationSub;
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [cacheRecords, setCacheRecords] = useState([]);
|
||||||
|
|
||||||
const start = async () => {
|
// Handlers -------------------
|
||||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
||||||
if (status !== 'granted') {
|
|
||||||
setError('Location permission denied');
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced });
|
const loadCaches = useCallback(async () => {
|
||||||
setUserLocation({ latitude: current.coords.latitude, longitude: current.coords.longitude });
|
if (!session.currentGid) {
|
||||||
|
setCacheRecords([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rows = await getCaches(session.currentGid, null);
|
||||||
|
setCacheRecords(rows || []);
|
||||||
|
}, [session.currentGid]);
|
||||||
|
|
||||||
locationSub = await Location.watchPositionAsync(
|
useEffect(() => { loadCaches(); }, [loadCaches]);
|
||||||
{
|
|
||||||
accuracy: Location.Accuracy.Balanced,
|
|
||||||
distanceInterval: 1,
|
|
||||||
timeInterval: 1000,
|
|
||||||
},
|
|
||||||
(next) => {
|
|
||||||
setUserLocation({ latitude: next.coords.latitude, longitude: next.coords.longitude });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
setLoading(false);
|
useEffect(() => {
|
||||||
|
let locationSub;
|
||||||
|
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});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
start();
|
||||||
|
return () => { if (locationSub) locationSub.remove(); };
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAddCache = async (coordinate) => {
|
||||||
|
if (!session.currentGid) return;
|
||||||
|
await upsertCache({
|
||||||
|
gid: session.currentGid,
|
||||||
|
latitude: coordinate.latitude,
|
||||||
|
longitude: coordinate.longitude,
|
||||||
|
radius: 20,
|
||||||
|
clue: `Cache ${cacheRecords.length + 1}`,
|
||||||
|
subgroupId: 1,
|
||||||
|
});
|
||||||
|
await loadCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
start();
|
const handleMoveCache = async (cacheId, coordinate) => {
|
||||||
|
if (!session.currentGid) return;
|
||||||
return () => {
|
const current = cacheRecords.find((cache) => cache.id === cacheId);
|
||||||
if (locationSub) {
|
if (!current) return;
|
||||||
locationSub.remove();
|
await upsertCache({
|
||||||
}
|
gid: session.currentGid,
|
||||||
|
cacheId,
|
||||||
|
latitude: coordinate.latitude,
|
||||||
|
longitude: coordinate.longitude,
|
||||||
|
radius: current.radius,
|
||||||
|
clue: current.clue,
|
||||||
|
subgroupId: current.subgroupId,
|
||||||
|
});
|
||||||
|
await loadCaches();
|
||||||
};
|
};
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleAddCache = async (coordinate) => {
|
// View -----------------------
|
||||||
|
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
return;
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.body}>Create or join a game first.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await upsertCache({
|
|
||||||
gid: session.currentGid,
|
|
||||||
latitude: coordinate.latitude,
|
|
||||||
longitude: coordinate.longitude,
|
|
||||||
radius: 20,
|
|
||||||
clue: `Cache ${cacheRecords.length + 1}`,
|
|
||||||
subgroupId: 1,
|
|
||||||
});
|
|
||||||
await loadCaches();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMoveCache = async (cacheId, coordinate) => {
|
if (!session.isAcceptedAdmin) {
|
||||||
if (!session.currentGid) {
|
return (
|
||||||
return;
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.body}>Only admins can edit cache locations.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const current = cacheRecords.find((cache) => cache.id === cacheId);
|
|
||||||
if (!current) {
|
if (loading) {
|
||||||
return;
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<ActivityIndicator size="large"/>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.error}>{error}</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await upsertCache({
|
|
||||||
gid: session.currentGid,
|
|
||||||
cacheId,
|
|
||||||
latitude: coordinate.latitude,
|
|
||||||
longitude: coordinate.longitude,
|
|
||||||
radius: current.radius,
|
|
||||||
clue: current.clue,
|
|
||||||
subgroupId: current.subgroupId,
|
|
||||||
});
|
|
||||||
await loadCaches();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!session.currentGid) {
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.container}>
|
||||||
<Text style={styles.body}>Create or join a game first.</Text>
|
<View style={styles.mapWrap}>
|
||||||
</Screen>
|
<AdminCacheEditorView
|
||||||
|
userLocation={userLocation}
|
||||||
|
caches={cacheRecords}
|
||||||
|
onAddCache={handleAddCache}
|
||||||
|
onMoveCache={handleMoveCache}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (!session.isAcceptedAdmin) {
|
|
||||||
return (
|
|
||||||
<Screen style={styles.center}>
|
|
||||||
<Text style={styles.body}>Only admins can edit cache locations.</Text>
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<Screen style={styles.center}>
|
|
||||||
<ActivityIndicator size="large" />
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<Screen style={styles.center}>
|
|
||||||
<Text style={styles.error}>{error}</Text>
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Screen style={styles.container}>
|
|
||||||
<View style={styles.mapWrap}>
|
|
||||||
<AdminCacheEditorView
|
|
||||||
userLocation={userLocation}
|
|
||||||
caches={cacheRecords}
|
|
||||||
onAddCache={handleAddCache}
|
|
||||||
onMoveCache={handleMoveCache}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {padding: 0},
|
||||||
padding: 0,
|
mapWrap: {flex: 1},
|
||||||
},
|
center: {justifyContent: 'center', alignItems: 'center'},
|
||||||
mapWrap: {
|
body: {color: '#4b5563', fontSize: 15},
|
||||||
flex: 1,
|
error: {color: '#dc2626', fontSize: 15},
|
||||||
},
|
|
||||||
center: {
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
color: '#4b5563',
|
|
||||||
fontSize: 15,
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
color: '#dc2626',
|
|
||||||
fontSize: 15,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ManageGameScreen;
|
export default ManageGameScreen;
|
||||||
@@ -3,18 +3,23 @@ import {StyleSheet, Text} from 'react-native';
|
|||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const ManagePrivateGameScreen = () => {
|
const ManagePrivateGameScreen = () => {
|
||||||
return (
|
// Initialisation -------------
|
||||||
<Screen style={styles.container}>
|
// State ----------------------
|
||||||
<Text style={styles.title}>Manage Private Game</Text>
|
// Handlers -------------------
|
||||||
<Text style={styles.body}>Manage private game setup goes here.</Text>
|
// View -----------------------
|
||||||
</Screen>
|
|
||||||
);
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Manage Private Game</Text>
|
||||||
|
<Text style={styles.body}>Manage private game setup goes here.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { justifyContent: 'center', alignItems: 'center' },
|
container: {justifyContent: 'center', alignItems: 'center'},
|
||||||
title: { fontSize: 24, fontWeight: 'bold' },
|
title: {fontSize: 24, fontWeight: 'bold'},
|
||||||
body: { color: '#4b5563', fontSize: 15, marginTop: 10 },
|
body: {color: '#4b5563', fontSize: 15, marginTop: 10},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ManagePrivateGameScreen;
|
export default ManagePrivateGameScreen;
|
||||||
@@ -2,17 +2,24 @@ import React from 'react';
|
|||||||
import {StyleSheet, Text} from 'react-native';
|
import {StyleSheet, Text} from 'react-native';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const ManageSubgroupsScreen = () => (
|
const ManageSubgroupsScreen = () => {
|
||||||
<Screen style={styles.container}>
|
// Initialisation -------------
|
||||||
<Text style={styles.title}>Manage Subgroups</Text>
|
// State ----------------------
|
||||||
<Text style={styles.body}>Manage subgroups setup goes here.</Text>
|
// Handlers -------------------
|
||||||
</Screen>
|
// View -----------------------
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Manage Subgroups</Text>
|
||||||
|
<Text style={styles.body}>Manage subgroups setup goes here.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { justifyContent: 'center', alignItems: 'center' },
|
container: {justifyContent: 'center', alignItems: 'center'},
|
||||||
title: { fontSize: 24, fontWeight: 'bold' },
|
title: {fontSize: 24, fontWeight: 'bold'},
|
||||||
body: { color: '#4b5563', fontSize: 15, marginTop: 10 },
|
body: {color: '#4b5563', fontSize: 15, marginTop: 10},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ManageSubgroupsScreen;
|
export default ManageSubgroupsScreen;
|
||||||
+123
-145
@@ -9,175 +9,153 @@ import usePlayerGame from '../../hooks/usePlayerGame';
|
|||||||
import {getSession} from '../../hooks/SessionStore';
|
import {getSession} from '../../hooks/SessionStore';
|
||||||
import Cache from '../../models/Cache';
|
import Cache from '../../models/Cache';
|
||||||
|
|
||||||
const toHeading = ({ x, y }) => {
|
const toHeading = ({x, y}) => {
|
||||||
const angle = Math.atan2(y, x) * (180 / Math.PI);
|
const angle = Math.atan2(y, x) * (180 / Math.PI);
|
||||||
return angle < 0 ? angle + 360 : angle;
|
return angle < 0 ? angle + 360 : angle;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MapScreen = () => {
|
const MapScreen = () => {
|
||||||
const session = getSession();
|
// Initialisation ------------
|
||||||
const { getCaches, claimCache } = useGameHook();
|
|
||||||
const [userLocation, setUserLocation] = useState(null);
|
|
||||||
const [heading, setHeading] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [cacheRecords, setCacheRecords] = useState([]);
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
|
|
||||||
const activeCaches = useMemo(() => (
|
const session = getSession();
|
||||||
cacheRecords.map(
|
const {getCaches, claimCache} = useGameHook();
|
||||||
(cache) => new Cache(
|
|
||||||
cache.id,
|
|
||||||
cache.coordinates.latitude,
|
|
||||||
cache.coordinates.longitude,
|
|
||||||
cache.radius,
|
|
||||||
cache.clue,
|
|
||||||
cache.groupId,
|
|
||||||
cache.subgroupId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
), [cacheRecords]);
|
|
||||||
|
|
||||||
const subgroupFilter = session.currentSGid;
|
// State ----------------------
|
||||||
const { visibleCache, isClaiming, setIsClaiming } = usePlayerGame(userLocation, heading, activeCaches);
|
|
||||||
|
|
||||||
const loadCaches = useCallback(async () => {
|
const [userLocation, setUserLocation] = useState(null);
|
||||||
if (!session.currentGid) {
|
const [heading, setHeading] = useState(null);
|
||||||
setCacheRecords([]);
|
const [loading, setLoading] = useState(true);
|
||||||
return;
|
const [cacheRecords, setCacheRecords] = useState([]);
|
||||||
}
|
const [error, setError] = useState('');
|
||||||
const rows = await getCaches(session.currentGid, subgroupFilter);
|
|
||||||
setCacheRecords(rows || []);
|
|
||||||
}, [getCaches, session.currentGid, subgroupFilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const activeCaches = useMemo(() => (
|
||||||
loadCaches();
|
cacheRecords.map((cache) => new Cache(
|
||||||
}, [loadCaches]);
|
cache.id,
|
||||||
|
cache.coordinates.latitude,
|
||||||
|
cache.coordinates.longitude,
|
||||||
|
cache.radius,
|
||||||
|
cache.clue,
|
||||||
|
cache.groupId,
|
||||||
|
cache.subgroupId,
|
||||||
|
))
|
||||||
|
), [cacheRecords]);
|
||||||
|
|
||||||
useEffect(() => {
|
const subgroupFilter = session.currentSGid;
|
||||||
let locationSub;
|
const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame(userLocation, heading, activeCaches);
|
||||||
let headingSub;
|
|
||||||
|
|
||||||
const start = async () => {
|
// Handlers -------------------
|
||||||
const { status } = await Location.requestForegroundPermissionsAsync();
|
|
||||||
if (status !== 'granted') {
|
|
||||||
setError('Location permission denied');
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const current = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced });
|
const loadCaches = useCallback(async () => {
|
||||||
setUserLocation({ latitude: current.coords.latitude, longitude: current.coords.longitude });
|
if (!session.currentGid) {
|
||||||
|
setCacheRecords([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rows = await getCaches(session.currentGid, subgroupFilter);
|
||||||
|
setCacheRecords(rows || []);
|
||||||
|
}, [session.currentGid, subgroupFilter]);
|
||||||
|
|
||||||
locationSub = await Location.watchPositionAsync(
|
useEffect(() => { loadCaches(); }, [loadCaches]);
|
||||||
{
|
|
||||||
accuracy: Location.Accuracy.Balanced,
|
|
||||||
distanceInterval: 1,
|
|
||||||
timeInterval: 1000,
|
|
||||||
},
|
|
||||||
(next) => {
|
|
||||||
setUserLocation({ latitude: next.coords.latitude, longitude: next.coords.longitude });
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Magnetometer.setUpdateInterval(500);
|
useEffect(() => {
|
||||||
headingSub = Magnetometer.addListener((data) => {
|
let locationSub;
|
||||||
setHeading(toHeading(data));
|
let headingSub;
|
||||||
});
|
|
||||||
|
|
||||||
setLoading(false);
|
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();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClaim = async (cacheId) => {
|
||||||
|
if (!session.currentGid) return;
|
||||||
|
await claimCache({
|
||||||
|
gid: session.currentGid,
|
||||||
|
cacheId,
|
||||||
|
uid: session.currentUid,
|
||||||
|
tid: session.currentTid,
|
||||||
|
});
|
||||||
|
setIsClaiming(false);
|
||||||
|
await loadCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
start();
|
// View -----------------------
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (locationSub) {
|
|
||||||
locationSub.remove();
|
|
||||||
}
|
|
||||||
if (headingSub) {
|
|
||||||
headingSub.remove();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleClaim = async (cacheId) => {
|
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
return;
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.body}>Join or create a game to use map features.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.isAcceptedAdmin) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.body}>Admins manage caches from Manage Game.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<ActivityIndicator size="large"/>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.error}>{error}</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
await claimCache({
|
|
||||||
gid: session.currentGid,
|
|
||||||
cacheId,
|
|
||||||
uid: session.currentUid,
|
|
||||||
tid: session.currentTid,
|
|
||||||
});
|
|
||||||
setIsClaiming(false);
|
|
||||||
await loadCaches();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!session.currentGid) {
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.container}>
|
||||||
<Text style={styles.body}>Join or create a game to use map features.</Text>
|
<View style={styles.mapWrap}>
|
||||||
</Screen>
|
<PlayerMapView
|
||||||
|
userLocation={userLocation}
|
||||||
|
visibleCache={visibleCache}
|
||||||
|
isClaiming={isClaiming}
|
||||||
|
onClaimSuccess={handleClaim}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (session.isAcceptedAdmin) {
|
|
||||||
return (
|
|
||||||
<Screen style={styles.center}>
|
|
||||||
<Text style={styles.body}>Admins manage caches from Manage Game.</Text>
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<Screen style={styles.center}>
|
|
||||||
<ActivityIndicator size="large" />
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
|
||||||
<Screen style={styles.center}>
|
|
||||||
<Text style={styles.error}>{error}</Text>
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Screen style={styles.container}>
|
|
||||||
<View style={styles.mapWrap}>
|
|
||||||
<PlayerMapView
|
|
||||||
userLocation={userLocation}
|
|
||||||
visibleCache={visibleCache}
|
|
||||||
isClaiming={isClaiming}
|
|
||||||
onClaimSuccess={handleClaim}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Screen>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {padding: 0},
|
||||||
padding: 0,
|
mapWrap: {flex: 1},
|
||||||
},
|
center: {justifyContent: 'center', alignItems: 'center'},
|
||||||
mapWrap: {
|
body: {color: '#4b5563', fontSize: 15},
|
||||||
flex: 1,
|
error: {color: '#dc2626', fontSize: 15},
|
||||||
},
|
|
||||||
center: {
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
color: '#4b5563',
|
|
||||||
fontSize: 15,
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
color: '#dc2626',
|
|
||||||
fontSize: 15,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default MapScreen;
|
export default MapScreen;
|
||||||
@@ -4,175 +4,177 @@ import useAuthHook from '../../hooks/useAuthHook';
|
|||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
import {Button, ButtonTray} from '../UI/Button';
|
import {Button, ButtonTray} from '../UI/Button';
|
||||||
|
|
||||||
const RegisterScreen = ({ navigation }) => {
|
const RegisterScreen = ({navigation}) => {
|
||||||
const {
|
// Initialisation ------------
|
||||||
username,
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
confirmPassword,
|
|
||||||
accountType,
|
|
||||||
error,
|
|
||||||
isLoading,
|
|
||||||
setUsername,
|
|
||||||
setEmail,
|
|
||||||
setPassword,
|
|
||||||
setConfirmPassword,
|
|
||||||
setAccountType,
|
|
||||||
register,
|
|
||||||
} = useAuthHook();
|
|
||||||
const [submitting, setSubmitting] = useState(false);
|
|
||||||
|
|
||||||
const handleRegister = async () => {
|
const {
|
||||||
setSubmitting(true);
|
username, email, password, confirmPassword, accountType,
|
||||||
const user = await register();
|
error, isLoading,
|
||||||
setSubmitting(false);
|
setUsername, setEmail, setPassword, setConfirmPassword, setAccountType,
|
||||||
if (!user) {
|
register,
|
||||||
return;
|
} = useAuthHook();
|
||||||
}
|
|
||||||
if (user.IsAcceptedAdmin) {
|
|
||||||
navigation.reset({ index: 0, routes: [{ name: 'Admin' }] });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
navigation.reset({ index: 0, routes: [{ name: 'Game' }] });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
// State ----------------------
|
||||||
<Screen style={styles.container}>
|
|
||||||
<View style={styles.content}>
|
|
||||||
<Text style={styles.title}>Create Account</Text>
|
|
||||||
|
|
||||||
<TextInput
|
const [submitting, setSubmitting] = useState(false);
|
||||||
style={styles.input}
|
|
||||||
placeholder="Username"
|
|
||||||
value={username}
|
|
||||||
onChangeText={setUsername}
|
|
||||||
editable={!isLoading}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
placeholder="Email"
|
|
||||||
autoCapitalize="none"
|
|
||||||
keyboardType="email-address"
|
|
||||||
value={email}
|
|
||||||
onChangeText={setEmail}
|
|
||||||
editable={!isLoading}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
placeholder="Password"
|
|
||||||
secureTextEntry
|
|
||||||
value={password}
|
|
||||||
onChangeText={setPassword}
|
|
||||||
editable={!isLoading}
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
placeholder="Confirm Password"
|
|
||||||
secureTextEntry
|
|
||||||
value={confirmPassword}
|
|
||||||
onChangeText={setConfirmPassword}
|
|
||||||
editable={!isLoading}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View style={styles.typeRow}>
|
// Handlers -------------------
|
||||||
<Pressable
|
|
||||||
style={[styles.typeButton, accountType === 'Individual' && styles.typeButtonActive]}
|
|
||||||
onPress={() => setAccountType('Individual')}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<Text style={[styles.typeText, accountType === 'Individual' && styles.typeTextActive]}>Individual</Text>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable
|
|
||||||
style={[styles.typeButton, accountType === 'Business/School' && styles.typeButtonActive]}
|
|
||||||
onPress={() => setAccountType('Business/School')}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
<Text style={[styles.typeText, accountType === 'Business/School' && styles.typeTextActive]}>Business</Text>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{error ? <Text style={styles.errorText}>{error}</Text> : null}
|
const handleRegister = async () => {
|
||||||
|
setSubmitting(true);
|
||||||
|
const user = await register();
|
||||||
|
setSubmitting(false);
|
||||||
|
if (!user) return;
|
||||||
|
if (user.IsAcceptedAdmin) {
|
||||||
|
navigation.reset({index: 0, routes: [{name: 'Admin'}]});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigation.reset({index: 0, routes: [{name: 'Game'}]});
|
||||||
|
};
|
||||||
|
|
||||||
<ButtonTray>
|
// View -----------------------
|
||||||
<Button
|
|
||||||
label={submitting ? 'Creating...' : 'Create Account'}
|
|
||||||
onClick={handleRegister}
|
|
||||||
styleButton={styles.submitButton}
|
|
||||||
styleLabel={styles.submitText}
|
|
||||||
/>
|
|
||||||
</ButtonTray>
|
|
||||||
|
|
||||||
<Pressable onPress={() => navigation.navigate('Auth', { screen: 'LoginScreen' })}>
|
return (
|
||||||
<Text style={styles.linkText}>Back to Login</Text>
|
<Screen style={styles.container}>
|
||||||
</Pressable>
|
<View style={styles.content}>
|
||||||
</View>
|
<Text style={styles.title}>Create Account</Text>
|
||||||
</Screen>
|
|
||||||
);
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Username"
|
||||||
|
value={username}
|
||||||
|
onChangeText={setUsername}
|
||||||
|
editable={!isLoading}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Email"
|
||||||
|
autoCapitalize="none"
|
||||||
|
keyboardType="email-address"
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
editable={!isLoading}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Password"
|
||||||
|
secureTextEntry
|
||||||
|
value={password}
|
||||||
|
onChangeText={setPassword}
|
||||||
|
editable={!isLoading}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
secureTextEntry
|
||||||
|
value={confirmPassword}
|
||||||
|
onChangeText={setConfirmPassword}
|
||||||
|
editable={!isLoading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.typeRow}>
|
||||||
|
<Pressable
|
||||||
|
style={[styles.typeButton, accountType === 'Individual' && styles.typeButtonActive]}
|
||||||
|
onPress={() => setAccountType('Individual')}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Text style={[styles.typeText, accountType === 'Individual' && styles.typeTextActive]}>
|
||||||
|
Individual
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
style={[styles.typeButton, accountType === 'Business/School' && styles.typeButtonActive]}
|
||||||
|
onPress={() => setAccountType('Business/School')}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<Text style={[styles.typeText, accountType === 'Business/School' && styles.typeTextActive]}>
|
||||||
|
Business
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{error ? <Text style={styles.errorText}>{error}</Text> : null}
|
||||||
|
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label={submitting ? 'Creating...' : 'Create Account'}
|
||||||
|
onClick={handleRegister}
|
||||||
|
styleButton={styles.submitButton}
|
||||||
|
styleLabel={styles.submitText}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
|
||||||
|
<Pressable onPress={() => navigation.navigate('Auth', {screen: 'LoginScreen'})}>
|
||||||
|
<Text style={styles.linkText}>Back to Login</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#d1d5db',
|
borderColor: '#d1d5db',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
typeRow: {
|
typeRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: 10,
|
gap: 10,
|
||||||
},
|
},
|
||||||
typeButton: {
|
typeButton: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#cbd5e1',
|
borderColor: '#cbd5e1',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
},
|
},
|
||||||
typeButtonActive: {
|
typeButtonActive: {
|
||||||
backgroundColor: '#2563eb',
|
backgroundColor: '#2563eb',
|
||||||
borderColor: '#2563eb',
|
borderColor: '#2563eb',
|
||||||
},
|
},
|
||||||
typeText: {
|
typeText: {
|
||||||
color: '#1f2937',
|
color: '#1f2937',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
typeTextActive: {
|
typeTextActive: {
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
color: '#dc2626',
|
color: '#dc2626',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
},
|
},
|
||||||
submitButton: {
|
submitButton: {
|
||||||
backgroundColor: '#2563eb',
|
backgroundColor: '#2563eb',
|
||||||
borderColor: '#2563eb',
|
borderColor: '#2563eb',
|
||||||
},
|
},
|
||||||
submitText: {
|
submitText: {
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
},
|
},
|
||||||
linkText: {
|
linkText: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: '#2563eb',
|
color: '#2563eb',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
paddingVertical: 10,
|
paddingVertical: 10,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default RegisterScreen;
|
export default RegisterScreen;
|
||||||
@@ -2,17 +2,24 @@ import React from 'react';
|
|||||||
import {StyleSheet, Text} from 'react-native';
|
import {StyleSheet, Text} from 'react-native';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
|
|
||||||
const TeamScreen = () => (
|
const TeamScreen = () => {
|
||||||
<Screen style={styles.container}>
|
// Initialisation -------------
|
||||||
<Text style={styles.title}>Team Management</Text>
|
// State ----------------------
|
||||||
<Text style={styles.body}>Team management setup goes here.</Text>
|
// Handlers -------------------
|
||||||
</Screen>
|
// View -----------------------
|
||||||
);
|
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.title}>Team Management</Text>
|
||||||
|
<Text style={styles.body}>Team management setup goes here.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { justifyContent: 'center', alignItems: 'center' },
|
container: {justifyContent: 'center', alignItems: 'center'},
|
||||||
title: { fontSize: 24, fontWeight: 'bold' },
|
title: {fontSize: 24, fontWeight: 'bold'},
|
||||||
body: { color: '#4b5563', fontSize: 15, marginTop: 10 },
|
body: {color: '#4b5563', fontSize: 15, marginTop: 10},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TeamScreen;
|
export default TeamScreen;
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// --- Session Store ---
|
||||||
|
|
||||||
const session = {
|
const session = {
|
||||||
currentUid: null,
|
currentUid: null,
|
||||||
currentGid: null,
|
currentGid: null,
|
||||||
@@ -7,6 +9,8 @@ const session = {
|
|||||||
isAcceptedAdmin: false,
|
isAcceptedAdmin: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- Handlers ---
|
||||||
|
|
||||||
const setSessionUser = (user) => {
|
const setSessionUser = (user) => {
|
||||||
session.currentUid = user?.Uid ?? null;
|
session.currentUid = user?.Uid ?? null;
|
||||||
session.currentGid = user?.Gid ?? null;
|
session.currentGid = user?.Gid ?? null;
|
||||||
|
|||||||
+2
-21
@@ -1,22 +1,3 @@
|
|||||||
import Constants from 'expo-constants';
|
const API_BASE_URL = 'http://localhost:3000';
|
||||||
|
|
||||||
const hostFromExpo = Constants.expoConfig?.hostUri?.split(':')[0] || null;
|
export default API_BASE_URL;
|
||||||
const apiBaseUrl = 'http://localhost:3000'; // Shirish, i need to deploy the database first so... wait until that... sorry buddy...
|
|
||||||
// if you want you can run your own db locally i can send you the files for it just ask :(((
|
|
||||||
|
|
||||||
const postJson = async (path, payload) => {
|
|
||||||
const response = await fetch(`${apiBaseUrl}${path}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(result.message || 'Request failed');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const postDb = async (action, payload = {}) => {
|
|
||||||
return postJson('/db', { action, payload });
|
|
||||||
};
|
|
||||||
+36
-25
@@ -1,8 +1,16 @@
|
|||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
import {postDb} from './dbLink';
|
import API from '../components/API/API';
|
||||||
|
import API_BASE_URL from './dbLink';
|
||||||
import {clearSession, setSessionUser} from './SessionStore';
|
import {clearSession, setSessionUser} from './SessionStore';
|
||||||
|
|
||||||
const useAuthHook = () => {
|
const useAuthHook = () => {
|
||||||
|
// Initialisation ------------
|
||||||
|
|
||||||
|
const loginEndpoint = `${API_BASE_URL}/auth/login`;
|
||||||
|
const registerEndpoint = `${API_BASE_URL}/auth/register`;
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
@@ -11,40 +19,41 @@ const useAuthHook = () => {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
// Handlers -------------------
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
setError('');
|
setError('');
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
const response = await API.post(loginEndpoint, {
|
||||||
const user = await postDb('login', { email: email.trim(), password });
|
email: email.trim(),
|
||||||
setSessionUser(user);
|
password,
|
||||||
setIsLoading(false);
|
});
|
||||||
return user;
|
setIsLoading(false);
|
||||||
} catch (e) {
|
if (response.isSuccess) {
|
||||||
setError(e.message);
|
setSessionUser(response.result);
|
||||||
setIsLoading(false);
|
return response.result;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
setError(response.message);
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const register = async () => {
|
const register = async () => {
|
||||||
setError('');
|
setError('');
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
const response = await API.post(registerEndpoint, {
|
||||||
const user = await postDb('register', {
|
username: username.trim(),
|
||||||
username: username.trim(),
|
email: email.trim().toLowerCase(),
|
||||||
email: email.trim().toLowerCase(),
|
password,
|
||||||
password,
|
confirmPassword,
|
||||||
confirmPassword,
|
accountType,
|
||||||
accountType,
|
});
|
||||||
});
|
setIsLoading(false);
|
||||||
setSessionUser(user);
|
if (response.isSuccess) {
|
||||||
setIsLoading(false);
|
setSessionUser(response.result);
|
||||||
return user;
|
return response.result;
|
||||||
} catch (e) {
|
|
||||||
setError(e.message);
|
|
||||||
setIsLoading(false);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
setError(response.message);
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
@@ -57,6 +66,8 @@ const useAuthHook = () => {
|
|||||||
setError('');
|
setError('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Return ---------------------
|
||||||
|
|
||||||
return {
|
return {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
|
|||||||
+84
-15
@@ -1,33 +1,102 @@
|
|||||||
import {postDb} from './dbLink';
|
import API from '../components/API/API';
|
||||||
|
import API_BASE_URL from './dbLink';
|
||||||
|
|
||||||
const useGameHook = () => {
|
const useGameHook = () => {
|
||||||
const request = async (action, payload = {}) => postDb(action, payload);
|
// Initialisation ------------
|
||||||
|
|
||||||
const getGameTypes = async () => request('getGameTypes');
|
const gameTypesEndpoint = `${API_BASE_URL}/game-types`;
|
||||||
|
const groupsEndpoint = `${API_BASE_URL}/groups`;
|
||||||
|
const subgroupMembershipsEndpoint = `${API_BASE_URL}/subgroup-memberships`;
|
||||||
|
const teamsEndpoint = `${API_BASE_URL}/teams`;
|
||||||
|
const teamMembersEndpoint = `${API_BASE_URL}/team-members`;
|
||||||
|
const gameDataEndpoint = `${API_BASE_URL}/game-data`;
|
||||||
|
const adminWaitlistEndpoint = `${API_BASE_URL}/admin-waitlist`;
|
||||||
|
|
||||||
const getCreatedPrivateGame = async (userId) => request('getCreatedPrivateGameByUser', { userId });
|
// Handlers -------------------
|
||||||
|
|
||||||
const createPrivateGame = async (payload) => request('createPrivateGame', payload);
|
const getGameTypes = async () => {
|
||||||
|
const response = await API.get(gameTypesEndpoint);
|
||||||
|
return response.isSuccess ? response.result : [];
|
||||||
|
};
|
||||||
|
|
||||||
const joinPrivateGame = async (payload) => request('joinPrivateGame', payload);
|
const getCreatedPrivateGame = async (userId) => {
|
||||||
|
const response = await API.get(`${groupsEndpoint}?CreatedByUid=${userId}`);
|
||||||
|
return response.isSuccess ? response.result : [];
|
||||||
|
};
|
||||||
|
|
||||||
const joinAsAdmin = async (payload) => request('joinAsAdmin', payload);
|
const createPrivateGame = async (payload) => {
|
||||||
|
const response = await API.post(groupsEndpoint, payload);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
const getLobby = async (gid) => request('getLobby', { gid });
|
const joinPrivateGame = async (payload) => {
|
||||||
|
const response = await API.post(subgroupMembershipsEndpoint, payload);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
const getTeam = async (tid) => request('getTeam', { tid });
|
const joinAsAdmin = async (payload) => {
|
||||||
|
const response = await API.post(adminWaitlistEndpoint, payload);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
const createTeam = async (payload) => request('createTeam', payload);
|
const getLobby = async (gid) => {
|
||||||
|
const response = await API.get(`${groupsEndpoint}/${gid}`);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
const joinTeamByCode = async (payload) => request('joinTeamByCode', payload);
|
const getTeam = async (tid) => {
|
||||||
|
const response = await API.get(`${teamsEndpoint}/${tid}`);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
const getMapPoints = async (gid, sgid = null) => request('getMapPoints', { gid, sgid });
|
const createTeam = async (payload) => {
|
||||||
|
const response = await API.post(teamsEndpoint, payload);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
const getCaches = async (gid, sgid = null) => request('getCaches', { gid, sgid });
|
const joinTeamByCode = async (payload) => {
|
||||||
|
const response = await API.post(teamMembersEndpoint, payload);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
const upsertCache = async (payload) => request('upsertCache', payload);
|
const getMapPoints = async (gid, sgid = null) => {
|
||||||
|
let url = `${gameDataEndpoint}/${gid}/caches`;
|
||||||
|
if (sgid !== null) url += `?SGid=${sgid}`;
|
||||||
|
const response = await API.get(url);
|
||||||
|
return response.isSuccess ? response.result : [];
|
||||||
|
};
|
||||||
|
|
||||||
const claimCache = async (payload) => request('claimCache', payload);
|
const getCaches = async (gid, sgid = null) => {
|
||||||
|
let url = `${gameDataEndpoint}/${gid}/caches`;
|
||||||
|
if (sgid !== null) url += `?SGid=${sgid}`;
|
||||||
|
const response = await API.get(url);
|
||||||
|
return response.isSuccess ? response.result : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const upsertCache = async (payload) => {
|
||||||
|
if (payload.cacheId) {
|
||||||
|
const response = await API.put(
|
||||||
|
`${gameDataEndpoint}/${payload.gid}/caches/${payload.cacheId}`,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
}
|
||||||
|
const response = await API.post(
|
||||||
|
`${gameDataEndpoint}/${payload.gid}/caches`,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const claimCache = async (payload) => {
|
||||||
|
const response = await API.put(
|
||||||
|
`${gameDataEndpoint}/${payload.gid}/caches/${payload.cacheId}`,
|
||||||
|
{ClaimedByUid: payload.uid, ClaimedByTid: payload.tid},
|
||||||
|
);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return ---------------------
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getGameTypes,
|
getGameTypes,
|
||||||
|
|||||||
+25
-24
@@ -2,36 +2,37 @@ import {useEffect, useState} from 'react';
|
|||||||
import {isLookingAtCache, isWithinRadius} from '../utils/geoMath';
|
import {isLookingAtCache, isWithinRadius} from '../utils/geoMath';
|
||||||
|
|
||||||
const usePlayerGame = (playerLocation, playerHeading, activeCaches) => {
|
const usePlayerGame = (playerLocation, playerHeading, activeCaches) => {
|
||||||
const [visibleCache, setVisibleCache] = useState(null);
|
// State ----------------------
|
||||||
const [isClaiming, setIsClaiming] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const [visibleCache, setVisibleCache] = useState(null);
|
||||||
if (!playerLocation || playerHeading === null || playerHeading === undefined) {
|
const [isClaiming, setIsClaiming] = useState(false);
|
||||||
setVisibleCache(null);
|
|
||||||
setIsClaiming(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nearbyCache = (activeCaches || []).find((cache) =>
|
// Handlers -------------------
|
||||||
isWithinRadius(playerLocation, cache.coordinates, cache.radius));
|
|
||||||
|
|
||||||
if (!nearbyCache) {
|
useEffect(() => {
|
||||||
setVisibleCache(null);
|
if (!playerLocation || playerHeading === null || playerHeading === undefined) {
|
||||||
setIsClaiming(false);
|
setVisibleCache(null);
|
||||||
return;
|
setIsClaiming(false);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isTargeted = isLookingAtCache(
|
const nearbyCache = (activeCaches || []).find((cache) =>
|
||||||
playerHeading,
|
isWithinRadius(playerLocation, cache.coordinates, cache.radius));
|
||||||
playerLocation,
|
|
||||||
nearbyCache.coordinates,
|
|
||||||
);
|
|
||||||
|
|
||||||
setVisibleCache(nearbyCache);
|
if (!nearbyCache) {
|
||||||
setIsClaiming(isTargeted);
|
setVisibleCache(null);
|
||||||
}, [playerLocation, playerHeading, activeCaches]);
|
setIsClaiming(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return { visibleCache, isClaiming, setIsClaiming };
|
const isTargeted = isLookingAtCache(playerHeading, playerLocation, nearbyCache.coordinates);
|
||||||
|
setVisibleCache(nearbyCache);
|
||||||
|
setIsClaiming(isTargeted);
|
||||||
|
}, [playerLocation, playerHeading, activeCaches]);
|
||||||
|
|
||||||
|
// Return ---------------------
|
||||||
|
|
||||||
|
return {visibleCache, isClaiming, setIsClaiming};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default usePlayerGame;
|
export default usePlayerGame;
|
||||||
|
|||||||
Reference in new issue
Block a user