// React
import { useState, useEffect, useRef } from 'react';
import { ScrollView, View, Text, Image, Alert, StyleSheet, TouchableOpacity, TouchableHighlight } from 'react-native';
import MapView, { Marker, Circle, Polygon } from 'react-native-maps';
// Expo
import { useRouter } from 'expo-router';
// Components
import CustomImage from '../components/image';
import CustomTextInput from '../components/input';
import Stat from '../components/stat';
import Collapsible from 'react-native-collapsible';
import LinearGradient from 'react-native-linear-gradient';
// Other
import { useSocket } from '../context/socketContext';
import { useTeamContext } from '../context/teamContext';
import { useTeamConnexion } from '../context/teamConnexionContext';
import { useTimeDifference } from '../hook/useTimeDifference';
import { GameState } from '../util/gameState';
import useGame from '../hook/useGame';
const backgroundColor = '#f5f5f5';
const initialRegion = {latitude: 48.864, longitude: 2.342, latitudeDelta: 0, longitudeDelta: 50} // France centrée sur Paris
const zoneTypes = {
circle: "circle",
polygon: "polygon"
}
export default function Display() {
const arrowUp = require('../assets/images/arrow.png');
const [collapsibleState, setCollapsibleState] = useState(true);
const [bottomContainerHeight, setBottomContainerHeight] = useState(0);
const router = useRouter();
const {SERVER_URL} = useSocket();
const {gameSettings, zoneType, zoneExtremities, nextZoneDate, isShrinking, location, startLocationTracking, stopLocationTracking, gameState} = useTeamContext();
const {loggedIn, logout, loading} = useTeamConnexion();
const {sendCurrentPosition, capture, enemyLocation, enemyName, startingArea, captureCode, name, ready, captured, lastSentLocation, locationSendDeadline, teamId, outOfZone, outOfZoneDeadline, distance, startDate, finishDate, nCaptures, nSentLocation} = useGame();
const [enemyCaptureCode, setEnemyCaptureCode] = useState("");
const [timeLeftSendLocation] = useTimeDifference(locationSendDeadline, 1000);
const [timeLeftNextZone] = useTimeDifference(nextZoneDate, 1000);
const [timeLeftOutOfZone] = useTimeDifference(outOfZoneDeadline, 1000);
const [timeSinceStart] = useTimeDifference(startDate, 1000);
const [avgSpeed, setAvgSpeed] = useState(0); // Speed in m/s
const [enemyImageURI, setEnemyImageURI] = useState("../assets/images/missing_image.jpg");
const [captureStatus, setCaptureStatus] = useState(0); // 0 : no capture | 1 : waiting for response from server | 2 : capture failed | 3 : capture succesful
const captureStatusColor = {0: "#777", 1: "#FFA500", 2: "#FF6B6B", 3: "#81C784"};
const mapRef = useRef(null);
const [centerMap, setCenterMap] = useState(true);
// Router
useEffect(() => {
if (!loading) {
if (!loggedIn) {
router.replace("/");
}
}
}, [loggedIn, loading]);
// Activating geolocation tracking
useEffect(() => {
if (loggedIn && (gameState == GameState.SETUP || gameState == GameState.PLAYING || gameState == GameState.PLACEMENT) && !captured) {
startLocationTracking();
} else {
stopLocationTracking();
}
}, [loggedIn, gameState, captured]);
// Refresh the image
useEffect(() => {
setEnemyImageURI(`${SERVER_URL}/photo/enemy?team=${teamId}&t=${new Date().getTime()}`);
}, [enemyName, teamId]);
// Capture state update
useEffect(() => {
if (captureStatus == 2 || captureStatus == 3) {
const timeout = setTimeout(() => {
setCaptureStatus(0);
}, 3000);
return () => clearTimeout(timeout);
}
}, [captureStatus]);
// Center the map on user position
useEffect(() => {
if (centerMap && mapRef.current && location) {
mapRef.current.animateToRegion({latitude: location[0], longitude: location[1], latitudeDelta: 0, longitudeDelta: 0.02}, 1000);
}
}, [centerMap, mapRef, location]);
// Update the average speed
useEffect(() => {
const time = finishDate ? (finishDate - startDate) : timeSinceStart;
setAvgSpeed(distance/time);
}, [distance, finishDate, timeSinceStart]);
function toggleCollapsible() {
setCollapsibleState(!collapsibleState);
};
function handleCapture() {
if (captureStatus != 1) {
setCaptureStatus(1);
capture(enemyCaptureCode)
.then((response) => {
if (response.hasCaptured) {
setCaptureStatus(3);
} else {
setCaptureStatus(2);
}
})
.catch(() => {
Alert.alert("Échec", "La connexion au serveur a échoué.");
setCaptureStatus(2);
});
setEnemyCaptureCode("");
}
}
function formatTimeMinutes(time) {
// time is in seconds
if (!Number.isInteger(time)) return "Inconnue";
if (time < 0) time = 0;
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return String(minutes).padStart(2,"0") + ":" + String(seconds).padStart(2,"0");
}
function formatTimeHours(time) {
// time is in seconds
if (!Number.isInteger(time)) return "Inconnue";
if (time < 0) time = 0;
const hours = Math.floor(time / 3600);
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return String(hours).padStart(2,"0") + ":" + String(minutes).padStart(2,"0") + ":" + String(seconds).padStart(2,"0");
}
function circle(color, circle) {
return (
);
}
const Logout = () => {
return (
);
}
const Settings = () => {
return (
Alert.alert("Settings")}>
);
}
const TeamName = () => {
return(
{(name ?? "Indisponible")}
);
}
const GameLog = () => {
return (
{ gameState == GameState.SETUP && Préparation de la partie}
{ gameState == GameState.PLACEMENT && Phase de placement}
{ gameState == GameState.PLAYING && !outOfZone && La partie est en cours}
{ gameState == GameState.PLAYING && outOfZone && Hors zone (pénalité dans {formatTimeMinutes(-timeLeftOutOfZone)})}
{ gameState == GameState.FINISHED && La partie est terminée}
);
}
const TimeBeforeNextZone = () => {
return (
{isShrinking ? "Réduction de la zone" : "Durée de la zone"}
{formatTimeMinutes(-timeLeftNextZone)}
);
}
const TimeBeforeNextPosition = () => {
return (
Position envoyée dans
{formatTimeMinutes(-timeLeftSendLocation)}
);
}
const Timers = () => {
return (
{ TimeBeforeNextZone() }
{ TimeBeforeNextPosition() }
);
}
const Ready = () => {
return (
{ready ? "Placé" : "Non placé"}
);
}
const CapturedMessage = () => {
return (
{gameSettings?.capturedMessage || "Vous avez été éliminé..."}
);
}
const EndGameMessage = () => {
return (
{captured && {gameSettings?.loserEndGameMessage || "Vous avez perdu..."}}
{!captured && {gameSettings?.winnerEndGameMessage || "Vous avez gagné !"}}
);
}
const Zones = () => {
switch (zoneType) {
case zoneTypes.circle:
return (
{ zoneExtremities.begin && }
{ zoneExtremities.end && }
);
case zoneTypes.polygon:
return (
{ zoneExtremities.begin && }
{ zoneExtremities.end && }
);
default:
return null;
}
}
const Map = () => {
return (
setCenterMap(false)}>
{ gameState == GameState.PLACEMENT && startingArea && circle("0, 0, 255", startingArea)}
{ gameState == GameState.PLAYING && zoneExtremities && }
{ location &&
}
{ gameState == GameState.PLAYING && lastSentLocation &&
}
{ gameState == GameState.PLAYING && enemyLocation &&
}
);
}
const UpdatePositionButton = () => {
return (
);
}
const CenterMapButton = () => {
return (
setCenterMap(true)}>
);
}
const LayerButton = () => {
return(
Alert.alert("Layer")}>
);
}
const CollapsibleButton = () => {
return (
);
}
const TeamCaptureCode = () => {
return (
Code de {(name ?? "Indisponible")} : {String(captureCode).padStart(4,"0")}
);
}
const ChasedTeamImage = () => {
return (
{"Cible (" + (enemyName ?? "Indisponible") + ")"}
);
}
const CaptureCode = () => {
return (
);
}
const CaptureButton = () => {
return (
);
}
const Stats = () => {
return (
{(distance / 1000).toFixed(1)}km
{formatTimeHours((finishDate ? Math.floor((finishDate - startDate) / 1000) : timeSinceStart))}
{(avgSpeed*3.6).toFixed(1)}km/h
{nCaptures}
{nSentLocation}
);
}
return (
{ Logout() }
{ false && Settings() }
{ TeamName() }
{ GameLog() }
{ gameState == GameState.PLACEMENT &&
Ready()
}
{ gameState == GameState.PLAYING && !captured &&
Timers()
}
{ gameState == GameState.PLAYING && captured &&
CapturedMessage()
}
{ gameState == GameState.FINISHED &&
EndGameMessage()
}
{setBottomContainerHeight(event.nativeEvent.layout.height)}}>
{ Map() }
{ !centerMap && CenterMapButton() }
{ false && gameState == GameState.PLAYING && !captured &&
{ LayerButton() }
}
{ gameState == GameState.PLAYING && !captured &&
{ UpdatePositionButton() }
}
{ gameState == GameState.PLAYING && !captured &&
{ CollapsibleButton() }
{ TeamCaptureCode() }
{ ChasedTeamImage() }
{ CaptureCode() }
{ CaptureButton() }
{ Stats() }
}
);
}
const styles = StyleSheet.create({
globalContainer: {
backgroundColor: backgroundColor,
flex: 1,
},
topContainer: {
width: '100%',
alignItems: 'center',
padding: 15,
},
topheadContainer: {
width: "100%",
flexDirection: "row",
justifyContent: 'space-between'
},
teamNameContainer: {
width: '100%',
alignItems: 'center',
justifyContent: 'center'
},
logContainer: {
width: '100%',
alignItems: 'center',
justifyContent: 'center',
marginTop: 15
},
gameState: {
borderWidth: 2,
borderRadius: 10,
width: "100%",
backgroundColor: 'white',
fontSize: 20,
padding: 10,
},
gameStateOutOfZone: {
borderWidth: 2,
borderRadius: 10,
width: "100%",
backgroundColor: 'white',
fontSize: 20,
padding: 10,
borderColor: 'red'
},
timersContainer: {
width: '100%',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
marginTop: 15
},
zoneTimerContainer: {
width: "50%",
alignItems: 'center',
justifyContent: 'center',
},
positionTimerContainer: {
width: "50%",
alignItems: 'center',
justifyContent: 'center',
},
readyIndicator: {
width: "100%",
maxWidth: 240,
height: 61,
alignItems: 'center',
justifyContent: 'center',
padding: 3,
borderRadius: 10
},
bottomContainer: {
flex: 1,
alignItems: 'center'
},
mapContainer: {
flex: 1,
width: '100%',
borderTopLeftRadius: 30,
borderTopRightRadius: 30,
overflow: 'hidden',
},
outerDrawerContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
alignItems: 'center'
},
innerDrawerContainer: {
width: "100%",
alignItems: 'center',
backgroundColor: backgroundColor,
borderTopLeftRadius: 30,
borderTopRightRadius: 30,
overflow: 'hidden',
},
collapsibleButton: {
justifyContent: 'center',
alignItems: 'center',
width: "100%",
height: 45
},
collapsibleWindow: {
width: "100%",
alignItems: 'center',
justifyContent: 'center',
backgroundColor: backgroundColor,
},
collapsibleContent: {
paddingHorizontal: 15,
alignItems: 'center'
},
centerMapContainer: {
position: 'absolute',
right: 20,
top: 20,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: 'white',
borderWidth: 2,
borderColor: 'black',
alignItems: 'center',
justifyContent: 'center',
},
toolBarLeft: {
position: 'absolute',
left: 30,
bottom: 80
},
toolBarRight: {
position: 'absolute',
right: 30,
bottom: 80
},
updatePositionContainer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'white',
borderWidth: 4,
borderColor: 'black',
alignItems: 'center',
justifyContent: 'center',
},
layerContainer: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: 'white',
borderWidth: 4,
borderColor: 'black',
alignItems: 'center',
justifyContent: 'center',
},
imageContainer: {
width: "100%",
alignItems: "center",
justifyContent: "center",
marginTop: 15
},
actionsContainer: {
flexDirection: "row",
width: "100%",
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 15
},
actionsLeftContainer: {
flexGrow: 1,
alignItems: 'center',
justifyContent: 'center',
marginRight: 15
},
actionsRightContainer: {
width: 100,
alignItems: 'center',
justifyContent: 'center'
},
button: {
borderRadius: 12,
width: '100%',
height: 75,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#444'
},
});