Add global events, map, cache and leaderboard screens

Introduce four new screens for global events: GlobalEventsScreen (lists public events and handles joining), GlobalMapScreen (map/list view of event caches with user location via expo-location and markers using react-native-maps), GlobalCacheViewScreen (cache detail view with ability to log discoveries), and GlobalLeaderboardScreen (rankings and recent finds). Screens use existing global hooks and SessionStore for fetching/joining events, caches, finds and for storing current global player context; they include loading/error states and navigation between screens. Also add simple tab components, styling, and refresh actions.
This commit is contained in:
suddenlyoranges committed 2026-04-09 15:29:29 +01:00
1 parent 6e25fe6807
commit 157d69c21c
5 files changed
+800

No files matched your search

@@ -0,0 +1,140 @@
import { useCallback, useEffect, useState } from "react";
import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
import Screen from "../../layout/Screen";
import { Button, ButtonTray } from "../../UI/Button";
import EventList from "../../../entity/event/EventList";
import useGlobalHook from "../../../hooks/useGlobalHook";
import { getSession, setGlobalSession } from "../../../hooks/SessionStore";
const GlobalEventsScreen = ({ navigation }) => {
// Initialisations ---------------------
const { getPublicEvents, getPlayersByEvent, joinEvent } = useGlobalHook();
const session = getSession();
// State -------------------------------
const [events, setEvents] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState("");
const [joining, setJoining] = useState(null);
// Handlers ----------------------------
const loadEvents = useCallback(async () => {
setIsLoading(true);
setError("");
const data = await getPublicEvents();
setEvents(data);
setIsLoading(false);
}, []);
useEffect(() => {
loadEvents();
}, [loadEvents]);
const handleSelect = async (event) => {
setError("");
setJoining(event.EventID);
// Check if already a player
const players = await getPlayersByEvent(event.EventID);
const existing = (players || []).find(
(p) => p.PlayerUserID === session.currentUid,
);
if (existing) {
setGlobalSession(event.EventID, existing.PlayerID);
navigation.navigate("GlobalMapScreen", { eventId: event.EventID });
} else {
// Join the event
const player = await joinEvent({
PlayerUserID: session.currentUid,
PlayerEventID: event.EventID,
});
if (player) {
setGlobalSession(event.EventID, player.PlayerID);
navigation.navigate("GlobalMapScreen", { eventId: event.EventID });
} else {
setError("Failed to join event. Please try again.");
}
}
setJoining(null);
};
// View --------------------------------
return (
<Screen>
<View style={styles.container}>
<ButtonTray>
<Button label="Refresh" onClick={loadEvents} />
</ButtonTray>
{error ? <Text style={styles.error}>{error}</Text> : null}
{isLoading ? (
<View style={styles.center}>
<ActivityIndicator size="large" />
<Text style={styles.loadingText}>Loading public events…</Text>
</View>
) : events.length === 0 ? (
<Text style={styles.empty}>No public events available.</Text>
) : (
<>
<Text style={styles.hint}>
Tap an event to join and explore its caches.
</Text>
<EventList events={events} onSelect={handleSelect} />
</>
)}
{joining !== null && (
<View style={styles.overlay}>
<ActivityIndicator size="large" color="#fff" />
</View>
)}
</View>
</Screen>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
gap: 12,
},
center: {
flex: 1,
alignItems: "center",
justifyContent: "center",
gap: 10,
},
loadingText: {
color: "#6b7280",
fontSize: 14,
},
empty: {
textAlign: "center",
color: "#9ca3af",
marginTop: 30,
fontSize: 15,
},
hint: {
fontSize: 13,
color: "#6b7280",
},
error: {
color: "#dc2626",
fontSize: 14,
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: "rgba(0,0,0,0.35)",
alignItems: "center",
justifyContent: "center",
},
});
export default GlobalEventsScreen;