diff --git a/mobile/traque-app/app/_layout.jsx b/mobile/traque-app/app/_layout.jsx
index cbe85b9..2ec2090 100644
--- a/mobile/traque-app/app/_layout.jsx
+++ b/mobile/traque-app/app/_layout.jsx
@@ -1,19 +1,16 @@
// Expo
import { Slot } from 'expo-router';
// Contexts
-import { SocketProvider } from "../context/socketContext";
import { TeamConnexionProvider } from "../context/teamConnexionContext";
import { TeamProvider } from "../context/teamContext";
const Layout = () => {
return (
-
-
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/mobile/traque-app/app/index.jsx b/mobile/traque-app/app/index.jsx
index 4580d4a..a9a1c3a 100644
--- a/mobile/traque-app/app/index.jsx
+++ b/mobile/traque-app/app/index.jsx
@@ -9,34 +9,33 @@ import { CustomImage } from '../components/image';
import { CustomTextInput } from '../components/input';
// Contexts
import { useTeamConnexion } from "../context/teamConnexionContext";
-import { useTeamContext } from "../context/teamContext";
// Hooks
import { usePickImage } from '../hook/usePickImage';
-import { useImageApi } from '../hook/useImageApi';
-// Util
-import { Colors } from '../util/colors';
+// Services
+import { uploadTeamImage } from '../services/imageService';
+import { getLocationAuthorization, stopLocationTracking } from '../services/backgroundLocationTask';
+// Constants
+import { COLORS } from '../constants';
const Index = () => {
const router = useRouter();
- const {login, loggedIn} = useTeamConnexion();
- const {getLocationAuthorization, stopLocationTracking} = useTeamContext();
+ const { login, loggedIn } = useTeamConnexion();
const {image, pickImage} = usePickImage();
const [teamId, setTeamId] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
- const { uploadTeamImage } = useImageApi();
- // Disbaling location tracking
+ // Disbaling location tracking and asking permissions
useEffect(() => {
stopLocationTracking();
- }, [stopLocationTracking]);
+ getLocationAuthorization();
+ }, []);
// Routeur
useEffect(() => {
if (loggedIn) {
- uploadTeamImage(image?.uri);
router.replace("/interface");
}
- }, [router, loggedIn, uploadTeamImage, image]);
+ }, [router, loggedIn, image]);
const handleSubmit = async () => {
if (isSubmitting || !getLocationAuthorization()) return;
@@ -53,6 +52,7 @@ const Index = () => {
const response = await login(teamId);
if (response.isLoggedIn) {
+ uploadTeamImage(teamId, image?.uri);
setTeamId("");
} else {
setTimeout(() => Alert.alert("Échec", "L'ID d'équipe est inconnu."), 100);
@@ -94,7 +94,7 @@ const styles = StyleSheet.create({
flexGrow: 1,
alignItems: 'center',
paddingVertical: 20,
- backgroundColor: Colors.background
+ backgroundColor: COLORS.background
},
transitionContainer: {
flexGrow: 1,
diff --git a/mobile/traque-app/app/interface.jsx b/mobile/traque-app/app/interface.jsx
index 9a831a1..6cb3622 100644
--- a/mobile/traque-app/app/interface.jsx
+++ b/mobile/traque-app/app/interface.jsx
@@ -6,21 +6,23 @@ import { useRouter } from 'expo-router';
// Components
import { CustomMap } from '../components/map';
import { Drawer } from '../components/drawer';
+import { TimerMMSS } from '../components/timer';
// Contexts
import { useTeamConnexion } from '../context/teamConnexionContext';
import { useTeamContext } from '../context/teamContext';
// Hooks
import { useGame } from '../hook/useGame';
import { useTimeDifference } from '../hook/useTimeDifference';
+// Services
+import { startLocationTracking } from '../services/backgroundLocationTask';
// Util
-import { GameState } from '../util/gameState';
-import { TimerMMSS } from '../components/timer';
import { secondsToMMSS } from '../util/functions';
-import { Colors } from '../util/colors';
+// Constants
+import { GAME_STATE, COLORS } from '../constants';
const Interface = () => {
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 {loggedIn, logout} = useTeamConnexion();
const {sendCurrentPosition} = useGame();
@@ -31,16 +33,16 @@ const Interface = () => {
const statusMessage = useMemo(() => {
switch (gameState) {
- case GameState.SETUP:
+ case GAME_STATE.SETUP:
return messages?.waiting || "Préparation de la partie";
- case GameState.PLACEMENT:
+ case GAME_STATE.PLACEMENT:
return "Phase de placement";
- case GameState.PLAYING:
+ case GAME_STATE.PLAYING:
if (captured) return messages?.captured || "Vous avez été éliminé...";
if (!outOfZone) return "La partie est en cours";
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`;
- case GameState.FINISHED:
+ case GAME_STATE.FINISHED:
return `Vous avez ${captured ? (messages?.loser || "perdu...") : (messages?.winner || "gagné !")}`;
default:
return "Inconnue";
@@ -56,12 +58,8 @@ const Interface = () => {
// Activating geolocation tracking
useEffect(() => {
- if (loggedIn) {
- startLocationTracking();
- } else {
- stopLocationTracking();
- }
- }, [startLocationTracking, stopLocationTracking, loggedIn]);
+ startLocationTracking();
+ }, []);
return (
@@ -83,12 +81,12 @@ const Interface = () => {
- { gameState == GameState.PLACEMENT &&
+ { gameState == GAME_STATE.PLACEMENT &&
{ready ? "Placé" : "Non placé"}
}
- { gameState == GameState.PLAYING && !captured &&
+ { gameState == GAME_STATE.PLAYING && !captured &&
}
@@ -99,12 +97,12 @@ const Interface = () => {
setBottomContainerHeight(event.nativeEvent.layout.height)}>
- { gameState == GameState.PLAYING && !captured && !hasHandicap &&
+ { gameState == GAME_STATE.PLAYING && !captured && !hasHandicap &&
}
- { gameState == GameState.PLAYING && !captured &&
+ { gameState == GAME_STATE.PLAYING && !captured &&
}
@@ -116,7 +114,7 @@ export default Interface;
const styles = StyleSheet.create({
globalContainer: {
- backgroundColor: Colors.background,
+ backgroundColor: COLORS.background,
flex: 1,
},
topContainer: {
diff --git a/mobile/traque-app/components/drawer.jsx b/mobile/traque-app/components/drawer.jsx
index a40b1c6..96cd974 100644
--- a/mobile/traque-app/components/drawer.jsx
+++ b/mobile/traque-app/components/drawer.jsx
@@ -8,17 +8,20 @@ import { CustomImage } from './image';
import { CustomTextInput } from './input';
import { Stat } from './stat';
// Contexts
+import { useTeamConnexion } from '../context/teamConnexionContext';
import { useTeamContext } from '../context/teamContext';
// Hooks
import { useTimeDifference } from '../hook/useTimeDifference';
import { useGame } from '../hook/useGame';
+// Services
+import { enemyImage } from '../services/imageService';
// Util
-import { GameState } from '../util/gameState';
-import { Colors } from '../util/colors';
import { secondsToHHMMSS } from '../util/functions';
-import { useImageApi } from '../hook/useImageApi';
+// Constants
+import { GAME_STATE, COLORS } from '../constants';
export const Drawer = ({ height }) => {
+ const { teamId } = useTeamConnexion();
const [collapsibleState, setCollapsibleState] = useState(true);
const [enemyCaptureCode, setEnemyCaptureCode] = useState("");
const {teamInfos, gameState, startDate} = useTeamContext();
@@ -27,7 +30,6 @@ export const Drawer = ({ height }) => {
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 captureStatusColor = {0: "#777", 1: "#FFA500", 2: "#FF6B6B", 3: "#81C784"};
- const { enemyImage } = useImageApi();
const avgSpeed = useMemo(() => {
const hours = (finishDate ? (finishDate - startDate) : timeSinceStart*1000) / 1000 / 3600;
@@ -76,13 +78,13 @@ export const Drawer = ({ height }) => {
- { gameState == GameState.PLAYING &&
+ { gameState == GAME_STATE.PLAYING &&
Code de {(name ?? "Indisponible")} : {String(captureCode).padStart(4,"0")}
}
- { gameState == GameState.PLAYING && !hasHandicap &&
+ { gameState == GAME_STATE.PLAYING && !hasHandicap &&
{"Cible (" + (enemyName ?? "Indisponible") + ")"}
-
+
@@ -122,7 +124,7 @@ const styles = StyleSheet.create({
},
innerDrawerContainer: {
width: "100%",
- backgroundColor: Colors.background,
+ backgroundColor: COLORS.background,
borderTopLeftRadius: 30,
borderTopRightRadius: 30,
overflow: 'hidden',
@@ -136,7 +138,7 @@ const styles = StyleSheet.create({
collapsibleWindow: {
width: "100%",
justifyContent: 'center',
- backgroundColor: Colors.background,
+ backgroundColor: COLORS.background,
},
collapsibleContent: {
paddingHorizontal: 15,
diff --git a/mobile/traque-app/components/map.jsx b/mobile/traque-app/components/map.jsx
index 8375f25..4711223 100644
--- a/mobile/traque-app/components/map.jsx
+++ b/mobile/traque-app/components/map.jsx
@@ -7,20 +7,22 @@ import LinearGradient from 'react-native-linear-gradient';
import { DashedCircle, InvertedCircle, InvertedPolygon } from './layer';
// Contexts
import { useTeamContext } from '../context/teamContext';
+// Hook
+import { useLocation } from '../hook/useLocation';
// Util
-import { GameState } from '../util/gameState';
-import { ZoneTypes, InitialRegions } from '../util/constants';
+import { ZONE_TYPES, INITIAL_REGIONS, GAME_STATE } from '../constants';
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 [centerMap, setCenterMap] = useState(true);
const mapRef = useRef(null);
// 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);
+ if (centerMap && location && mapRef.current) {
+ mapRef.current.animateToRegion({...location, latitudeDelta: 0, longitudeDelta: 0.02}, 1000);
}
}, [centerMap, location]);
@@ -30,7 +32,7 @@ export const CustomMap = () => {
const latToLatitude = (pos) => ({latitude: pos.lat, longitude: pos.lng});
const startZone = useMemo(() => {
- if (gameState != GameState.PLACEMENT || !startingArea) return null;
+ if (gameState != GAME_STATE.PLACEMENT || !startingArea) return null;
return (
@@ -38,7 +40,7 @@ export const CustomMap = () => {
}, [gameState, startingArea]);
const gameZone = useMemo(() => {
- if (gameState !== GameState.PLAYING || !zoneExtremities) return null;
+ if (gameState !== GAME_STATE.PLAYING || !zoneExtremities) return null;
const items = [];
@@ -47,7 +49,7 @@ export const CustomMap = () => {
const strokeWidth = 3;
const lineDashPattern = [30, 10];
- if (zoneType === ZoneTypes.circle) {
+ if (zoneType === ZONE_TYPES.CIRCLE) {
if (zoneExtremities.begin) items.push(
{
lineDashPattern={lineDashPattern}
/>
);
- } else if (zoneType === ZoneTypes.polygon) {
+ } else if (zoneType === ZONE_TYPES.POLYGON) {
if (zoneExtremities.begin) items.push(
{
if (!location) return null;
return (
- Alert.alert("Position actuelle", "Ceci est votre position")}>
+ Alert.alert("Position actuelle", "Ceci est votre position")}>
);
}, [location]);
const lastPositionMarker = useMemo(() => {
- if (gameState != GameState.PLAYING || !lastSentLocation || hasHandicap) return null;
+ if (gameState != GAME_STATE.PLAYING || !lastSentLocation || hasHandicap) return null;
return (
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]);
const enemyPositionMarker = useMemo(() => {
- if (gameState != GameState.PLAYING || !enemyLocation || hasHandicap) return null;
+ if (gameState != GAME_STATE.PLAYING || !enemyLocation || hasHandicap) return null;
return (
Alert.alert("Position ennemie", "Ceci est la dernière position de vos ennemis connue")}>
@@ -124,7 +126,7 @@ export const CustomMap = () => {
return (
- setCenterMap(false)} toolbarEnabled={false}>
+ setCenterMap(false)} toolbarEnabled={false}>
{startZone}
{gameZone}
{currentPositionMarker}
diff --git a/mobile/traque-app/util/colors.js b/mobile/traque-app/constants/colors.js
similarity index 54%
rename from mobile/traque-app/util/colors.js
rename to mobile/traque-app/constants/colors.js
index bad3068..35f1e88 100644
--- a/mobile/traque-app/util/colors.js
+++ b/mobile/traque-app/constants/colors.js
@@ -1,3 +1,3 @@
-export const Colors = {
+export const COLORS = {
background: '#f5f5f5'
};
diff --git a/mobile/traque-app/constants/config.js b/mobile/traque-app/constants/config.js
new file mode 100644
index 0000000..940be22
--- /dev/null
+++ b/mobile/traque-app/constants/config.js
@@ -0,0 +1,2 @@
+export const SERVER_URL = process.env.EXPO_PUBLIC_SERVER_URL;
+export const SOCKET_URL = process.env.EXPO_PUBLIC_SOCKET_URL;
diff --git a/mobile/traque-app/constants/game.js b/mobile/traque-app/constants/game.js
new file mode 100644
index 0000000..2815623
--- /dev/null
+++ b/mobile/traque-app/constants/game.js
@@ -0,0 +1,11 @@
+export const GAME_STATE = {
+ SETUP: "setup",
+ PLACEMENT: "placement",
+ PLAYING: "playing",
+ FINISHED: "finished"
+};
+
+export const ZONE_TYPES = {
+ CIRCLE: "circle",
+ POLYGON: "polygon"
+};
diff --git a/mobile/traque-app/constants/index.js b/mobile/traque-app/constants/index.js
new file mode 100644
index 0000000..f0e4d6e
--- /dev/null
+++ b/mobile/traque-app/constants/index.js
@@ -0,0 +1,4 @@
+export * from './config';
+export * from './game';
+export * from './map';
+export * from './colors';
diff --git a/mobile/traque-app/constants/map.js b/mobile/traque-app/constants/map.js
new file mode 100644
index 0000000..967ce80
--- /dev/null
+++ b/mobile/traque-app/constants/map.js
@@ -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"
+};
diff --git a/mobile/traque-app/context/socketContext.jsx b/mobile/traque-app/context/socketContext.jsx
deleted file mode 100644
index bb84505..0000000
--- a/mobile/traque-app/context/socketContext.jsx
+++ /dev/null
@@ -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 (
- {children}
- );
-};
-
-export const useSocket = () => {
- return useContext(SocketContext);
-};
diff --git a/mobile/traque-app/context/teamContext.jsx b/mobile/traque-app/context/teamContext.jsx
index 65c3ed3..d0cab3b 100644
--- a/mobile/traque-app/context/teamContext.jsx
+++ b/mobile/traque-app/context/teamContext.jsx
@@ -1,24 +1,30 @@
// React
-import { createContext, useContext, useMemo, useState } from "react";
+import { createContext, useContext, useMemo, useState, useEffect } from "react";
// Context
-import { useSocket } from "./socketContext";
import { useTeamConnexion } from "./teamConnexionContext";
// Hook
import { useSendDeviceInfo } from "../hook/useSendDeviceInfo";
-import { useLocation } from "../hook/useLocation";
-import { useSocketListener } from "../hook/useSocketListener";
-// Util
-import { GameState } from "../util/gameState";
+// Services
+import { socket } from "../services/socket";
+// Constants
+import { GAME_STATE } from "../constants";
const TeamContext = createContext();
+const useSocketListener = (event, callback) => {
+ useEffect(() => {
+ socket.on(event, callback);
+ return () => {
+ socket.off(event, callback);
+ };
+ }, [callback, event]);
+};
+
export const TeamProvider = ({children}) => {
- const {teamSocket} = useSocket();
- const [location, getLocationAuthorization, startLocationTracking, stopLocationTracking] = useLocation(5000, 10);
// update_team
const [teamInfos, setTeamInfos] = useState({});
// game_state
- const [gameState, setGameState] = useState(GameState.SETUP);
+ const [gameState, setGAME_STATE] = useState(GAME_STATE.SETUP);
const [startDate, setStartDate] = useState(null);
// current_zone
const [zoneExtremities, setZoneExtremities] = useState(null);
@@ -31,16 +37,16 @@ export const TeamProvider = ({children}) => {
useSendDeviceInfo();
- useSocketListener(teamSocket, "update_team", (data) => {
+ useSocketListener("update_team", (data) => {
setTeamInfos(teamInfos => ({...teamInfos, ...data}));
});
- useSocketListener(teamSocket, "game_state", (data) => {
- setGameState(data.state);
+ useSocketListener("game_state", (data) => {
+ setGAME_STATE(data.state);
setStartDate(data.date);
});
- useSocketListener(teamSocket, "settings", (data) => {
+ useSocketListener("settings", (data) => {
setMessages(data.messages);
setZoneType(data.zone.type);
//TODO
@@ -48,16 +54,16 @@ export const TeamProvider = ({children}) => {
//setOutOfZoneDelay(data.outOfZoneDelay);
});
- useSocketListener(teamSocket, "current_zone", (data) => {
+ useSocketListener("current_zone", (data) => {
setZoneExtremities({begin: data.begin, end: data.end});
setNextZoneDate(data.endDate);
});
- useSocketListener(teamSocket, "logout", logout);
+ useSocketListener("logout", logout);
const value = useMemo(() => (
- {teamInfos, gameState, startDate, zoneType, zoneExtremities, nextZoneDate, messages, location, getLocationAuthorization, startLocationTracking, stopLocationTracking}
- ), [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]);
return (
diff --git a/mobile/traque-app/hook/useGame.jsx b/mobile/traque-app/hook/useGame.jsx
index ba13cba..91624d4 100644
--- a/mobile/traque-app/hook/useGame.jsx
+++ b/mobile/traque-app/hook/useGame.jsx
@@ -1,14 +1,13 @@
// React
import { useCallback } from "react";
-// Hook
-import { useSocketCommands } from "./useSocketCommands";
+// Services
+import { emitSendPosition, emitCapture } from "../services/socketEmitter";
export const useGame = () => {
- const { emitSendPosition, emitCapture } = useSocketCommands();
const sendCurrentPosition = useCallback(() => {
emitSendPosition();
- }, [emitSendPosition]);
+ }, []);
const capture = useCallback((captureCode) => {
return new Promise((resolve, reject) => {
@@ -22,7 +21,7 @@ export const useGame = () => {
resolve(response);
});
});
- }, [emitCapture]);
+ }, []);
return { sendCurrentPosition, capture };
};
diff --git a/mobile/traque-app/hook/useImageApi.jsx b/mobile/traque-app/hook/useImageApi.jsx
deleted file mode 100644
index 7149c45..0000000
--- a/mobile/traque-app/hook/useImageApi.jsx
+++ /dev/null
@@ -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 };
-};
diff --git a/mobile/traque-app/hook/useLocation.jsx b/mobile/traque-app/hook/useLocation.jsx
index b616984..bdba8ef 100644
--- a/mobile/traque-app/hook/useLocation.jsx
+++ b/mobile/traque-app/hook/useLocation.jsx
@@ -1,82 +1,33 @@
// React
-import { useEffect, useState, useCallback, useMemo } from "react";
-import { Alert } from "react-native";
+import { useState, useEffect } from 'react';
// Expo
-import { defineTask, isTaskRegisteredAsync } from "expo-task-manager";
import * as Location from 'expo-location';
-// Hook
-import { useSocketCommands } from "./useSocketCommands";
+// Constants
+import { LOCATION_PARAMETERS } from '../constants';
-export const useLocation = (timeInterval, distanceInterval) => {
- const { emitUpdatePosition } = useSocketCommands();
- 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.");
- }
- }, []);
+export const useLocation = () => {
+ const [location, setLocation] = useState(null);
useEffect(() => {
- getLocationAuthorization();
- }, [getLocationAuthorization]);
+ let subscription;
- 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 };
};
diff --git a/mobile/traque-app/hook/useSendDeviceInfo.jsx b/mobile/traque-app/hook/useSendDeviceInfo.jsx
index ab1be39..6d560a3 100644
--- a/mobile/traque-app/hook/useSendDeviceInfo.jsx
+++ b/mobile/traque-app/hook/useSendDeviceInfo.jsx
@@ -3,11 +3,10 @@ import { useEffect, useRef } from 'react';
import DeviceInfo from 'react-native-device-info';
// Context
import { useTeamConnexion } from "../context/teamConnexionContext";
-// Hook
-import { useSocketCommands } from "./useSocketCommands";
+// Services
+import { emitBattery, emitDeviceInfo } from "../services/socketEmitter";
export const useSendDeviceInfo = () => {
- const { emitBattery, emitDeviceInfo } = useSocketCommands();
const { loggedIn } = useTeamConnexion();
const isMounted = useRef(true);
@@ -41,7 +40,7 @@ export const useSendDeviceInfo = () => {
isMounted.current = false;
clearInterval(batteryCheckInterval);
};
- }, [emitBattery, emitDeviceInfo, loggedIn]);
+ }, [loggedIn]);
return null;
};
diff --git a/mobile/traque-app/hook/useSocketAuth.jsx b/mobile/traque-app/hook/useSocketAuth.jsx
index 5efebaf..0b4fdfe 100644
--- a/mobile/traque-app/hook/useSocketAuth.jsx
+++ b/mobile/traque-app/hook/useSocketAuth.jsx
@@ -2,10 +2,10 @@
import { useState, useEffect, useCallback, useRef } from 'react';
// Hook
import { useLocalStorage } from './useLocalStorage';
-import { useSocketCommands } from "./useSocketCommands";
+// Services
+import { emitLogin, emitLogout } from "../services/socketEmitter";
export const useSocketAuth = () => {
- const { emitLogin, emitLogout } = useSocketCommands();
const [loggedIn, setLoggedIn] = useState(false);
const [savedPassword, setSavedPassword] = useLocalStorage("team_password", null);
const isMounted = useRef(true);
@@ -42,7 +42,7 @@ export const useSocketAuth = () => {
}
});
});
- }, [emitLogin, setSavedPassword]);
+ }, [setSavedPassword]);
useEffect(() => {
if (!loggedIn && savedPassword) {
@@ -54,7 +54,7 @@ export const useSocketAuth = () => {
setLoggedIn(false);
setSavedPassword(null);
emitLogout();
- }, [emitLogout, setSavedPassword]);
+ }, [setSavedPassword]);
return {login, logout, password: savedPassword, loggedIn};
};
diff --git a/mobile/traque-app/hook/useSocketCommands.jsx b/mobile/traque-app/hook/useSocketCommands.jsx
deleted file mode 100644
index 0f9ae6d..0000000
--- a/mobile/traque-app/hook/useSocketCommands.jsx
+++ /dev/null
@@ -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 };
-};
diff --git a/mobile/traque-app/hook/useSocketListener.jsx b/mobile/traque-app/hook/useSocketListener.jsx
deleted file mode 100644
index 55c86b6..0000000
--- a/mobile/traque-app/hook/useSocketListener.jsx
+++ /dev/null
@@ -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]);
-};
diff --git a/mobile/traque-app/services/backgroundLocationTask.js b/mobile/traque-app/services/backgroundLocationTask.js
new file mode 100644
index 0000000..23b7bd2
--- /dev/null
+++ b/mobile/traque-app/services/backgroundLocationTask.js
@@ -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);
+};
diff --git a/mobile/traque-app/services/imageService.js b/mobile/traque-app/services/imageService.js
new file mode 100644
index 0000000..0711fdb
--- /dev/null
+++ b/mobile/traque-app/services/imageService.js
@@ -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}`};
+};
diff --git a/mobile/traque-app/services/socket.js b/mobile/traque-app/services/socket.js
new file mode 100644
index 0000000..aa629fb
--- /dev/null
+++ b/mobile/traque-app/services/socket.js
@@ -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"
+});
diff --git a/mobile/traque-app/services/socketEmitter.js b/mobile/traque-app/services/socketEmitter.js
new file mode 100644
index 0000000..e788747
--- /dev/null
+++ b/mobile/traque-app/services/socketEmitter.js
@@ -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);
+};
diff --git a/mobile/traque-app/util/constants.js b/mobile/traque-app/util/constants.js
deleted file mode 100644
index cabfae7..0000000
--- a/mobile/traque-app/util/constants.js
+++ /dev/null
@@ -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"
-};
diff --git a/mobile/traque-app/util/gameState.js b/mobile/traque-app/util/gameState.js
deleted file mode 100644
index 3333b42..0000000
--- a/mobile/traque-app/util/gameState.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export const GameState = {
- SETUP: "setup",
- PLACEMENT: "placement",
- PLAYING: "playing",
- FINISHED: "finished"
-};