diff --git a/App.js b/App.js index 1d85452..a3f4d8d 100644 --- a/App.js +++ b/App.js @@ -16,6 +16,7 @@ import GlobalEventsScreen from "./src/components/screens/global/GlobalEventsScre import GlobalMapScreen from "./src/components/screens/global/GlobalMapScreen"; import GlobalCacheViewScreen from "./src/components/screens/global/GlobalCacheViewScreen"; import GlobalLeaderboardScreen from "./src/components/screens/global/GlobalLeaderboardScreen"; +import GlobalProfileScreen from "./src/components/screens/global/GlobalProfileScreen"; import {loadSession} from "./src/hooks/SessionStore"; const RootStack = createNativeStackNavigator(); @@ -63,6 +64,10 @@ const GameStack = () => ( name="GlobalLeaderboardScreen" component={GlobalLeaderboardScreen} /> + ); @@ -100,6 +105,10 @@ const AdminStack = () => ( name="GlobalLeaderboardScreen" component={GlobalLeaderboardScreen} /> + ); diff --git a/assets/images/default-pfp.png b/assets/images/default-pfp.png new file mode 100644 index 0000000..3603454 Binary files /dev/null and b/assets/images/default-pfp.png differ diff --git a/src/components/API/api.json b/src/components/API/api.json new file mode 100644 index 0000000..1d10e9f --- /dev/null +++ b/src/components/API/api.json @@ -0,0 +1,6 @@ +{ + "GLOBAL_API_BASE_URL": "https://mark0s.com/geoquest/v1", + "GLOBAL_API_KEY": "v5ctyr", + "DEFAULT_GLOBAL_PROFILE_IMAGE_URL": "https://images.generated.photos/eL1-OlKDqGf1IaL_b2O8aSj7osDX_eFVHZEoJ0f3ZV0/rs:fit:256:256/czM6Ly9pY29uczgu/Z3Bob3Rvcy1wcm9k/LnBob3Rvcy92M18w/NzYwNjc0LmpwZw.jpg" +} + diff --git a/src/components/layout/BottomNavbar.js b/src/components/layout/BottomNavbar.js index bd17f0d..8fbed0b 100644 --- a/src/components/layout/BottomNavbar.js +++ b/src/components/layout/BottomNavbar.js @@ -92,10 +92,22 @@ const BottomNavbar = ({navigation, routeName, routeParams}) => { ...(inGame ? [{label: 'Leave', route: '__leave__', onPress: handleLeaveGame}] : []), ]; + const globalEventParams = session.currentGlobalEventId + ? {eventId: session.currentGlobalEventId} + : undefined; + const globalTabs = [ {label: 'Events', route: 'GlobalEventsScreen', onPress: () => navigation.navigate('GlobalEventsScreen')}, - {label: 'Map', route: 'GlobalMapScreen', onPress: () => navigation.navigate('GlobalMapScreen')}, - {label: 'Leaderboard', route: 'GlobalLeaderboardScreen', onPress: () => navigation.navigate('GlobalLeaderboardScreen')}, + { + label: 'Map', + route: 'GlobalMapScreen', + onPress: () => navigation.navigate('GlobalMapScreen', globalEventParams), + }, + { + label: 'Leaderboard', + route: 'GlobalLeaderboardScreen', + onPress: () => navigation.navigate('GlobalLeaderboardScreen', globalEventParams), + }, {label: 'Exit', route: '__exit_global__', onPress: handleExitGlobal}, ]; diff --git a/src/components/layout/Screen.js b/src/components/layout/Screen.js index 8c60b9f..b424220 100644 --- a/src/components/layout/Screen.js +++ b/src/components/layout/Screen.js @@ -25,12 +25,14 @@ const Screen = ({children, style, showBack = false}) => { GlobalMapScreen: 'Global Map', GlobalCacheViewScreen: 'Cache Details', GlobalLeaderboardScreen: 'Global Leaderboard', + GlobalProfileScreen: 'Global Profile', }; // State ---------------------- const routeName = route.name; const isAuthScreen = routeName === 'LoginScreen' || routeName === 'RegisterScreen'; + const isGlobalRoute = routeName.startsWith('Global'); const pageTitle = titleMap[routeName] || 'Treasure Hunt'; // Handlers ------------------- @@ -50,6 +52,7 @@ const Screen = ({children, style, showBack = false}) => { )} diff --git a/src/components/layout/TopNavbar.js b/src/components/layout/TopNavbar.js index 934120a..8504236 100644 --- a/src/components/layout/TopNavbar.js +++ b/src/components/layout/TopNavbar.js @@ -2,7 +2,7 @@ import {Pressable, StyleSheet, Text, View} from 'react-native'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {clearSession} from '../../hooks/SessionStore'; -const TopNavbar = ({title, showBack, navigation}) => { +const TopNavbar = ({title, showBack, showProfile = false, navigation}) => { // Initialisation ------------ const insets = useSafeAreaInsets(); @@ -23,6 +23,10 @@ const TopNavbar = ({title, showBack, navigation}) => { }); }; + const handleProfile = () => { + navigation.navigate('GlobalProfileScreen'); + }; + // View ----------------------- return ( @@ -36,6 +40,11 @@ const TopNavbar = ({title, showBack, navigation}) => { {title} + {showProfile ? ( + + Profile + + ) : null} Logout @@ -61,8 +70,10 @@ const styles = StyleSheet.create({ alignItems: 'flex-start', }, rightSlot: { - width: 72, + width: 148, alignItems: 'flex-end', + flexDirection: 'row', + justifyContent: 'flex-end', }, iconButton: { paddingVertical: 8, @@ -80,6 +91,16 @@ const styles = StyleSheet.create({ fontWeight: '700', color: '#ffffff', }, + profileButton: { + paddingVertical: 6, + paddingHorizontal: 8, + marginRight: 4, + }, + profileText: { + color: '#ffffff', + fontSize: 13, + fontWeight: '600', + }, logoutButton: { paddingVertical: 6, paddingHorizontal: 8, diff --git a/src/components/screens/MapScreen.js b/src/components/screens/MapScreen.js index 5455674..afdd1b8 100644 --- a/src/components/screens/MapScreen.js +++ b/src/components/screens/MapScreen.js @@ -12,10 +12,12 @@ import ClaimTimerView from '../gameplay/ClaimTimerView'; import PlayerMapView from '../gameplay/PlayerMapView'; import Card from '../UI/Card'; import useGameHook from '../../hooks/useGameHook'; +import useGlobalHook from '../../hooks/useGlobalHook'; import usePlayerGame from '../../hooks/usePlayerGame'; import { clearPendingOrgJoin, getSession, + setGlobalUserId, setPendingAdmin, setPendingOrgJoin, setSelectedCache, @@ -34,6 +36,7 @@ const MapScreen = ({navigation, route}) => { const session = getSession(); const isBusiness = Boolean(session.isBusiness); const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, joinTeamByCode, createPrivateGame, getGroups, getLobby, getUser, getSubgroups, getGroupByOrgCode} = useGameHook(); + const {ensureGlobalUser, isGlobalApiReady} = useGlobalHook(); // Stable ref to prevent infinite re-render loops const getCachesRef = useRef(getCaches); @@ -107,6 +110,23 @@ const MapScreen = ({navigation, route}) => { ? {...userLocation, latitudeDelta: 0.01, longitudeDelta: 0.01} : DEFAULT_REGION; + useEffect(() => { + if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) return; + + if (session.currentGlobalEventId && session.currentGlobalPlayerId) { + navigation.replace('GlobalMapScreen', {eventId: session.currentGlobalEventId}); + return; + } + + navigation.replace('GlobalEventsScreen'); + }, [ + navigation, + session.currentGameMode, + session.currentGlobalEventId, + session.currentGlobalPlayerId, + session.isBusiness, + ]); + useEffect(() => { if (!session.currentGid) return; getLobbyRef.current(session.currentGid).then(setGroupInfo); @@ -291,7 +311,16 @@ const MapScreen = ({navigation, route}) => { setQuickJoiningCode(null); }; - const handleEnterGlobal = () => { + const handleEnterGlobal = async () => { + if (isGlobalApiReady()) { + const privateUser = await getUser(session.currentUid); + const globalUser = await ensureGlobalUser(session.currentUid, privateUser || null); + if (!globalUser?.UserID) { + setError('Could not initialise your global account. Please try again.'); + return; + } + setGlobalUserId(globalUser.UserID); + } setSessionMode(GAME_MODE.GLOBAL); navigation.navigate('GlobalEventsScreen'); }; diff --git a/src/components/screens/global/GlobalCacheViewScreen.js b/src/components/screens/global/GlobalCacheViewScreen.js index 327d607..257244c 100644 --- a/src/components/screens/global/GlobalCacheViewScreen.js +++ b/src/components/screens/global/GlobalCacheViewScreen.js @@ -1,72 +1,32 @@ -import {useEffect, useState} from "react"; -import {ActivityIndicator, Alert, StyleSheet, Text, View} from "react-native"; +import {useEffect} from "react"; +import {StyleSheet, Text, View} from "react-native"; import Screen from "../../layout/Screen"; import {Button, ButtonTray} from "../../UI/Button"; import Card from "../../UI/Card"; -import useGlobalHook from "../../../hooks/useGlobalHook"; import {getSession} from "../../../hooks/SessionStore"; import {GAME_MODE} from "../../../utils/gameConstants"; const GlobalCacheViewScreen = ({ navigation, route }) => { // Initialisations --------------------- - const { cache, isFound: initIsFound, onFindLogged } = route.params; - const { logFind } = useGlobalHook(); + const { cache, isFound: initIsFound } = route.params; const session = getSession(); - // State ------------------------------- - - const [isFound, setIsFound] = useState(initIsFound); - const [isLogging, setIsLogging] = useState(false); - // Handlers ---------------------------- - const handleLogFind = async () => { - if (!session.currentGlobalPlayerId) { - Alert.alert( - "Not joined", - "You must join the event before logging a find.", - ); - return; - } - - Alert.alert( - "Log Discovery", - `Record that you found "${cache.CacheName}" and earn ${cache.CachePoints ?? 0} points?`, - [ - { text: "Cancel", style: "cancel" }, - { - text: "Log Find", - onPress: async () => { - setIsLogging(true); - const result = await logFind({ - FindPlayerID: session.currentGlobalPlayerId, - FindCacheID: cache.CacheID, - FindDatetime: new Date().toISOString(), - }); - setIsLogging(false); - if (result) { - setIsFound(true); - if (onFindLogged) onFindLogged(); - Alert.alert( - "Discovery logged!", - `+${cache.CachePoints ?? 0} points earned.`, - ); - } else { - Alert.alert("Error", "Could not log find. Please try again."); - } - }, - }, - ], - ); - }; - useEffect(() => { if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) { navigation.replace("MapScreen"); } }, [navigation, session.currentGameMode, session.isBusiness]); + const handleBackToMap = () => { + navigation.navigate("GlobalMapScreen", { + eventId: cache.CacheEventID ?? session.currentGlobalEventId, + selectedCacheId: cache.CacheID, + }); + }; + // View -------------------------------- return ( @@ -78,11 +38,11 @@ const GlobalCacheViewScreen = ({ navigation, route }) => { - {isFound ? "Already Found" : "Not Yet Found"} + {initIsFound ? "Already Found" : "Not Yet Found"} @@ -104,23 +64,17 @@ const GlobalCacheViewScreen = ({ navigation, route }) => { {cache.CacheClue} ) : null} + + {!initIsFound ? ( + + Claim this cache from the Global Map while within 30m and facing it. + + ) : null} - {isLogging ? ( - - ) : ( - - {!isFound && ( -