added games screen

games screen added to bottom navbar. Now user can go to games screen and select private or public game type
This commit is contained in:
suddenlyoranges committed 2026-04-08 21:03:03 +01:00
1 parent 93fb30a7bb
commit 4d5eac38b7
5 files changed
+463 -162

No files matched your search

+91 -55
View File
@@ -1,8 +1,8 @@
import {useEffect, useState} from "react";
import {ActivityIndicator, View} from "react-native";
import {NavigationContainer} from "@react-navigation/native";
import {createNativeStackNavigator} from "@react-navigation/native-stack";
import {SafeAreaProvider} from "react-native-safe-area-context";
import { useEffect, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { SafeAreaProvider } from "react-native-safe-area-context";
import LoginScreen from "./src/components/screens/LoginScreen";
import RegisterScreen from "./src/components/screens/RegisterScreen";
import TeamScreen from "./src/components/screens/TeamScreen";
@@ -11,7 +11,8 @@ import LeaderboardScreen from "./src/components/screens/LeaderboardScreen";
import GameSettingsScreen from "./src/components/screens/GameSettingsScreen";
import PlayersScreen from "./src/components/screens/PlayersScreen";
import ExpandedMapScreen from "./src/components/screens/ExpandedMapScreen";
import {loadSession} from "./src/hooks/SessionStore";
import GameSelectionScreen from "./src/components/screens/GameSelectionScreen";
import { loadSession } from "./src/hooks/SessionStore";
const RootStack = createNativeStackNavigator();
const AuthStackNav = createNativeStackNavigator();
@@ -19,71 +20,106 @@ const GameStackNav = createNativeStackNavigator();
const AdminStackNav = createNativeStackNavigator();
const screenOptions = {
headerShown: false,
contentStyle: {backgroundColor: '#ffffff'},
headerShown: false,
contentStyle: { backgroundColor: "#ffffff" },
};
const AuthStack = () => (
<AuthStackNav.Navigator screenOptions={{...screenOptions, keyboardHandlingEnabled: false}}>
<AuthStackNav.Screen name="LoginScreen" component={LoginScreen}/>
<AuthStackNav.Screen name="RegisterScreen" component={RegisterScreen}/>
</AuthStackNav.Navigator>
<AuthStackNav.Navigator
screenOptions={{ ...screenOptions, keyboardHandlingEnabled: false }}
>
<AuthStackNav.Screen name="LoginScreen" component={LoginScreen} />
<AuthStackNav.Screen name="RegisterScreen" component={RegisterScreen} />
</AuthStackNav.Navigator>
);
const GameStack = () => (
<GameStackNav.Navigator screenOptions={screenOptions}>
<GameStackNav.Screen name="MapScreen" component={MapScreen}/>
<GameStackNav.Screen name="TeamScreen" component={TeamScreen}/>
<GameStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
<GameStackNav.Screen name="PlayersScreen" component={PlayersScreen}/>
<GameStackNav.Screen name="ExpandedMapScreen" component={ExpandedMapScreen}/>
</GameStackNav.Navigator>
<GameStackNav.Navigator screenOptions={screenOptions}>
<GameStackNav.Screen
name="GameSelectionScreen"
component={GameSelectionScreen}
/>
<GameStackNav.Screen name="MapScreen" component={MapScreen} />
<GameStackNav.Screen name="TeamScreen" component={TeamScreen} />
<GameStackNav.Screen
name="LeaderboardScreen"
component={LeaderboardScreen}
/>
<GameStackNav.Screen name="PlayersScreen" component={PlayersScreen} />
<GameStackNav.Screen
name="ExpandedMapScreen"
component={ExpandedMapScreen}
/>
</GameStackNav.Navigator>
);
const AdminStack = () => (
<AdminStackNav.Navigator screenOptions={screenOptions}>
<AdminStackNav.Screen name="GameSettingsScreen" component={GameSettingsScreen}/>
<AdminStackNav.Screen name="MapScreen" component={MapScreen}/>
<AdminStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
<AdminStackNav.Screen name="PlayersScreen" component={PlayersScreen}/>
<AdminStackNav.Screen name="ExpandedMapScreen" component={ExpandedMapScreen}/>
<AdminStackNav.Screen name="TeamScreen" component={TeamScreen}/>
</AdminStackNav.Navigator>
<AdminStackNav.Navigator screenOptions={screenOptions}>
<AdminStackNav.Screen
name="GameSelectionScreen"
component={GameSelectionScreen}
/>
<AdminStackNav.Screen
name="GameSettingsScreen"
component={GameSettingsScreen}
/>
<AdminStackNav.Screen name="MapScreen" component={MapScreen} />
<AdminStackNav.Screen
name="LeaderboardScreen"
component={LeaderboardScreen}
/>
<AdminStackNav.Screen name="PlayersScreen" component={PlayersScreen} />
<AdminStackNav.Screen
name="ExpandedMapScreen"
component={ExpandedMapScreen}
/>
<AdminStackNav.Screen name="TeamScreen" component={TeamScreen} />
</AdminStackNav.Navigator>
);
export const App = () => {
// Load persisted session from AsyncStorage before choosing initial route
const [loading, setLoading] = useState(true);
const [initialRoute, setInitialRoute] = useState('Auth');
// Load persisted session from AsyncStorage before choosing initial route
const [loading, setLoading] = useState(true);
const [initialRoute, setInitialRoute] = useState("Auth");
useEffect(() => {
loadSession().then((session) => {
if (session.currentUid) {
setInitialRoute(session.isAcceptedAdmin ? 'Admin' : 'Game');
}
setLoading(false);
});
}, []);
if (loading) {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff'}}>
<ActivityIndicator size="large" />
</View>
);
}
useEffect(() => {
loadSession().then((session) => {
if (session.currentUid) {
setInitialRoute(session.isAcceptedAdmin ? "Admin" : "Game");
}
setLoading(false);
});
}, []);
if (loading) {
return (
<SafeAreaProvider>
<NavigationContainer>
<RootStack.Navigator initialRouteName={initialRoute} screenOptions={screenOptions}>
<RootStack.Screen name="Auth" component={AuthStack}/>
<RootStack.Screen name="Game" component={GameStack}/>
<RootStack.Screen name="Admin" component={AdminStack}/>
</RootStack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff",
}}
>
<ActivityIndicator size="large" />
</View>
);
}
return (
<SafeAreaProvider>
<NavigationContainer>
<RootStack.Navigator
initialRouteName={initialRoute}
screenOptions={screenOptions}
>
<RootStack.Screen name="Auth" component={AuthStack} />
<RootStack.Screen name="Game" component={GameStack} />
<RootStack.Screen name="Admin" component={AdminStack} />
</RootStack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
};
export default App;
+149 -98
View File
@@ -1,113 +1,164 @@
import {Alert, Pressable, StyleSheet, Text, View} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {clearGameSession, getSession} from '../../hooks/SessionStore';
import API, {API_BASE_URL} from '../API/API';
import { Alert, Pressable, StyleSheet, Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { clearGameSession, getSession } from "../../hooks/SessionStore";
import API, { API_BASE_URL } from "../API/API";
const BottomNavbar = ({navigation, routeName}) => {
// Initialisation ------------
const BottomNavbar = ({ navigation, routeName }) => {
// Initialisation ------------
const insets = useSafeAreaInsets();
const session = getSession();
const insets = useSafeAreaInsets();
const session = getSession();
const adminTabs = [
{label: 'Settings', route: 'GameSettingsScreen', onPress: () => navigation.navigate('GameSettingsScreen')},
{label: 'Map', route: 'MapScreen', onPress: () => navigation.navigate('MapScreen')},
{label: 'Players', route: 'PlayersScreen', onPress: () => navigation.navigate('PlayersScreen')},
{label: 'Leaderboard', route: 'LeaderboardScreen', onPress: () => navigation.navigate('LeaderboardScreen')},
];
const adminTabs = [
{
label: "Settings",
route: "GameSettingsScreen",
onPress: () => navigation.navigate("GameSettingsScreen"),
},
{
label: "Map",
route: "MapScreen",
onPress: () => navigation.navigate("MapScreen"),
},
{
label: "Players",
route: "PlayersScreen",
onPress: () => navigation.navigate("PlayersScreen"),
},
{
label: "Leaderboard",
route: "LeaderboardScreen",
onPress: () => navigation.navigate("LeaderboardScreen"),
},
{
label: "Games",
route: "GameSelectionScreen",
onPress: () => navigation.navigate("GameSelectionScreen"),
},
];
const playerTabs = [
{label: 'Team', route: 'TeamScreen', onPress: () => navigation.navigate('TeamScreen')},
{label: 'Map', route: 'MapScreen', onPress: () => navigation.navigate('MapScreen')},
{label: 'Leaderboard', route: 'LeaderboardScreen', onPress: () => navigation.navigate('LeaderboardScreen')},
{label: 'Leave', route: '__leave__', onPress: () => handleLeaveGame()},
];
const playerTabs = [
{
label: "Team",
route: "TeamScreen",
onPress: () => navigation.navigate("TeamScreen"),
},
{
label: "Map",
route: "MapScreen",
onPress: () => navigation.navigate("MapScreen"),
},
{
label: "Leaderboard",
route: "LeaderboardScreen",
onPress: () => navigation.navigate("LeaderboardScreen"),
},
{
label: "Games",
route: "GameSelectionScreen",
onPress: () => navigation.navigate("GameSelectionScreen"),
},
const tabs = session.isAcceptedAdmin ? adminTabs : playerTabs;
{ label: "Leave", route: "__leave__", onPress: () => handleLeaveGame() },
];
// State ----------------------
// Handlers -------------------
const tabs = session.isAcceptedAdmin ? adminTabs : playerTabs;
const handleLeaveGame = () => {
Alert.alert('Leave Game', 'Are you sure you want to leave this game?', [
{text: 'Cancel', style: 'cancel'},
{
text: 'Leave',
style: 'destructive',
onPress: async () => {
const s = getSession();
const endpoint = `${API_BASE_URL}/subgroup-memberships?Uid=${s.currentUid}&Gid=${s.currentGid}`;
const res = await API.get(endpoint);
if (res.isSuccess && res.result.length > 0) {
await API.delete(`${API_BASE_URL}/subgroup-memberships/${res.result[0].id}`);
}
clearGameSession();
navigation.reset({index: 0, routes: [{name: 'Game'}]});
},
},
]);
};
// State ----------------------
// Handlers -------------------
// View -----------------------
const handleLeaveGame = () => {
Alert.alert("Leave Game", "Are you sure you want to leave this game?", [
{ text: "Cancel", style: "cancel" },
{
text: "Leave",
style: "destructive",
onPress: async () => {
const s = getSession();
const endpoint = `${API_BASE_URL}/subgroup-memberships?Uid=${s.currentUid}&Gid=${s.currentGid}`;
const res = await API.get(endpoint);
if (res.isSuccess && res.result.length > 0) {
await API.delete(
`${API_BASE_URL}/subgroup-memberships/${res.result[0].id}`,
);
}
clearGameSession();
navigation.reset({ index: 0, routes: [{ name: "Game" }] });
},
},
]);
};
return (
<View style={[styles.container, {paddingBottom: insets.bottom}]}>
{tabs.map((tab) => {
const isActive = routeName === tab.route;
const isLeave = tab.route === '__leave__';
return (
<Pressable
key={tab.label}
onPress={tab.onPress}
style={[styles.tab, isActive && styles.tabActive, isLeave && styles.tabLeave]}
>
<Text style={[styles.tabText, isActive && styles.tabTextActive, isLeave && styles.tabTextLeave]}>
{tab.label}
</Text>
</Pressable>
);
})}
</View>
);
// View -----------------------
return (
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
{tabs.map((tab) => {
const isActive = routeName === tab.route;
const isLeave = tab.route === "__leave__";
return (
<Pressable
key={tab.label}
onPress={tab.onPress}
style={[
styles.tab,
isActive && styles.tabActive,
isLeave && styles.tabLeave,
]}
>
<Text
style={[
styles.tabText,
isActive && styles.tabTextActive,
isLeave && styles.tabTextLeave,
]}
>
{tab.label}
</Text>
</Pressable>
);
})}
</View>
);
};
const styles = StyleSheet.create({
container: {
minHeight: 64,
borderTopWidth: 1,
borderTopColor: '#555555',
backgroundColor: '#4a4a4a',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingHorizontal: 8,
},
tab: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 12,
borderRadius: 8,
marginHorizontal: 4,
},
tabActive: {
backgroundColor: '#5c5c5c',
},
tabLeave: {
backgroundColor: 'rgba(220,38,38,0.15)',
},
tabText: {
color: '#d1d5db',
fontSize: 12,
fontWeight: '600',
textAlign: 'center',
},
tabTextActive: {
color: '#ffffff',
},
tabTextLeave: {
color: '#fca5a5',
},
container: {
minHeight: 64,
borderTopWidth: 1,
borderTopColor: "#555555",
backgroundColor: "#4a4a4a",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
paddingHorizontal: 8,
},
tab: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingVertical: 12,
borderRadius: 8,
marginHorizontal: 4,
},
tabActive: {
backgroundColor: "#5c5c5c",
},
tabLeave: {
backgroundColor: "rgba(220,38,38,0.15)",
},
tabText: {
color: "#d1d5db",
fontSize: 12,
fontWeight: "600",
textAlign: "center",
},
tabTextActive: {
color: "#ffffff",
},
tabTextLeave: {
color: "#fca5a5",
},
});
export default BottomNavbar;
+26 -9
View File
@@ -1,7 +1,7 @@
import {StatusBar} from "expo-status-bar";
import {StyleSheet, View} from "react-native";
import {useNavigation, useRoute} from "@react-navigation/native";
import {useSafeAreaInsets} from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, View } from "react-native";
import { useNavigation, useRoute } from "@react-navigation/native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import TopNavbar from "./TopNavbar";
import BottomNavbar from "./BottomNavbar";
@@ -17,6 +17,7 @@ const Screen = ({ children, style, showBack = false }) => {
TeamScreen: "Current Team",
MapScreen: "Map",
GameSettingsScreen: "Game Settings",
GameSelectionScreen: "Games",
PlayersScreen: "Players",
ExpandedMapScreen: "Map View",
};
@@ -24,17 +25,34 @@ const Screen = ({ children, style, showBack = false }) => {
// State ----------------------
const routeName = route.name;
const isAuthScreen = routeName === "LoginScreen" || routeName === "RegisterScreen";
const isAuthScreen =
routeName === "LoginScreen" || routeName === "RegisterScreen";
const pageTitle = titleMap[routeName] || "Treasure Hunt";
// Handlers -------------------
// View -----------------------
return (
<View style={[styles.screen, isAuthScreen && {paddingTop: insets.top, paddingBottom: insets.bottom}]}>
{!isAuthScreen && <TopNavbar title={pageTitle} showBack={showBack} navigation={navigation} />}
<View
style={[
styles.screen,
isAuthScreen && {
paddingTop: insets.top,
paddingBottom: insets.bottom,
},
]}
>
{!isAuthScreen && (
<TopNavbar
title={pageTitle}
showBack={showBack}
navigation={navigation}
/>
)}
<View style={[styles.content, style]}>{children}</View>
{!isAuthScreen && <BottomNavbar navigation={navigation} routeName={routeName} />}
{!isAuthScreen && (
<BottomNavbar navigation={navigation} routeName={routeName} />
)}
<StatusBar style="light" />
</View>
);
@@ -52,4 +70,3 @@ const styles = StyleSheet.create({
});
export default Screen;
@@ -0,0 +1,184 @@
import { ScrollView, StyleSheet, Text, View } from "react-native";
import Screen from "../layout/Screen";
import Card from "../UI/Card";
import { Button, ButtonTray } from "../UI/Button";
import { getSession } from "../../hooks/SessionStore";
const GameSelectionScreen = ({ navigation }) => {
// Initialisations ---------------------
const session = getSession();
const isAdmin = Boolean(session.isAcceptedAdmin);
const privateGames = [
{
id: "active-game",
name: "Private Game",
players: "4 players",
endsIn: "Ends in 2h 15m",
},
];
// State -------------------------------
// Handlers ----------------------------
const handleCreateGame = () => {
if (isAdmin) {
navigation.navigate("GameSettingsScreen");
return;
}
navigation.navigate("TeamScreen");
};
const handleOpenGame = () => {
navigation.navigate("TeamScreen");
};
const handleExplore = () => {
navigation.navigate("MapScreen");
};
// View --------------------------------
return (
<Screen style={styles.screenContent}>
<ScrollView contentContainerStyle={styles.scrollContent}>
<View style={styles.sectionTop}>
<Text style={styles.sectionTitle}>Your Games</Text>
<ButtonTray>
<Button
label={isAdmin ? "Create Game" : "Manage Team"}
onClick={handleCreateGame}
styleButton={styles.primaryButton}
styleLabel={styles.primaryButtonLabel}
/>
</ButtonTray>
</View>
<View style={styles.sectionBlock}>
<Text style={styles.blockTitle}>Private Games</Text>
{privateGames.map((game) => (
<Card key={game.id} style={styles.gameCard}>
<Text style={styles.gameName}>{game.name}</Text>
<Text style={styles.gameMeta}>{game.players}</Text>
<Text style={styles.gameMeta}>{game.endsIn}</Text>
<View style={styles.cardActions}>
<Button
label="Show"
onClick={handleOpenGame}
styleButton={styles.secondaryButton}
styleLabel={styles.secondaryButtonLabel}
/>
</View>
</Card>
))}
</View>
<View style={styles.sectionBlock}>
<Text style={styles.blockTitle}>Global Mode</Text>
<Text style={styles.supportText}>
Explore all visible caches and live activity from the map.
</Text>
<ButtonTray>
<Button
label="Explore"
onClick={handleExplore}
styleButton={styles.neutralButton}
styleLabel={styles.neutralButtonLabel}
/>
</ButtonTray>
</View>
</ScrollView>
</Screen>
);
};
const styles = StyleSheet.create({
screenContent: {
backgroundColor: "#f3f4f6",
paddingHorizontal: 0,
paddingVertical: 0,
},
scrollContent: {
padding: 15,
paddingBottom: 28,
},
sectionTop: {
marginBottom: 16,
gap: 10,
},
sectionTitle: {
fontSize: 22,
fontWeight: "700",
color: "#111827",
},
sectionBlock: {
marginTop: 8,
marginBottom: 10,
},
blockTitle: {
fontSize: 15,
fontWeight: "700",
color: "#374151",
marginBottom: 8,
},
gameCard: {
marginBottom: 10,
paddingHorizontal: 16,
paddingVertical: 14,
borderColor: "#d1d5db",
borderRadius: 10,
},
gameName: {
fontSize: 17,
fontWeight: "700",
color: "#111827",
marginBottom: 4,
},
gameMeta: {
fontSize: 14,
color: "#4b5563",
marginBottom: 2,
},
cardActions: {
marginTop: 12,
maxWidth: 160,
},
supportText: {
fontSize: 14,
color: "#4b5563",
marginBottom: 10,
lineHeight: 20,
},
primaryButton: {
backgroundColor: "#16a34a",
borderColor: "#16a34a",
},
primaryButtonLabel: {
color: "#ffffff",
fontWeight: "600",
},
secondaryButton: {
backgroundColor: "#2563eb",
borderColor: "#2563eb",
flex: 0,
minHeight: 38,
paddingHorizontal: 18,
},
secondaryButtonLabel: {
color: "#ffffff",
fontWeight: "600",
fontSize: 14,
},
neutralButton: {
backgroundColor: "#374151",
borderColor: "#374151",
maxWidth: 180,
},
neutralButtonLabel: {
color: "#ffffff",
fontWeight: "600",
},
});
export default GameSelectionScreen;
+13
View File
@@ -0,0 +1,13 @@
/*import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
const Component = () => {
// Initialisations ---------------------
// State -------------------------------
// Handlers ----------------------------
// View --------------------------------
return (
);
};
const styles = StyleSheet.create({
});
export default Component;
*/