mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-04-10 16:30:18 +02:00
Fix background task + socket in services + cleaning
This commit is contained in:
@@ -1,19 +1,16 @@
|
|||||||
// Expo
|
// Expo
|
||||||
import { Slot } from 'expo-router';
|
import { Slot } from 'expo-router';
|
||||||
// Contexts
|
// Contexts
|
||||||
import { SocketProvider } from "../context/socketContext";
|
|
||||||
import { TeamConnexionProvider } from "../context/teamConnexionContext";
|
import { TeamConnexionProvider } from "../context/teamConnexionContext";
|
||||||
import { TeamProvider } from "../context/teamContext";
|
import { TeamProvider } from "../context/teamContext";
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
return (
|
return (
|
||||||
<SocketProvider>
|
<TeamConnexionProvider>
|
||||||
<TeamConnexionProvider>
|
<TeamProvider>
|
||||||
<TeamProvider>
|
<Slot/>
|
||||||
<Slot/>
|
</TeamProvider>
|
||||||
</TeamProvider>
|
</TeamConnexionProvider>
|
||||||
</TeamConnexionProvider>
|
|
||||||
</SocketProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,34 +9,33 @@ import { CustomImage } from '../components/image';
|
|||||||
import { CustomTextInput } from '../components/input';
|
import { CustomTextInput } from '../components/input';
|
||||||
// Contexts
|
// Contexts
|
||||||
import { useTeamConnexion } from "../context/teamConnexionContext";
|
import { useTeamConnexion } from "../context/teamConnexionContext";
|
||||||
import { useTeamContext } from "../context/teamContext";
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import { usePickImage } from '../hook/usePickImage';
|
import { usePickImage } from '../hook/usePickImage';
|
||||||
import { useImageApi } from '../hook/useImageApi';
|
// Services
|
||||||
// Util
|
import { uploadTeamImage } from '../services/imageService';
|
||||||
import { Colors } from '../util/colors';
|
import { getLocationAuthorization, stopLocationTracking } from '../services/backgroundLocationTask';
|
||||||
|
// Constants
|
||||||
|
import { COLORS } from '../constants';
|
||||||
|
|
||||||
const Index = () => {
|
const Index = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {login, loggedIn} = useTeamConnexion();
|
const { login, loggedIn } = useTeamConnexion();
|
||||||
const {getLocationAuthorization, stopLocationTracking} = useTeamContext();
|
|
||||||
const {image, pickImage} = usePickImage();
|
const {image, pickImage} = usePickImage();
|
||||||
const [teamId, setTeamId] = useState("");
|
const [teamId, setTeamId] = useState("");
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const { uploadTeamImage } = useImageApi();
|
|
||||||
|
|
||||||
// Disbaling location tracking
|
// Disbaling location tracking and asking permissions
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stopLocationTracking();
|
stopLocationTracking();
|
||||||
}, [stopLocationTracking]);
|
getLocationAuthorization();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Routeur
|
// Routeur
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
uploadTeamImage(image?.uri);
|
|
||||||
router.replace("/interface");
|
router.replace("/interface");
|
||||||
}
|
}
|
||||||
}, [router, loggedIn, uploadTeamImage, image]);
|
}, [router, loggedIn, image]);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (isSubmitting || !getLocationAuthorization()) return;
|
if (isSubmitting || !getLocationAuthorization()) return;
|
||||||
@@ -53,6 +52,7 @@ const Index = () => {
|
|||||||
const response = await login(teamId);
|
const response = await login(teamId);
|
||||||
|
|
||||||
if (response.isLoggedIn) {
|
if (response.isLoggedIn) {
|
||||||
|
uploadTeamImage(teamId, image?.uri);
|
||||||
setTeamId("");
|
setTeamId("");
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => Alert.alert("Échec", "L'ID d'équipe est inconnu."), 100);
|
setTimeout(() => Alert.alert("Échec", "L'ID d'équipe est inconnu."), 100);
|
||||||
@@ -94,7 +94,7 @@ const styles = StyleSheet.create({
|
|||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingVertical: 20,
|
paddingVertical: 20,
|
||||||
backgroundColor: Colors.background
|
backgroundColor: COLORS.background
|
||||||
},
|
},
|
||||||
transitionContainer: {
|
transitionContainer: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
|
|||||||
@@ -6,21 +6,23 @@ import { useRouter } from 'expo-router';
|
|||||||
// Components
|
// Components
|
||||||
import { CustomMap } from '../components/map';
|
import { CustomMap } from '../components/map';
|
||||||
import { Drawer } from '../components/drawer';
|
import { Drawer } from '../components/drawer';
|
||||||
|
import { TimerMMSS } from '../components/timer';
|
||||||
// Contexts
|
// Contexts
|
||||||
import { useTeamConnexion } from '../context/teamConnexionContext';
|
import { useTeamConnexion } from '../context/teamConnexionContext';
|
||||||
import { useTeamContext } from '../context/teamContext';
|
import { useTeamContext } from '../context/teamContext';
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useGame } from '../hook/useGame';
|
import { useGame } from '../hook/useGame';
|
||||||
import { useTimeDifference } from '../hook/useTimeDifference';
|
import { useTimeDifference } from '../hook/useTimeDifference';
|
||||||
|
// Services
|
||||||
|
import { startLocationTracking } from '../services/backgroundLocationTask';
|
||||||
// Util
|
// Util
|
||||||
import { GameState } from '../util/gameState';
|
|
||||||
import { TimerMMSS } from '../components/timer';
|
|
||||||
import { secondsToMMSS } from '../util/functions';
|
import { secondsToMMSS } from '../util/functions';
|
||||||
import { Colors } from '../util/colors';
|
// Constants
|
||||||
|
import { GAME_STATE, COLORS } from '../constants';
|
||||||
|
|
||||||
const Interface = () => {
|
const Interface = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {teamInfos, messages, nextZoneDate, isShrinking, startLocationTracking, stopLocationTracking, gameState} = useTeamContext();
|
const {teamInfos, messages, nextZoneDate, isShrinking, gameState} = useTeamContext();
|
||||||
const {name, ready, captured, locationSendDeadline, outOfZone, outOfZoneDeadline, hasHandicap, enemyHasHandicap} = teamInfos;
|
const {name, ready, captured, locationSendDeadline, outOfZone, outOfZoneDeadline, hasHandicap, enemyHasHandicap} = teamInfos;
|
||||||
const {loggedIn, logout} = useTeamConnexion();
|
const {loggedIn, logout} = useTeamConnexion();
|
||||||
const {sendCurrentPosition} = useGame();
|
const {sendCurrentPosition} = useGame();
|
||||||
@@ -31,16 +33,16 @@ const Interface = () => {
|
|||||||
|
|
||||||
const statusMessage = useMemo(() => {
|
const statusMessage = useMemo(() => {
|
||||||
switch (gameState) {
|
switch (gameState) {
|
||||||
case GameState.SETUP:
|
case GAME_STATE.SETUP:
|
||||||
return messages?.waiting || "Préparation de la partie";
|
return messages?.waiting || "Préparation de la partie";
|
||||||
case GameState.PLACEMENT:
|
case GAME_STATE.PLACEMENT:
|
||||||
return "Phase de placement";
|
return "Phase de placement";
|
||||||
case GameState.PLAYING:
|
case GAME_STATE.PLAYING:
|
||||||
if (captured) return messages?.captured || "Vous avez été éliminé...";
|
if (captured) return messages?.captured || "Vous avez été éliminé...";
|
||||||
if (!outOfZone) return "La partie est en cours";
|
if (!outOfZone) return "La partie est en cours";
|
||||||
if (!hasHandicap) return `Veuillez retourner dans la zone\nHandicap dans ${secondsToMMSS(-timeLeftOutOfZone)}`;
|
if (!hasHandicap) return `Veuillez retourner dans la zone\nHandicap dans ${secondsToMMSS(-timeLeftOutOfZone)}`;
|
||||||
else return `Veuillez retourner dans la zone\nVotre position est révélée en continue`;
|
else return `Veuillez retourner dans la zone\nVotre position est révélée en continue`;
|
||||||
case GameState.FINISHED:
|
case GAME_STATE.FINISHED:
|
||||||
return `Vous avez ${captured ? (messages?.loser || "perdu...") : (messages?.winner || "gagné !")}`;
|
return `Vous avez ${captured ? (messages?.loser || "perdu...") : (messages?.winner || "gagné !")}`;
|
||||||
default:
|
default:
|
||||||
return "Inconnue";
|
return "Inconnue";
|
||||||
@@ -56,12 +58,8 @@ const Interface = () => {
|
|||||||
|
|
||||||
// Activating geolocation tracking
|
// Activating geolocation tracking
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loggedIn) {
|
startLocationTracking();
|
||||||
startLocationTracking();
|
}, []);
|
||||||
} else {
|
|
||||||
stopLocationTracking();
|
|
||||||
}
|
|
||||||
}, [startLocationTracking, stopLocationTracking, loggedIn]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.globalContainer}>
|
<View style={styles.globalContainer}>
|
||||||
@@ -83,12 +81,12 @@ const Interface = () => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.infoContainer}>
|
<View style={styles.infoContainer}>
|
||||||
{ gameState == GameState.PLACEMENT &&
|
{ gameState == GAME_STATE.PLACEMENT &&
|
||||||
<View style={[styles.readyIndicator, {backgroundColor: ready ? "#3C3" : "#C33"}]}>
|
<View style={[styles.readyIndicator, {backgroundColor: ready ? "#3C3" : "#C33"}]}>
|
||||||
<Text style={{color: '#fff', fontSize: 16}}>{ready ? "Placé" : "Non placé"}</Text>
|
<Text style={{color: '#fff', fontSize: 16}}>{ready ? "Placé" : "Non placé"}</Text>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
{ gameState == GameState.PLAYING && !captured && <Fragment>
|
{ gameState == GAME_STATE.PLAYING && !captured && <Fragment>
|
||||||
<TimerMMSS style={{width: "50%"}} title={isShrinking ? "Réduction de la zone" : "Durée de la zone"} seconds={-timeLeftNextZone} />
|
<TimerMMSS style={{width: "50%"}} title={isShrinking ? "Réduction de la zone" : "Durée de la zone"} seconds={-timeLeftNextZone} />
|
||||||
<TimerMMSS style={{width: "50%"}} title={"Position envoyée dans"} seconds={!hasHandicap ? -timeLeftSendLocation: 0} />
|
<TimerMMSS style={{width: "50%"}} title={"Position envoyée dans"} seconds={!hasHandicap ? -timeLeftSendLocation: 0} />
|
||||||
</Fragment>}
|
</Fragment>}
|
||||||
@@ -99,12 +97,12 @@ const Interface = () => {
|
|||||||
</View>
|
</View>
|
||||||
<View style={styles.bottomContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
|
<View style={styles.bottomContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
|
||||||
<CustomMap/>
|
<CustomMap/>
|
||||||
{ gameState == GameState.PLAYING && !captured && !hasHandicap &&
|
{ gameState == GAME_STATE.PLAYING && !captured && !hasHandicap &&
|
||||||
<TouchableOpacity style={styles.updatePosition} onPress={sendCurrentPosition}>
|
<TouchableOpacity style={styles.updatePosition} onPress={sendCurrentPosition}>
|
||||||
<Image source={require("../assets/images/update_position.png")} style={{width: 40, height: 40}} resizeMode="contain"></Image>
|
<Image source={require("../assets/images/update_position.png")} style={{width: 40, height: 40}} resizeMode="contain"></Image>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
}
|
}
|
||||||
{ gameState == GameState.PLAYING && !captured &&
|
{ gameState == GAME_STATE.PLAYING && !captured &&
|
||||||
<Drawer height={bottomContainerHeight}/>
|
<Drawer height={bottomContainerHeight}/>
|
||||||
}
|
}
|
||||||
</View>
|
</View>
|
||||||
@@ -116,7 +114,7 @@ export default Interface;
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
globalContainer: {
|
globalContainer: {
|
||||||
backgroundColor: Colors.background,
|
backgroundColor: COLORS.background,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
topContainer: {
|
topContainer: {
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ import { CustomImage } from './image';
|
|||||||
import { CustomTextInput } from './input';
|
import { CustomTextInput } from './input';
|
||||||
import { Stat } from './stat';
|
import { Stat } from './stat';
|
||||||
// Contexts
|
// Contexts
|
||||||
|
import { useTeamConnexion } from '../context/teamConnexionContext';
|
||||||
import { useTeamContext } from '../context/teamContext';
|
import { useTeamContext } from '../context/teamContext';
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useTimeDifference } from '../hook/useTimeDifference';
|
import { useTimeDifference } from '../hook/useTimeDifference';
|
||||||
import { useGame } from '../hook/useGame';
|
import { useGame } from '../hook/useGame';
|
||||||
|
// Services
|
||||||
|
import { enemyImage } from '../services/imageService';
|
||||||
// Util
|
// Util
|
||||||
import { GameState } from '../util/gameState';
|
|
||||||
import { Colors } from '../util/colors';
|
|
||||||
import { secondsToHHMMSS } from '../util/functions';
|
import { secondsToHHMMSS } from '../util/functions';
|
||||||
import { useImageApi } from '../hook/useImageApi';
|
// Constants
|
||||||
|
import { GAME_STATE, COLORS } from '../constants';
|
||||||
|
|
||||||
export const Drawer = ({ height }) => {
|
export const Drawer = ({ height }) => {
|
||||||
|
const { teamId } = useTeamConnexion();
|
||||||
const [collapsibleState, setCollapsibleState] = useState(true);
|
const [collapsibleState, setCollapsibleState] = useState(true);
|
||||||
const [enemyCaptureCode, setEnemyCaptureCode] = useState("");
|
const [enemyCaptureCode, setEnemyCaptureCode] = useState("");
|
||||||
const {teamInfos, gameState, startDate} = useTeamContext();
|
const {teamInfos, gameState, startDate} = useTeamContext();
|
||||||
@@ -27,7 +30,6 @@ export const Drawer = ({ height }) => {
|
|||||||
const [timeSinceStart] = useTimeDifference(startDate, 1000);
|
const [timeSinceStart] = useTimeDifference(startDate, 1000);
|
||||||
const [captureStatus, setCaptureStatus] = useState(0); // 0 : no capture | 1 : waiting for response from server | 2 : capture failed | 3 : capture succesful
|
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 captureStatusColor = {0: "#777", 1: "#FFA500", 2: "#FF6B6B", 3: "#81C784"};
|
||||||
const { enemyImage } = useImageApi();
|
|
||||||
|
|
||||||
const avgSpeed = useMemo(() => {
|
const avgSpeed = useMemo(() => {
|
||||||
const hours = (finishDate ? (finishDate - startDate) : timeSinceStart*1000) / 1000 / 3600;
|
const hours = (finishDate ? (finishDate - startDate) : timeSinceStart*1000) / 1000 / 3600;
|
||||||
@@ -76,13 +78,13 @@ export const Drawer = ({ height }) => {
|
|||||||
</TouchableHighlight>
|
</TouchableHighlight>
|
||||||
<Collapsible style={[styles.collapsibleWindow, {height: height - 44}]} title="Collapse" collapsed={collapsibleState}>
|
<Collapsible style={[styles.collapsibleWindow, {height: height - 44}]} title="Collapse" collapsed={collapsibleState}>
|
||||||
<ScrollView contentContainerStyle={styles.collapsibleContent}>
|
<ScrollView contentContainerStyle={styles.collapsibleContent}>
|
||||||
{ gameState == GameState.PLAYING &&
|
{ gameState == GAME_STATE.PLAYING &&
|
||||||
<Text style={{fontSize: 22, fontWeight: "bold", textAlign: "center"}}>Code de {(name ?? "Indisponible")} : {String(captureCode).padStart(4,"0")}</Text>
|
<Text style={{fontSize: 22, fontWeight: "bold", textAlign: "center"}}>Code de {(name ?? "Indisponible")} : {String(captureCode).padStart(4,"0")}</Text>
|
||||||
}
|
}
|
||||||
{ gameState == GameState.PLAYING && !hasHandicap && <Fragment>
|
{ gameState == GAME_STATE.PLAYING && !hasHandicap && <Fragment>
|
||||||
<View style={styles.imageContainer}>
|
<View style={styles.imageContainer}>
|
||||||
<Text style={{fontSize: 15, margin: 5}}>{"Cible (" + (enemyName ?? "Indisponible") + ")"}</Text>
|
<Text style={{fontSize: 15, margin: 5}}>{"Cible (" + (enemyName ?? "Indisponible") + ")"}</Text>
|
||||||
<CustomImage source={enemyImage} canZoom/>
|
<CustomImage source={enemyImage(teamId)} canZoom/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.actionsContainer}>
|
<View style={styles.actionsContainer}>
|
||||||
<View style={styles.actionsLeftContainer}>
|
<View style={styles.actionsLeftContainer}>
|
||||||
@@ -122,7 +124,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
innerDrawerContainer: {
|
innerDrawerContainer: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
backgroundColor: Colors.background,
|
backgroundColor: COLORS.background,
|
||||||
borderTopLeftRadius: 30,
|
borderTopLeftRadius: 30,
|
||||||
borderTopRightRadius: 30,
|
borderTopRightRadius: 30,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
@@ -136,7 +138,7 @@ const styles = StyleSheet.create({
|
|||||||
collapsibleWindow: {
|
collapsibleWindow: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
backgroundColor: Colors.background,
|
backgroundColor: COLORS.background,
|
||||||
},
|
},
|
||||||
collapsibleContent: {
|
collapsibleContent: {
|
||||||
paddingHorizontal: 15,
|
paddingHorizontal: 15,
|
||||||
|
|||||||
@@ -7,20 +7,22 @@ import LinearGradient from 'react-native-linear-gradient';
|
|||||||
import { DashedCircle, InvertedCircle, InvertedPolygon } from './layer';
|
import { DashedCircle, InvertedCircle, InvertedPolygon } from './layer';
|
||||||
// Contexts
|
// Contexts
|
||||||
import { useTeamContext } from '../context/teamContext';
|
import { useTeamContext } from '../context/teamContext';
|
||||||
|
// Hook
|
||||||
|
import { useLocation } from '../hook/useLocation';
|
||||||
// Util
|
// Util
|
||||||
import { GameState } from '../util/gameState';
|
import { ZONE_TYPES, INITIAL_REGIONS, GAME_STATE } from '../constants';
|
||||||
import { ZoneTypes, InitialRegions } from '../util/constants';
|
|
||||||
|
|
||||||
export const CustomMap = () => {
|
export const CustomMap = () => {
|
||||||
const {teamInfos, zoneType, zoneExtremities, location, gameState} = useTeamContext();
|
const { location } = useLocation();
|
||||||
|
const {teamInfos, zoneType, zoneExtremities, gameState} = useTeamContext();
|
||||||
const {enemyLocation, startingArea, lastSentLocation, hasHandicap} = teamInfos;
|
const {enemyLocation, startingArea, lastSentLocation, hasHandicap} = teamInfos;
|
||||||
const [centerMap, setCenterMap] = useState(true);
|
const [centerMap, setCenterMap] = useState(true);
|
||||||
const mapRef = useRef(null);
|
const mapRef = useRef(null);
|
||||||
|
|
||||||
// Center the map on user position
|
// Center the map on user position
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (centerMap && mapRef.current && location) {
|
if (centerMap && location && mapRef.current) {
|
||||||
mapRef.current.animateToRegion({latitude: location[0], longitude: location[1], latitudeDelta: 0, longitudeDelta: 0.02}, 1000);
|
mapRef.current.animateToRegion({...location, latitudeDelta: 0, longitudeDelta: 0.02}, 1000);
|
||||||
}
|
}
|
||||||
}, [centerMap, location]);
|
}, [centerMap, location]);
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ export const CustomMap = () => {
|
|||||||
const latToLatitude = (pos) => ({latitude: pos.lat, longitude: pos.lng});
|
const latToLatitude = (pos) => ({latitude: pos.lat, longitude: pos.lng});
|
||||||
|
|
||||||
const startZone = useMemo(() => {
|
const startZone = useMemo(() => {
|
||||||
if (gameState != GameState.PLACEMENT || !startingArea) return null;
|
if (gameState != GAME_STATE.PLACEMENT || !startingArea) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Circle key="start-zone" center={{ latitude: startingArea.center.lat, longitude: startingArea.center.lng }} radius={startingArea.radius} strokeWidth={2} strokeColor={`rgba(0, 0, 255, 1)`} fillColor={`rgba(0, 0, 255, 0.2)`}/>
|
<Circle key="start-zone" center={{ latitude: startingArea.center.lat, longitude: startingArea.center.lng }} radius={startingArea.radius} strokeWidth={2} strokeColor={`rgba(0, 0, 255, 1)`} fillColor={`rgba(0, 0, 255, 0.2)`}/>
|
||||||
@@ -38,7 +40,7 @@ export const CustomMap = () => {
|
|||||||
}, [gameState, startingArea]);
|
}, [gameState, startingArea]);
|
||||||
|
|
||||||
const gameZone = useMemo(() => {
|
const gameZone = useMemo(() => {
|
||||||
if (gameState !== GameState.PLAYING || !zoneExtremities) return null;
|
if (gameState !== GAME_STATE.PLAYING || !zoneExtremities) return null;
|
||||||
|
|
||||||
const items = [];
|
const items = [];
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ export const CustomMap = () => {
|
|||||||
const strokeWidth = 3;
|
const strokeWidth = 3;
|
||||||
const lineDashPattern = [30, 10];
|
const lineDashPattern = [30, 10];
|
||||||
|
|
||||||
if (zoneType === ZoneTypes.circle) {
|
if (zoneType === ZONE_TYPES.CIRCLE) {
|
||||||
if (zoneExtremities.begin) items.push(
|
if (zoneExtremities.begin) items.push(
|
||||||
<InvertedCircle
|
<InvertedCircle
|
||||||
key="game-zone-begin-circle"
|
key="game-zone-begin-circle"
|
||||||
@@ -68,7 +70,7 @@ export const CustomMap = () => {
|
|||||||
lineDashPattern={lineDashPattern}
|
lineDashPattern={lineDashPattern}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (zoneType === ZoneTypes.polygon) {
|
} else if (zoneType === ZONE_TYPES.POLYGON) {
|
||||||
if (zoneExtremities.begin) items.push(
|
if (zoneExtremities.begin) items.push(
|
||||||
<InvertedPolygon
|
<InvertedPolygon
|
||||||
key="game-zone-begin-poly"
|
key="game-zone-begin-poly"
|
||||||
@@ -95,14 +97,14 @@ export const CustomMap = () => {
|
|||||||
if (!location) return null;
|
if (!location) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Marker key={"current-position-marker"} coordinate={{ latitude: location[0], longitude: location[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position actuelle", "Ceci est votre position")}>
|
<Marker key={"current-position-marker"} coordinate={location} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position actuelle", "Ceci est votre position")}>
|
||||||
<Image source={require("../assets/images/marker/blue.png")} style={styles.markerImage} resizeMode="contain"/>
|
<Image source={require("../assets/images/marker/blue.png")} style={styles.markerImage} resizeMode="contain"/>
|
||||||
</Marker>
|
</Marker>
|
||||||
);
|
);
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
const lastPositionMarker = useMemo(() => {
|
const lastPositionMarker = useMemo(() => {
|
||||||
if (gameState != GameState.PLAYING || !lastSentLocation || hasHandicap) return null;
|
if (gameState != GAME_STATE.PLAYING || !lastSentLocation || hasHandicap) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Marker key={"last-position-marker"} coordinate={{ latitude: lastSentLocation[0], longitude: lastSentLocation[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position envoyée", "Ceci est votre dernière position connue par le serveur")}>
|
<Marker key={"last-position-marker"} coordinate={{ latitude: lastSentLocation[0], longitude: lastSentLocation[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position envoyée", "Ceci est votre dernière position connue par le serveur")}>
|
||||||
@@ -112,7 +114,7 @@ export const CustomMap = () => {
|
|||||||
}, [gameState, hasHandicap, lastSentLocation]);
|
}, [gameState, hasHandicap, lastSentLocation]);
|
||||||
|
|
||||||
const enemyPositionMarker = useMemo(() => {
|
const enemyPositionMarker = useMemo(() => {
|
||||||
if (gameState != GameState.PLAYING || !enemyLocation || hasHandicap) return null;
|
if (gameState != GAME_STATE.PLAYING || !enemyLocation || hasHandicap) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Marker key={"enemy-position-marker"} coordinate={{ latitude: enemyLocation[0], longitude: enemyLocation[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position ennemie", "Ceci est la dernière position de vos ennemis connue")}>
|
<Marker key={"enemy-position-marker"} coordinate={{ latitude: enemyLocation[0], longitude: enemyLocation[1] }} anchor={{ x: 0.33, y: 0.33 }} onPress={() => Alert.alert("Position ennemie", "Ceci est la dernière position de vos ennemis connue")}>
|
||||||
@@ -124,7 +126,7 @@ export const CustomMap = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<MapView ref={mapRef} style={{flex: 1}} initialRegion={InitialRegions.paris} mapType="standard" onTouchMove={() => setCenterMap(false)} toolbarEnabled={false}>
|
<MapView ref={mapRef} style={{flex: 1}} initialRegion={INITIAL_REGIONS.PARIS} mapType="standard" onTouchMove={() => setCenterMap(false)} toolbarEnabled={false}>
|
||||||
{startZone}
|
{startZone}
|
||||||
{gameZone}
|
{gameZone}
|
||||||
{currentPositionMarker}
|
{currentPositionMarker}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export const Colors = {
|
export const COLORS = {
|
||||||
background: '#f5f5f5'
|
background: '#f5f5f5'
|
||||||
};
|
};
|
||||||
2
mobile/traque-app/constants/config.js
Normal file
2
mobile/traque-app/constants/config.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const SERVER_URL = process.env.EXPO_PUBLIC_SERVER_URL;
|
||||||
|
export const SOCKET_URL = process.env.EXPO_PUBLIC_SOCKET_URL;
|
||||||
11
mobile/traque-app/constants/game.js
Normal file
11
mobile/traque-app/constants/game.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export const GAME_STATE = {
|
||||||
|
SETUP: "setup",
|
||||||
|
PLACEMENT: "placement",
|
||||||
|
PLAYING: "playing",
|
||||||
|
FINISHED: "finished"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ZONE_TYPES = {
|
||||||
|
CIRCLE: "circle",
|
||||||
|
POLYGON: "polygon"
|
||||||
|
};
|
||||||
4
mobile/traque-app/constants/index.js
Normal file
4
mobile/traque-app/constants/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './config';
|
||||||
|
export * from './game';
|
||||||
|
export * from './map';
|
||||||
|
export * from './colors';
|
||||||
32
mobile/traque-app/constants/map.js
Normal file
32
mobile/traque-app/constants/map.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
export const INITIAL_REGIONS = {
|
||||||
|
PARIS : {
|
||||||
|
latitude: 48.864,
|
||||||
|
longitude: 2.342,
|
||||||
|
latitudeDelta: 0,
|
||||||
|
longitudeDelta: 50
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LOCATION_PARAMETERS = {
|
||||||
|
LOCAL: {
|
||||||
|
accuracy: 4, // High
|
||||||
|
distanceInterval: 3, // meters
|
||||||
|
timeInterval: 1000, // ms
|
||||||
|
},
|
||||||
|
SERVER: {
|
||||||
|
accuracy: 4, // High
|
||||||
|
distanceInterval: 5, // meters
|
||||||
|
timeInterval: 5000, // ms
|
||||||
|
showsBackgroundLocationIndicator: true, // iOS only
|
||||||
|
pausesUpdatesAutomatically: false, // (iOS) Prevents auto-pausing of location updates
|
||||||
|
foregroundService: {
|
||||||
|
notificationTitle: "Enregistrement de votre position.",
|
||||||
|
notificationBody: "L'application utilise votre position en arrière plan.",
|
||||||
|
notificationColor: "#FF0000", // (Android) Notification icon color
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TASKS = {
|
||||||
|
BACKGROUND_LOCATION: "background-location-task"
|
||||||
|
};
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
// React
|
|
||||||
import { createContext, useContext, useMemo } from "react";
|
|
||||||
// IO
|
|
||||||
import { io } from "socket.io-client";
|
|
||||||
// Util
|
|
||||||
import { SOCKET_URL } from "../util/constants";
|
|
||||||
|
|
||||||
const SocketContext = createContext();
|
|
||||||
|
|
||||||
const teamSocket = io(SOCKET_URL, {path: "/back/socket.io"});
|
|
||||||
|
|
||||||
export const SocketProvider = ({ children }) => {
|
|
||||||
const value = useMemo(() => ({ teamSocket }), []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SocketContext.Provider value={value}>{children}</SocketContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSocket = () => {
|
|
||||||
return useContext(SocketContext);
|
|
||||||
};
|
|
||||||
@@ -1,24 +1,30 @@
|
|||||||
// React
|
// React
|
||||||
import { createContext, useContext, useMemo, useState } from "react";
|
import { createContext, useContext, useMemo, useState, useEffect } from "react";
|
||||||
// Context
|
// Context
|
||||||
import { useSocket } from "./socketContext";
|
|
||||||
import { useTeamConnexion } from "./teamConnexionContext";
|
import { useTeamConnexion } from "./teamConnexionContext";
|
||||||
// Hook
|
// Hook
|
||||||
import { useSendDeviceInfo } from "../hook/useSendDeviceInfo";
|
import { useSendDeviceInfo } from "../hook/useSendDeviceInfo";
|
||||||
import { useLocation } from "../hook/useLocation";
|
// Services
|
||||||
import { useSocketListener } from "../hook/useSocketListener";
|
import { socket } from "../services/socket";
|
||||||
// Util
|
// Constants
|
||||||
import { GameState } from "../util/gameState";
|
import { GAME_STATE } from "../constants";
|
||||||
|
|
||||||
const TeamContext = createContext();
|
const TeamContext = createContext();
|
||||||
|
|
||||||
|
const useSocketListener = (event, callback) => {
|
||||||
|
useEffect(() => {
|
||||||
|
socket.on(event, callback);
|
||||||
|
return () => {
|
||||||
|
socket.off(event, callback);
|
||||||
|
};
|
||||||
|
}, [callback, event]);
|
||||||
|
};
|
||||||
|
|
||||||
export const TeamProvider = ({children}) => {
|
export const TeamProvider = ({children}) => {
|
||||||
const {teamSocket} = useSocket();
|
|
||||||
const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10);
|
|
||||||
// update_team
|
// update_team
|
||||||
const [teamInfos, setTeamInfos] = useState({});
|
const [teamInfos, setTeamInfos] = useState({});
|
||||||
// game_state
|
// game_state
|
||||||
const [gameState, setGameState] = useState(GameState.SETUP);
|
const [gameState, setGAME_STATE] = useState(GAME_STATE.SETUP);
|
||||||
const [startDate, setStartDate] = useState(null);
|
const [startDate, setStartDate] = useState(null);
|
||||||
// current_zone
|
// current_zone
|
||||||
const [zoneExtremities, setZoneExtremities] = useState(null);
|
const [zoneExtremities, setZoneExtremities] = useState(null);
|
||||||
@@ -31,16 +37,16 @@ export const TeamProvider = ({children}) => {
|
|||||||
|
|
||||||
useSendDeviceInfo();
|
useSendDeviceInfo();
|
||||||
|
|
||||||
useSocketListener(teamSocket, "update_team", (data) => {
|
useSocketListener("update_team", (data) => {
|
||||||
setTeamInfos(teamInfos => ({...teamInfos, ...data}));
|
setTeamInfos(teamInfos => ({...teamInfos, ...data}));
|
||||||
});
|
});
|
||||||
|
|
||||||
useSocketListener(teamSocket, "game_state", (data) => {
|
useSocketListener("game_state", (data) => {
|
||||||
setGameState(data.state);
|
setGAME_STATE(data.state);
|
||||||
setStartDate(data.date);
|
setStartDate(data.date);
|
||||||
});
|
});
|
||||||
|
|
||||||
useSocketListener(teamSocket, "settings", (data) => {
|
useSocketListener("settings", (data) => {
|
||||||
setMessages(data.messages);
|
setMessages(data.messages);
|
||||||
setZoneType(data.zone.type);
|
setZoneType(data.zone.type);
|
||||||
//TODO
|
//TODO
|
||||||
@@ -48,16 +54,16 @@ export const TeamProvider = ({children}) => {
|
|||||||
//setOutOfZoneDelay(data.outOfZoneDelay);
|
//setOutOfZoneDelay(data.outOfZoneDelay);
|
||||||
});
|
});
|
||||||
|
|
||||||
useSocketListener(teamSocket, "current_zone", (data) => {
|
useSocketListener("current_zone", (data) => {
|
||||||
setZoneExtremities({begin: data.begin, end: data.end});
|
setZoneExtremities({begin: data.begin, end: data.end});
|
||||||
setNextZoneDate(data.endDate);
|
setNextZoneDate(data.endDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
useSocketListener(teamSocket, "logout", logout);
|
useSocketListener("logout", logout);
|
||||||
|
|
||||||
const value = useMemo(() => (
|
const value = useMemo(() => (
|
||||||
{teamInfos, gameState, startDate, zoneType, zoneExtremities, nextZoneDate, messages, location, getLocationAuthorization, startLocationTracking, stopLocationTracking}
|
{teamInfos, gameState, startDate, zoneType, zoneExtremities, nextZoneDate, messages}
|
||||||
), [teamInfos, gameState, startDate, zoneType, zoneExtremities, nextZoneDate, messages, location, getLocationAuthorization, startLocationTracking, stopLocationTracking]);
|
), [teamInfos, gameState, startDate, zoneType, zoneExtremities, nextZoneDate, messages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TeamContext.Provider value={value}>
|
<TeamContext.Provider value={value}>
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
// React
|
// React
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
// Hook
|
// Services
|
||||||
import { useSocketCommands } from "./useSocketCommands";
|
import { emitSendPosition, emitCapture } from "../services/socketEmitter";
|
||||||
|
|
||||||
export const useGame = () => {
|
export const useGame = () => {
|
||||||
const { emitSendPosition, emitCapture } = useSocketCommands();
|
|
||||||
|
|
||||||
const sendCurrentPosition = useCallback(() => {
|
const sendCurrentPosition = useCallback(() => {
|
||||||
emitSendPosition();
|
emitSendPosition();
|
||||||
}, [emitSendPosition]);
|
}, []);
|
||||||
|
|
||||||
const capture = useCallback((captureCode) => {
|
const capture = useCallback((captureCode) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -22,7 +21,7 @@ export const useGame = () => {
|
|||||||
resolve(response);
|
resolve(response);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [emitCapture]);
|
}, []);
|
||||||
|
|
||||||
return { sendCurrentPosition, capture };
|
return { sendCurrentPosition, capture };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
// Rect
|
|
||||||
import { useCallback, useMemo } from "react";
|
|
||||||
// Contexts
|
|
||||||
import { useTeamConnexion } from "../context/teamConnexionContext";
|
|
||||||
import { useTeamContext } from '../context/teamContext';
|
|
||||||
// Util
|
|
||||||
import { SERVER_URL } from "../util/constants";
|
|
||||||
|
|
||||||
export const useImageApi = () => {
|
|
||||||
const { teamId } = useTeamConnexion();
|
|
||||||
const { teamInfos } = useTeamContext();
|
|
||||||
const { enemyName } = teamInfos;
|
|
||||||
|
|
||||||
const uploadTeamImage = useCallback(async (imageUri) => {
|
|
||||||
if (!imageUri || !teamId) return;
|
|
||||||
|
|
||||||
const data = new FormData();
|
|
||||||
data.append('file', {
|
|
||||||
uri: imageUri,
|
|
||||||
name: 'photo.jpg',
|
|
||||||
type: 'image/jpeg',
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${SERVER_URL}/upload?team=${teamId}`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Échec de l'upload");
|
|
||||||
return await response.blob();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Erreur uploadImage :", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}, [teamId]);
|
|
||||||
|
|
||||||
const enemyImage = useMemo(() => {
|
|
||||||
if (!teamId || !enemyName) return require('../assets/images/missing_image.jpg');
|
|
||||||
|
|
||||||
return {uri: `${SERVER_URL}/photo/enemy?team=${teamId}`};
|
|
||||||
}, [teamId, enemyName]);
|
|
||||||
|
|
||||||
return { enemyImage, uploadTeamImage };
|
|
||||||
};
|
|
||||||
@@ -1,82 +1,33 @@
|
|||||||
// React
|
// React
|
||||||
import { useEffect, useState, useCallback, useMemo } from "react";
|
import { useState, useEffect } from 'react';
|
||||||
import { Alert } from "react-native";
|
|
||||||
// Expo
|
// Expo
|
||||||
import { defineTask, isTaskRegisteredAsync } from "expo-task-manager";
|
|
||||||
import * as Location from 'expo-location';
|
import * as Location from 'expo-location';
|
||||||
// Hook
|
// Constants
|
||||||
import { useSocketCommands } from "./useSocketCommands";
|
import { LOCATION_PARAMETERS } from '../constants';
|
||||||
|
|
||||||
export const useLocation = (timeInterval, distanceInterval) => {
|
export const useLocation = () => {
|
||||||
const { emitUpdatePosition } = useSocketCommands();
|
const [location, setLocation] = useState(null);
|
||||||
const [location, setLocation] = useState(null); // [latitude, longitude]
|
|
||||||
const LOCATION_TASK_NAME = "background-location-task";
|
|
||||||
const locationUpdateParameters = useMemo(() => ({
|
|
||||||
accuracy: Location.Accuracy.High,
|
|
||||||
distanceInterval: distanceInterval, // Update every 10 meters
|
|
||||||
timeInterval: timeInterval, // Minimum interval in ms
|
|
||||||
showsBackgroundLocationIndicator: true, // iOS only
|
|
||||||
pausesUpdatesAutomatically: false, // (iOS) Prevents auto-pausing of location updates
|
|
||||||
foregroundService: {
|
|
||||||
notificationTitle: "Enregistrement de votre position.",
|
|
||||||
notificationBody: "L'application utilise votre position en arrière plan.",
|
|
||||||
notificationColor: "#FF0000", // (Android) Notification icon color
|
|
||||||
},
|
|
||||||
}), [distanceInterval, timeInterval]);
|
|
||||||
|
|
||||||
defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data) {
|
|
||||||
const { locations } = data;
|
|
||||||
if (locations.length > 0) {
|
|
||||||
const firstLocation = locations[0];
|
|
||||||
const { latitude, longitude } = firstLocation.coords;
|
|
||||||
const new_location = [latitude, longitude];
|
|
||||||
try {
|
|
||||||
setLocation(new_location);
|
|
||||||
} catch (e) {
|
|
||||||
console.warn("setLocation failed (probably in background):", e);
|
|
||||||
}
|
|
||||||
emitUpdatePosition(new_location);
|
|
||||||
} else {
|
|
||||||
console.log("No location measured.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const getLocationAuthorization = useCallback(async () => {
|
|
||||||
const { status : statusForeground } = await Location.requestForegroundPermissionsAsync();
|
|
||||||
const { status : statusBackground } = await Location.requestBackgroundPermissionsAsync();
|
|
||||||
if (statusForeground !== "granted" || statusBackground !== "granted") {
|
|
||||||
Alert.alert("Échec", "Activez la localisation en arrière plan dans les paramètres.");
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const startLocationTracking = useCallback(async () => {
|
|
||||||
if (await getLocationAuthorization()) {
|
|
||||||
if (!(await isTaskRegisteredAsync(LOCATION_TASK_NAME))) {
|
|
||||||
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, locationUpdateParameters);
|
|
||||||
console.log("Location tracking started.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [getLocationAuthorization, locationUpdateParameters]);
|
|
||||||
|
|
||||||
const stopLocationTracking = useCallback(async () => {
|
|
||||||
if (await isTaskRegisteredAsync(LOCATION_TASK_NAME)) {
|
|
||||||
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
|
|
||||||
console.log("Location tracking stopped.");
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getLocationAuthorization();
|
let subscription;
|
||||||
}, [getLocationAuthorization]);
|
|
||||||
|
|
||||||
return [location, getLocationAuthorization, startLocationTracking, stopLocationTracking];
|
const startWatching = async () => {
|
||||||
|
const { status } = await Location.requestForegroundPermissionsAsync();
|
||||||
|
|
||||||
|
if (status !== 'granted') return;
|
||||||
|
|
||||||
|
subscription = await Location.watchPositionAsync(
|
||||||
|
LOCATION_PARAMETERS,
|
||||||
|
(location) => setLocation(location.coords)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
startWatching();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (subscription) subscription.remove();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { location };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import { useEffect, useRef } from 'react';
|
|||||||
import DeviceInfo from 'react-native-device-info';
|
import DeviceInfo from 'react-native-device-info';
|
||||||
// Context
|
// Context
|
||||||
import { useTeamConnexion } from "../context/teamConnexionContext";
|
import { useTeamConnexion } from "../context/teamConnexionContext";
|
||||||
// Hook
|
// Services
|
||||||
import { useSocketCommands } from "./useSocketCommands";
|
import { emitBattery, emitDeviceInfo } from "../services/socketEmitter";
|
||||||
|
|
||||||
export const useSendDeviceInfo = () => {
|
export const useSendDeviceInfo = () => {
|
||||||
const { emitBattery, emitDeviceInfo } = useSocketCommands();
|
|
||||||
const { loggedIn } = useTeamConnexion();
|
const { loggedIn } = useTeamConnexion();
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ export const useSendDeviceInfo = () => {
|
|||||||
isMounted.current = false;
|
isMounted.current = false;
|
||||||
clearInterval(batteryCheckInterval);
|
clearInterval(batteryCheckInterval);
|
||||||
};
|
};
|
||||||
}, [emitBattery, emitDeviceInfo, loggedIn]);
|
}, [loggedIn]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
// Hook
|
// Hook
|
||||||
import { useLocalStorage } from './useLocalStorage';
|
import { useLocalStorage } from './useLocalStorage';
|
||||||
import { useSocketCommands } from "./useSocketCommands";
|
// Services
|
||||||
|
import { emitLogin, emitLogout } from "../services/socketEmitter";
|
||||||
|
|
||||||
export const useSocketAuth = () => {
|
export const useSocketAuth = () => {
|
||||||
const { emitLogin, emitLogout } = useSocketCommands();
|
|
||||||
const [loggedIn, setLoggedIn] = useState(false);
|
const [loggedIn, setLoggedIn] = useState(false);
|
||||||
const [savedPassword, setSavedPassword] = useLocalStorage("team_password", null);
|
const [savedPassword, setSavedPassword] = useLocalStorage("team_password", null);
|
||||||
const isMounted = useRef(true);
|
const isMounted = useRef(true);
|
||||||
@@ -42,7 +42,7 @@ export const useSocketAuth = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [emitLogin, setSavedPassword]);
|
}, [setSavedPassword]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loggedIn && savedPassword) {
|
if (!loggedIn && savedPassword) {
|
||||||
@@ -54,7 +54,7 @@ export const useSocketAuth = () => {
|
|||||||
setLoggedIn(false);
|
setLoggedIn(false);
|
||||||
setSavedPassword(null);
|
setSavedPassword(null);
|
||||||
emitLogout();
|
emitLogout();
|
||||||
}, [emitLogout, setSavedPassword]);
|
}, [setSavedPassword]);
|
||||||
|
|
||||||
return {login, logout, password: savedPassword, loggedIn};
|
return {login, logout, password: savedPassword, loggedIn};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
// React
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
// Context
|
|
||||||
import { useSocket } from "../context/socketContext";
|
|
||||||
|
|
||||||
export const useSocketCommands = () => {
|
|
||||||
const { teamSocket } = useSocket();
|
|
||||||
|
|
||||||
const emitLogin = useCallback((password, callback) => {
|
|
||||||
if (!teamSocket?.connected) return;
|
|
||||||
console.log("Try to log in with :", password);
|
|
||||||
teamSocket.emit("login", password, callback);
|
|
||||||
}, [teamSocket]);
|
|
||||||
|
|
||||||
const emitLogout = useCallback(() => {
|
|
||||||
if (!teamSocket?.connected) return;
|
|
||||||
console.log("Logout.");
|
|
||||||
teamSocket.emit("logout");
|
|
||||||
}, [teamSocket]);
|
|
||||||
|
|
||||||
const emitSendPosition = useCallback(() => {
|
|
||||||
if (!teamSocket?.connected) return;
|
|
||||||
console.log("Reveal position.");
|
|
||||||
teamSocket.emit("send_position");
|
|
||||||
}, [teamSocket]);
|
|
||||||
|
|
||||||
const emitUpdatePosition = useCallback((location) => {
|
|
||||||
if (!teamSocket?.connected) return;
|
|
||||||
console.log("Update position :", location);
|
|
||||||
teamSocket.emit("update_position", location);
|
|
||||||
}, [teamSocket]);
|
|
||||||
|
|
||||||
const emitCapture = useCallback((captureCode, callback) => {
|
|
||||||
if (!teamSocket?.connected) return;
|
|
||||||
console.log("Try to capture :", captureCode);
|
|
||||||
teamSocket.emit("capture", captureCode, callback);
|
|
||||||
}, [teamSocket]);
|
|
||||||
|
|
||||||
const emitBattery = useCallback((level) => {
|
|
||||||
if (!teamSocket?.connected) return;
|
|
||||||
console.log("Send battery level.");
|
|
||||||
teamSocket.emit('battery_update', level);
|
|
||||||
}, [teamSocket]);
|
|
||||||
|
|
||||||
const emitDeviceInfo = useCallback((infos) => {
|
|
||||||
if (!teamSocket?.connected) return;
|
|
||||||
console.log("Send device infos.");
|
|
||||||
teamSocket.emit('device_info', infos);
|
|
||||||
}, [teamSocket]);
|
|
||||||
|
|
||||||
return { emitLogin, emitLogout, emitSendPosition, emitUpdatePosition, emitCapture, emitBattery, emitDeviceInfo };
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// React
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
export const useSocketListener = (socket, event, callback) => {
|
|
||||||
useEffect(() => {
|
|
||||||
socket.on(event, callback);
|
|
||||||
return () => {
|
|
||||||
socket.off(event, callback);
|
|
||||||
};
|
|
||||||
}, [callback, event, socket]);
|
|
||||||
};
|
|
||||||
48
mobile/traque-app/services/backgroundLocationTask.js
Normal file
48
mobile/traque-app/services/backgroundLocationTask.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// Expo
|
||||||
|
import { defineTask, isTaskRegisteredAsync } from "expo-task-manager";
|
||||||
|
import * as Location from 'expo-location';
|
||||||
|
// Services
|
||||||
|
import { emitUpdatePosition } from "./socketEmitter";
|
||||||
|
// Constants
|
||||||
|
import { TASKS, LOCATION_PARAMETERS } from "../constants";
|
||||||
|
|
||||||
|
|
||||||
|
// Task
|
||||||
|
|
||||||
|
defineTask(TASKS.BACKGROUND_LOCATION, async ({ data, error }) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
const { locations } = data;
|
||||||
|
if (locations.length == 0) {
|
||||||
|
console.log("No location measured.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { latitude, longitude } = locations[0].coords;
|
||||||
|
emitUpdatePosition([latitude, longitude]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
|
||||||
|
export const getLocationAuthorization = async () => {
|
||||||
|
const { status : statusForeground } = await Location.requestForegroundPermissionsAsync();
|
||||||
|
const { status : statusBackground } = await Location.requestBackgroundPermissionsAsync();
|
||||||
|
return statusForeground == "granted" && statusBackground == "granted";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startLocationTracking = async () => {
|
||||||
|
if (!(await getLocationAuthorization())) return;
|
||||||
|
if (await isTaskRegisteredAsync(TASKS.BACKGROUND_LOCATION)) return;
|
||||||
|
console.log("Location tracking started.");
|
||||||
|
await Location.startLocationUpdatesAsync(TASKS.BACKGROUND_LOCATION, LOCATION_PARAMETERS.SERVER);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stopLocationTracking = async () => {
|
||||||
|
if (!await isTaskRegisteredAsync(TASKS.BACKGROUND_LOCATION)) return;
|
||||||
|
console.log("Location tracking stopped.");
|
||||||
|
await Location.stopLocationUpdatesAsync(TASKS.BACKGROUND_LOCATION);
|
||||||
|
};
|
||||||
32
mobile/traque-app/services/imageService.js
Normal file
32
mobile/traque-app/services/imageService.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Constants
|
||||||
|
import { SERVER_URL } from "../constants";
|
||||||
|
|
||||||
|
export const uploadTeamImage = async (id, imageUri) => {
|
||||||
|
if (!imageUri || !id) return;
|
||||||
|
|
||||||
|
const data = new FormData();
|
||||||
|
data.append('file', {
|
||||||
|
uri: imageUri,
|
||||||
|
name: 'photo.jpg',
|
||||||
|
type: 'image/jpeg',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${SERVER_URL}/upload?team=${id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error("Échec de l'upload");
|
||||||
|
return await response.blob();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur uploadImage :", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const enemyImage = (id) => {
|
||||||
|
if (!id) return require('../assets/images/missing_image.jpg');
|
||||||
|
|
||||||
|
return {uri: `${SERVER_URL}/photo/enemy?team=${id}`};
|
||||||
|
};
|
||||||
8
mobile/traque-app/services/socket.js
Normal file
8
mobile/traque-app/services/socket.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Socket
|
||||||
|
import { io } from "socket.io-client";
|
||||||
|
// Constants
|
||||||
|
import { SOCKET_URL } from "../constants";
|
||||||
|
|
||||||
|
export const socket = io(SOCKET_URL, {
|
||||||
|
path: "/back/socket.io"
|
||||||
|
});
|
||||||
44
mobile/traque-app/services/socketEmitter.js
Normal file
44
mobile/traque-app/services/socketEmitter.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// Services
|
||||||
|
import { socket } from "./socket";
|
||||||
|
|
||||||
|
export const emitLogin = (password, callback) => {
|
||||||
|
if (!socket?.connected) return;
|
||||||
|
console.log("Try to log in with :", password);
|
||||||
|
socket.emit("login", password, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emitLogout = () => {
|
||||||
|
if (!socket?.connected) return;
|
||||||
|
console.log("Logout.");
|
||||||
|
socket.emit("logout");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emitSendPosition = () => {
|
||||||
|
if (!socket?.connected) return;
|
||||||
|
console.log("Reveal position.");
|
||||||
|
socket.emit("send_position");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emitUpdatePosition = (location) => {
|
||||||
|
if (!socket?.connected) return;
|
||||||
|
console.log("Update position :", location);
|
||||||
|
socket.emit("update_position", location);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emitCapture = (captureCode, callback) => {
|
||||||
|
if (!socket?.connected) return;
|
||||||
|
console.log("Try to capture :", captureCode);
|
||||||
|
socket.emit("capture", captureCode, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emitBattery = (level) => {
|
||||||
|
if (!socket?.connected) return;
|
||||||
|
console.log("Send battery level.");
|
||||||
|
socket.emit('battery_update', level);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emitDeviceInfo = (infos) => {
|
||||||
|
if (!socket?.connected) return;
|
||||||
|
console.log("Send device infos.");
|
||||||
|
socket.emit('device_info', infos);
|
||||||
|
};
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
export const SERVER_URL = process.env.EXPO_PUBLIC_SERVER_URL;
|
|
||||||
export const SOCKET_URL = process.env.EXPO_PUBLIC_SOCKET_URL;
|
|
||||||
|
|
||||||
export const InitialRegions = {
|
|
||||||
paris : {
|
|
||||||
latitude: 48.864,
|
|
||||||
longitude: 2.342,
|
|
||||||
latitudeDelta: 0,
|
|
||||||
longitudeDelta: 50
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ZoneTypes = {
|
|
||||||
circle: "circle",
|
|
||||||
polygon: "polygon"
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export const GameState = {
|
|
||||||
SETUP: "setup",
|
|
||||||
PLACEMENT: "placement",
|
|
||||||
PLAYING: "playing",
|
|
||||||
FINISHED: "finished"
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user