Ajout traque-app

This commit is contained in:
Sebastien Riviere
2025-08-24 10:30:32 +02:00
parent 623d1c05bf
commit a7f047388f
72 changed files with 45125 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
import { useSocket } from "../context/socketContext";
import { useTeamConnexion } from "../context/teamConnexionContext";
import { useTeamContext } from "../context/teamContext";
export default function useGame() {
const { teamSocket } = useSocket();
const { teamId } = useTeamConnexion();
const { teamInfos, gameState } = useTeamContext();
function sendCurrentPosition() {
console.log("Reveal position.")
teamSocket.emit("send_position");
}
function capture(captureCode) {
console.log("Try to capture :", captureCode);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
console.warn("Server did not respond to capture emit.");
reject();
}, 3000);
teamSocket.emit("capture", captureCode, (response) => {
clearTimeout(timeout);
console.log(response.message);
resolve(response);
});
});
}
return {...teamInfos, sendCurrentPosition, capture, teamId, gameState};
}

View File

@@ -0,0 +1,34 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useEffect, useState } from "react";
export function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(initialValue);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const item = await AsyncStorage.getItem(key);
setStoredValue(item ? JSON.parse(item) : initialValue);
} catch (error) {
console.log(error);
}
setLoading(false);
}
fetchData();
}, []);
const setValue = async value => {
try {
setLoading(true);
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
await AsyncStorage.setItem(key, JSON.stringify(valueToStore));
setLoading(false);
} catch (error) {
console.log(error);
}
}
return [storedValue, setValue, loading];
}

View File

@@ -0,0 +1,80 @@
import { useEffect, useState } from "react";
import { Alert } from "react-native";
import { defineTask, isTaskRegisteredAsync } from "expo-task-manager";
import * as Location from 'expo-location';
import { useSocket } from "../context/socketContext";
export function useLocation(timeInterval, distanceInterval) {
const [location, setLocation] = useState(null); // [latitude, longitude]
const { teamSocket } = useSocket();
const LOCATION_TASK_NAME = "background-location-task";
const locationUpdateParameters = {
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
},
};
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);
}
console.log("Sending position :", new_location);
teamSocket.emit("update_position", new_location);
} else {
console.log("No location measured.")
}
}
});
useEffect(() => {
getLocationAuthorization();
}, []);
async function getLocationAuthorization() {
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;
}
}
async function startLocationTracking() {
if (await getLocationAuthorization()) {
if (!(await isTaskRegisteredAsync(LOCATION_TASK_NAME))) {
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, locationUpdateParameters);
console.log("Location tracking started.")
}
}
}
async function stopLocationTracking() {
if (await isTaskRegisteredAsync(LOCATION_TASK_NAME)) {
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
console.log("Location tracking stopped.")
}
}
return [location, getLocationAuthorization, startLocationTracking, stopLocationTracking];
}

View File

@@ -0,0 +1,58 @@
import { useState, } from 'react';
import { Alert } from 'react-native';
import { launchImageLibraryAsync, requestMediaLibraryPermissionsAsync } from 'expo-image-picker';
export function usePickImage() {
const [image, setImage] = useState(null);
const pickImage = async () => {
try {
const permissionResult = await requestMediaLibraryPermissionsAsync();
if (permissionResult.granted === false) {
Alert.alert("Permission refusée", "Activez l'accès au stockage ou à la gallerie dans les paramètres.");
return;
}
let result = await launchImageLibraryAsync({
mediaTypes: ['images'],
allowsMultipleSelection: false,
allowsEditing: true,
aspect: [4, 3],
quality: 1,
});
if (!result.canceled && result) {
setImage(result.assets[0]);
}
else {
console.log('Image picker cancelled.');
}
}
catch (error) {
console.error('Error picking image;', error);
Alert.alert('Erreur', "Une erreur est survenue lors de la sélection d'une image.");
}
}
function sendImage(location) {
if (image) {
let data = new FormData();
data.append('file', {
uri: image.uri,
name: 'photo.jpg',
type: 'image/jpeg',
});
fetch(location , {
method: 'POST',
body: data,
headers: {
'Content-Type': 'multipart/form-data',
},
});
}
}
return {image, pickImage, sendImage};
}

View File

@@ -0,0 +1,35 @@
import { useEffect } from 'react';
import DeviceInfo from 'react-native-device-info';
import { useSocket } from "../context/socketContext";
import { useTeamConnexion } from "../context/teamConnexionContext";
export default function useSendDeviceInfo() {
const batteryUpdateTimeout = 5*60*1000;
const { teamSocket } = useSocket();
const {loggedIn} = useTeamConnexion();
useEffect(() => {
if (!loggedIn) return;
const sendInfo = async () => {
const brand = DeviceInfo.getBrand();
const model = DeviceInfo.getModel();
const name = await DeviceInfo.getDeviceName();
teamSocket.emit('deviceInfo', {model: brand + " " + model, name: name});
};
const sendBattery = async () => {
const level = await DeviceInfo.getBatteryLevel();
teamSocket.emit('batteryUpdate', Math.round(level * 100));
};
sendInfo();
sendBattery();
const batteryCheckInterval = setInterval(() => sendBattery(), batteryUpdateTimeout);
return () => {clearInterval(batteryCheckInterval)};
}, [loggedIn]);
return null;
}

View File

@@ -0,0 +1,72 @@
import { useEffect, useState } from 'react';
import { useLocalStorage } from './useLocalStorage';
const LOGIN_MESSAGE = "login";
const LOGOUT_MESSAGE = "logout";
export function useSocketAuth(socket, passwordName) {
const [loggedIn, setLoggedIn] = useState(false);
const [loading, setLoading] = useState(true);
const [waitingForResponse, setWaitingForResponse] = useState(false);
const [hasTriedSavedPassword, setHasTriedSavedPassword] = useState(false);
const [savedPassword, setSavedPassword, savedPasswordLoading] = useLocalStorage(passwordName, null);
useEffect(() => {
if (!loading && !hasTriedSavedPassword) {
console.log("Try to log in with saved password :", savedPassword);
setWaitingForResponse(true);
const timeout = setTimeout(() => {
console.warn("Server did not respond to login emit.");
setWaitingForResponse(false);
}, 3000);
socket.emit(LOGIN_MESSAGE, savedPassword, (response) => {
clearTimeout(timeout);
console.log(response.message);
setLoggedIn(response.isLoggedIn);
setWaitingForResponse(false);
});
setHasTriedSavedPassword(true);
}
}, [loading]);
function login(password) {
console.log("Try to log in with :", password);
setSavedPassword(password);
setWaitingForResponse(true);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
console.warn("Server did not respond to login emit.");
setWaitingForResponse(false);
reject();
}, 3000);
socket.emit(LOGIN_MESSAGE, password, (response) => {
clearTimeout(timeout);
console.log(response.message);
setLoggedIn(response.isLoggedIn);
setWaitingForResponse(false);
resolve(response);
});
});
}
function logout() {
console.log("Logout");
setSavedPassword(null);
setLoggedIn(false);
socket.emit(LOGOUT_MESSAGE);
}
useEffect(() => {
if(!waitingForResponse && !savedPasswordLoading) {
setLoading(false);
} else {
setLoading(true);
}
}, [waitingForResponse, savedPasswordLoading]);
return {login, logout, password: savedPassword, loggedIn, loading};
}

View File

@@ -0,0 +1,10 @@
import { useEffect } from "react";
export function useSocketListener(socket, event, callback) {
useEffect(() => {
socket.on(event,callback);
return () => {
socket.off(event, callback);
}
}, []);
}

View File

@@ -0,0 +1,21 @@
import { useEffect, useState } from "react";
export function useTimeDifference(refTime, timeout) {
// If refTime is in the past, time will be positive
// If refTime is in the future, time will be negative
// The time is updated every timeout milliseconds
const [time, setTime] = useState(0);
useEffect(() => {
const updateTime = () => {
setTime(Math.floor((Date.now() - refTime) / 1000));
};
updateTime();
const interval = setInterval(updateTime, timeout);
return () => clearInterval(interval);
}, [refTime]);
return [time];
}