mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-04-11 00:30:19 +02:00
Traduction + alias + routing + refactoring
This commit is contained in:
8
mobile/traque-app/app/(auth)/_layout.jsx
Normal file
8
mobile/traque-app/app/(auth)/_layout.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
// Expo
|
||||
import { Slot } from 'expo-router';
|
||||
|
||||
const AuthLayout = () => {
|
||||
return <Slot/>;
|
||||
};
|
||||
|
||||
export default AuthLayout;
|
||||
7
mobile/traque-app/app/(auth)/location-permission.jsx
Normal file
7
mobile/traque-app/app/(auth)/location-permission.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Text } from 'react-native';
|
||||
|
||||
const LocationPermission = () => {
|
||||
return <Text>{"Veuillez activer la géolocalisation en arrière plan dans les paramètres, puis relancez l'application."}</Text>;
|
||||
};
|
||||
|
||||
export default LocationPermission;
|
||||
132
mobile/traque-app/app/(auth)/login.jsx
Normal file
132
mobile/traque-app/app/(auth)/login.jsx
Normal file
@@ -0,0 +1,132 @@
|
||||
// React
|
||||
import { useState } from 'react';
|
||||
import { ScrollView, View, Text, StyleSheet, Image, Alert, TouchableHighlight } from 'react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
// Components
|
||||
import { TouchableImage } from '@/components/common/Image';
|
||||
import { CustomTextInput } from '@/components/common/Input';
|
||||
// Contexts
|
||||
import { useAuth } from "@/contexts/authContext";
|
||||
// Hooks
|
||||
import { usePickImage } from '@/hooks/usePickImage';
|
||||
// Services
|
||||
import { uploadTeamImage } from '@/services/api/image';
|
||||
// Constants
|
||||
import { COLORS } from '@/constants';
|
||||
|
||||
const Login = () => {
|
||||
const { t } = useTranslation();
|
||||
const { login } = useAuth();
|
||||
const { image, pickImage } = usePickImage();
|
||||
const [teamId, setTeamId] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
const regex = /^\d{6}$/;
|
||||
if (!regex.test(teamId)) {
|
||||
setTimeout(() => Alert.alert(t("error.title"), t("error.invalid_team_id")), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await login(teamId);
|
||||
|
||||
if (response) {
|
||||
uploadTeamImage(teamId, image?.uri);
|
||||
setTeamId("");
|
||||
} else {
|
||||
setTimeout(() => Alert.alert(t("error.title"), t("error.unknown_team_id")), 100);
|
||||
}
|
||||
} catch (error) {
|
||||
setTimeout(() => Alert.alert(t("error.title"), t("error.server_connection")), 100);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
<View style={styles.transitionContainer}>
|
||||
<View style={styles.subContainer}>
|
||||
<Image style={styles.logoImage} source={require('@/assets/images/logo/logo_traque.png')}/>
|
||||
<Text style={styles.logoText}>{t("index.header.title")}</Text>
|
||||
</View>
|
||||
<View style={styles.subContainer}>
|
||||
<CustomTextInput value={teamId} inputMode="numeric" placeholder={t("index.form.team_id_input")} style={styles.input} onChangeText={setTeamId}/>
|
||||
</View>
|
||||
<View style={styles.subContainer}>
|
||||
<Text style={{fontSize: 15}}>{t("index.form.image_label")}</Text>
|
||||
<Text style={{fontSize: 13, marginBottom: 3}}>{t("index.form.image_sublabel")}</Text>
|
||||
<TouchableImage source={image ? {uri: image.uri} : require('@/assets/images/missing_image.jpg')} onPress={pickImage}/>
|
||||
</View>
|
||||
<View style={styles.subContainer}>
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableHighlight style={styles.button} onPress={handleSubmit}>
|
||||
<Text style={styles.buttonLabel}>{isSubmitting ? "..." : t("index.form.validate_button")}</Text>
|
||||
</TouchableHighlight>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
paddingVertical: 20,
|
||||
backgroundColor: COLORS.background
|
||||
},
|
||||
transitionContainer: {
|
||||
flexGrow: 1,
|
||||
width: '80%',
|
||||
maxWidth: 600,
|
||||
alignItems: 'center',
|
||||
},
|
||||
subContainer: {
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
logoImage: {
|
||||
width: 130,
|
||||
height: 130,
|
||||
margin: 10,
|
||||
},
|
||||
logoText: {
|
||||
fontSize: 50,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
buttonContainer: {
|
||||
width: "100%",
|
||||
maxWidth: 240,
|
||||
height: 80,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 3,
|
||||
borderWidth: 4,
|
||||
borderColor: '#888',
|
||||
borderRadius: 18
|
||||
},
|
||||
button: {
|
||||
borderRadius: 10,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#555'
|
||||
},
|
||||
buttonLabel: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
8
mobile/traque-app/app/(game)/_layout.jsx
Normal file
8
mobile/traque-app/app/(game)/_layout.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
// Expo
|
||||
import { Slot } from 'expo-router';
|
||||
|
||||
const GameLayout = () => {
|
||||
return <Slot/>;
|
||||
};
|
||||
|
||||
export default GameLayout;
|
||||
31
mobile/traque-app/app/(game)/end.jsx
Normal file
31
mobile/traque-app/app/(game)/end.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// React
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
// Components
|
||||
import { Header } from '@/components/game/Header';
|
||||
// Constants
|
||||
import { COLORS } from '@/constants';
|
||||
|
||||
const End = () => {
|
||||
return (
|
||||
<View style={styles.globalContainer}>
|
||||
<View style={styles.topContainer}>
|
||||
<Header/>
|
||||
<Text>Fin de la partie !</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default End;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
globalContainer: {
|
||||
backgroundColor: COLORS.background,
|
||||
flex: 1,
|
||||
},
|
||||
topContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
padding: 15,
|
||||
}
|
||||
});
|
||||
110
mobile/traque-app/app/(game)/play.jsx
Normal file
110
mobile/traque-app/app/(game)/play.jsx
Normal file
@@ -0,0 +1,110 @@
|
||||
// React
|
||||
import { useState } from 'react';
|
||||
import { View, Alert, StyleSheet } from 'react-native';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
// Components
|
||||
import { Map } from '@/components/common/Map';
|
||||
import { TimerMMSS } from '@/components/common/Timer';
|
||||
import { Show } from '@/components/common/Show';
|
||||
import { PositionMarker } from '@/components/common/Layers';
|
||||
import { IconButton } from '@/components/common/IconButton';
|
||||
import { Header } from '@/components/game/Header';
|
||||
import { TargetInfoDrawer } from '@/components/game/TargetInfoDrawer';
|
||||
import { Toasts } from '@/components/game/Toasts';
|
||||
import { GameZone, StartZone } from '@/components/game/MapLayers';
|
||||
// Contexts
|
||||
import { useTeam } from '@/contexts/teamContext';
|
||||
// Hooks
|
||||
import { useUserState } from '@/hooks/useUserState';
|
||||
// Services
|
||||
import { emitSendPosition } from '@/services/socket/emitters';
|
||||
// Constants
|
||||
import { COLORS, USER_STATE } from '@/constants';
|
||||
|
||||
const Play = () => {
|
||||
const { t } = useTranslation();
|
||||
const { teamInfos, nextZoneDate } = useTeam();
|
||||
const { locationSendDeadline, hasHandicap, enemyLocation, lastSentLocation } = teamInfos;
|
||||
const userState = useUserState();
|
||||
const [bottomContainerHeight, setBottomContainerHeight] = useState(0);
|
||||
|
||||
return (
|
||||
<View style={styles.globalContainer}>
|
||||
<View style={styles.topContainer}>
|
||||
<Header/>
|
||||
<Show when={userState == USER_STATE.PLAYING}>
|
||||
<View style={styles.infoContainer}>
|
||||
<TimerMMSS style={{width: "50%"}} title={t("interface.zone_reduction_label")} date={nextZoneDate} />
|
||||
<TimerMMSS style={{width: "50%"}} title={t("interface.send_position_label")} date={locationSendDeadline} />
|
||||
</View>
|
||||
</Show>
|
||||
</View>
|
||||
<View style={styles.bottomContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
|
||||
<Map>
|
||||
<Show when={userState == USER_STATE.PLACEMENT}>
|
||||
<StartZone/>
|
||||
</Show>
|
||||
<Show when={userState == USER_STATE.PLAYING}>
|
||||
<GameZone/>
|
||||
</Show>
|
||||
<Show when={userState == USER_STATE.PLAYING && !hasHandicap}>
|
||||
<PositionMarker position={lastSentLocation} color={"grey"} onPress={() => Alert.alert(t("interface.map.previous_marker_title"), t("interface.map.previous_marker_description"))} />
|
||||
<PositionMarker position={enemyLocation} color={"red"} onPress={() => Alert.alert(t("interface.map.enemy_marker_title"), t("interface.map.enemy_marker_description"))} />
|
||||
</Show>
|
||||
</Map>
|
||||
<LinearGradient colors={['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)']} style={styles.gradient}/>
|
||||
<Show when={userState == USER_STATE.PLAYING && !hasHandicap}>
|
||||
<IconButton style={styles.updatePosition} source={require("@/assets/images/update_position.png")} onPress={emitSendPosition} />
|
||||
</Show>
|
||||
<Toasts/>
|
||||
</View>
|
||||
<Show when={userState == USER_STATE.PLAYING}>
|
||||
<TargetInfoDrawer height={bottomContainerHeight}/>
|
||||
</Show>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Play;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
globalContainer: {
|
||||
backgroundColor: COLORS.background,
|
||||
flex: 1,
|
||||
},
|
||||
topContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
padding: 15,
|
||||
},
|
||||
infoContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
marginTop: 15
|
||||
},
|
||||
bottomContainer: {
|
||||
flex: 1,
|
||||
borderTopLeftRadius: 30,
|
||||
borderTopRightRadius: 30,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
updatePosition: {
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
bottom: 80,
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: 'white',
|
||||
borderWidth: 4,
|
||||
borderColor: 'black'
|
||||
},
|
||||
gradient: {
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: 40,
|
||||
}
|
||||
});
|
||||
31
mobile/traque-app/app/(game)/wait.jsx
Normal file
31
mobile/traque-app/app/(game)/wait.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
// React
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
// Components
|
||||
import { Header } from '@/components/game/Header';
|
||||
// Constants
|
||||
import { COLORS } from '@/constants';
|
||||
|
||||
const Wait = () => {
|
||||
return (
|
||||
<View style={styles.globalContainer}>
|
||||
<View style={styles.topContainer}>
|
||||
<Header/>
|
||||
<Text>Veuillez patienter, la partie va bientôt commencer !</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Wait;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
globalContainer: {
|
||||
backgroundColor: COLORS.background,
|
||||
flex: 1,
|
||||
},
|
||||
topContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
padding: 15,
|
||||
}
|
||||
});
|
||||
@@ -1,17 +1,86 @@
|
||||
// React
|
||||
import { useEffect } from 'react';
|
||||
// Expo
|
||||
import { Slot } from 'expo-router';
|
||||
import { Slot, useRouter, usePathname } from 'expo-router';
|
||||
// Contexts
|
||||
import { AuthProvider } from "../src/contexts/authContext";
|
||||
import { TeamProvider } from "../src/contexts/teamContext";
|
||||
import { AuthProvider } from "@/contexts/authContext";
|
||||
import { TeamProvider } from "@/contexts/teamContext";
|
||||
// Services
|
||||
import { startLocationTracking , stopLocationTracking } from '@/services/tasks/backgroundLocation';
|
||||
// Constants
|
||||
import { USER_STATE } from '@/constants';
|
||||
// Traduction
|
||||
import '@/i18n/config';
|
||||
import { useUserState } from '@/hooks/useUserState';
|
||||
|
||||
const Layout = () => {
|
||||
const NavigationManager = () => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const userState = useUserState();
|
||||
|
||||
// Location tracking
|
||||
useEffect(() => {
|
||||
const trackingStates = [
|
||||
USER_STATE.WAITING,
|
||||
USER_STATE.PLACEMENT,
|
||||
USER_STATE.PLAYING,
|
||||
USER_STATE.CAPTURED,
|
||||
USER_STATE.FINISHED
|
||||
];
|
||||
|
||||
if (trackingStates.includes(userState)) {
|
||||
startLocationTracking();
|
||||
} else {
|
||||
stopLocationTracking();
|
||||
}
|
||||
}, [userState]);
|
||||
|
||||
// Routing
|
||||
useEffect(() => {
|
||||
let targetRoute;
|
||||
|
||||
switch (userState) {
|
||||
case USER_STATE.LOADING:
|
||||
return;
|
||||
case USER_STATE.NO_LOCATION:
|
||||
targetRoute = "/location-permission";
|
||||
break;
|
||||
case USER_STATE.OFFLINE:
|
||||
targetRoute = "/login";
|
||||
break;
|
||||
case USER_STATE.WAITING:
|
||||
targetRoute = "/wait";
|
||||
break;
|
||||
case USER_STATE.PLACEMENT:
|
||||
case USER_STATE.PLAYING:
|
||||
targetRoute = "/play";
|
||||
break;
|
||||
case USER_STATE.CAPTURED:
|
||||
case USER_STATE.FINISHED:
|
||||
targetRoute = "/end";
|
||||
break;
|
||||
default:
|
||||
targetRoute = "/";
|
||||
break;
|
||||
}
|
||||
|
||||
if (pathname !== targetRoute) {
|
||||
router.replace(targetRoute);
|
||||
}
|
||||
}, [router, pathname, userState]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const RootLayout = () => {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<TeamProvider>
|
||||
<Slot/>
|
||||
<NavigationManager/>
|
||||
</TeamProvider>
|
||||
</AuthProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
export default RootLayout;
|
||||
|
||||
@@ -1,121 +1,5 @@
|
||||
// React
|
||||
import { useState, useEffect } from 'react';
|
||||
import { ScrollView, View, Text, StyleSheet, Image, Alert } from 'react-native';
|
||||
// Expo
|
||||
import { useRouter } from 'expo-router';
|
||||
// Components
|
||||
import { CustomButton } from '../src/components/button';
|
||||
import { CustomImage } from '../src/components/image';
|
||||
import { CustomTextInput } from '../src/components/input';
|
||||
// Contexts
|
||||
import { useAuth } from "../src/contexts/authContext";
|
||||
// Hooks
|
||||
import { usePickImage } from '../src/hooks/usePickImage';
|
||||
// Services
|
||||
import { uploadTeamImage } from '../src/services/api/image';
|
||||
import { getLocationAuthorization, stopLocationTracking } from '../src/services/tasks/backgroundLocation';
|
||||
// Constants
|
||||
import { COLORS } from '../src/constants';
|
||||
|
||||
const Index = () => {
|
||||
const router = useRouter();
|
||||
const { loggedIn, login } = useAuth();
|
||||
const { image, pickImage } = usePickImage();
|
||||
const [teamId, setTeamId] = useState("");
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Disbaling location tracking and asking permissions
|
||||
useEffect(() => {
|
||||
stopLocationTracking();
|
||||
getLocationAuthorization();
|
||||
}, []);
|
||||
|
||||
// Routeur
|
||||
useEffect(() => {
|
||||
if (loggedIn) {
|
||||
router.replace("/interface");
|
||||
}
|
||||
}, [router, loggedIn, image]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (isSubmitting || !getLocationAuthorization()) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
const regex = /^\d{6}$/;
|
||||
if (!regex.test(teamId)) {
|
||||
setTimeout(() => Alert.alert("Erreur", "Veuillez entrer un ID d'équipe valide."), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
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);
|
||||
}
|
||||
} catch (error) {
|
||||
setTimeout(() => Alert.alert("Échec", "La connexion au serveur a échoué."), 100);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView contentContainerStyle={styles.container}>
|
||||
<View style={styles.transitionContainer}>
|
||||
<View style={styles.subContainer}>
|
||||
<Image style={styles.logoImage} source={require('../src/assets/images/logo/logo_traque.png')}/>
|
||||
<Text style={styles.logoText}>LA TRAQUE</Text>
|
||||
</View>
|
||||
<View style={styles.subContainer}>
|
||||
<CustomTextInput value={teamId} inputMode="numeric" placeholder="ID de l'équipe" style={styles.input} onChangeText={setTeamId}/>
|
||||
</View>
|
||||
<View style={styles.subContainer}>
|
||||
<Text style={{fontSize: 15}}>Appuyer pour changer la photo d'équipe</Text>
|
||||
<Text style={{fontSize: 13, marginBottom: 3}}>(Le haut du corps doit être visible)</Text>
|
||||
<CustomImage source={image ? {uri: image.uri} : require('../src/assets/images/missing_image.jpg')} onPress={pickImage}/>
|
||||
</View>
|
||||
<View style={styles.subContainer}>
|
||||
<CustomButton label={isSubmitting ? "..." : "Valider"} onPress={handleSubmit}/>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
paddingVertical: 20,
|
||||
backgroundColor: COLORS.background
|
||||
},
|
||||
transitionContainer: {
|
||||
flexGrow: 1,
|
||||
width: '80%',
|
||||
maxWidth: 600,
|
||||
alignItems: 'center',
|
||||
},
|
||||
subContainer: {
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
},
|
||||
logoImage: {
|
||||
width: 130,
|
||||
height: 130,
|
||||
margin: 10,
|
||||
},
|
||||
logoText: {
|
||||
fontSize: 50,
|
||||
fontWeight: 'bold',
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
// React
|
||||
import { useState, useEffect, useMemo, Fragment } from 'react';
|
||||
import { View, Text, Image, Alert, StyleSheet, TouchableOpacity } from 'react-native';
|
||||
// Expo
|
||||
import { useRouter } from 'expo-router';
|
||||
// Components
|
||||
import { CustomMap } from '../src/components/map';
|
||||
import { Drawer } from '../src/components/drawer';
|
||||
import { TimerMMSS } from '../src/components/timer';
|
||||
// Contexts
|
||||
import { useAuth } from '../src/contexts/authContext';
|
||||
import { useTeam } from '../src/contexts/teamContext';
|
||||
// Hooks
|
||||
import { useTimeDifference } from '../src/hooks/useTimeDifference';
|
||||
// Services
|
||||
import { emitSendPosition } from '../src/services/socket/emitters';
|
||||
import { startLocationTracking } from '../src/services/tasks/backgroundLocation';
|
||||
// Util
|
||||
import { secondsToMMSS } from '../src/utils/functions';
|
||||
// Constants
|
||||
import { GAME_STATE, COLORS } from '../src/constants';
|
||||
|
||||
const Interface = () => {
|
||||
const router = useRouter();
|
||||
const {teamInfos, messages, nextZoneDate, gameState} = useTeam();
|
||||
const {name, ready, captured, locationSendDeadline, outOfZone, outOfZoneDeadline, hasHandicap, enemyHasHandicap} = teamInfos;
|
||||
const { loggedIn, logout } = useAuth();
|
||||
const [timeLeftSendLocation] = useTimeDifference(locationSendDeadline, 1000);
|
||||
const [timeLeftNextZone] = useTimeDifference(nextZoneDate, 1000);
|
||||
const [timeLeftOutOfZone] = useTimeDifference(outOfZoneDeadline, 1000);
|
||||
const [bottomContainerHeight, setBottomContainerHeight] = useState(0);
|
||||
|
||||
const statusMessage = useMemo(() => {
|
||||
switch (gameState) {
|
||||
case GAME_STATE.SETUP:
|
||||
return messages?.waiting || "Préparation de la partie";
|
||||
case GAME_STATE.PLACEMENT:
|
||||
return "Phase de placement";
|
||||
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 GAME_STATE.FINISHED:
|
||||
return `Vous avez ${captured ? (messages?.loser || "perdu...") : (messages?.winner || "gagné !")}`;
|
||||
default:
|
||||
return "Inconnue";
|
||||
}
|
||||
}, [gameState, messages, outOfZone, hasHandicap, timeLeftOutOfZone, captured]);
|
||||
|
||||
// Router
|
||||
useEffect(() => {
|
||||
if (!loggedIn) {
|
||||
router.replace("/");
|
||||
}
|
||||
}, [router, loggedIn]);
|
||||
|
||||
// Activating geolocation tracking
|
||||
useEffect(() => {
|
||||
startLocationTracking();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={styles.globalContainer}>
|
||||
<View style={styles.topContainer}>
|
||||
<View style={styles.topheadContainer}>
|
||||
<TouchableOpacity style={{width: 40, height: 40}} onPress={logout}>
|
||||
<Image source={require('../src/assets/images/logout.png')} style={{width: 40, height: 40}} resizeMode="contain"></Image>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={{width: 40, height: 40}} onPress={() => Alert.alert("Settings")}>
|
||||
<Image source={require('../src/assets/images/cogwheel.png')} style={{width: 40, height: 40}} resizeMode="contain"></Image>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.teamNameContainer}>
|
||||
<Text style={{fontSize: 36, fontWeight: "bold", textAlign: "center"}}>{(name ?? "Indisponible")}</Text>
|
||||
</View>
|
||||
<View style={styles.logContainer}>
|
||||
<TouchableOpacity style={styles.gameState}>
|
||||
<Text style={{fontSize: 18}}>{statusMessage}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.infoContainer}>
|
||||
{ gameState == GAME_STATE.PLACEMENT &&
|
||||
<View style={[styles.readyIndicator, {backgroundColor: ready ? "#3C3" : "#C33"}]}>
|
||||
<Text style={{color: '#fff', fontSize: 16}}>{ready ? "Placé" : "Non placé"}</Text>
|
||||
</View>
|
||||
}
|
||||
{ gameState == GAME_STATE.PLAYING && !captured && <Fragment>
|
||||
<TimerMMSS style={{width: "50%"}} title={"Réduction de la zone dans"} seconds={-timeLeftNextZone} />
|
||||
<TimerMMSS style={{width: "50%"}} title={"Position envoyée dans"} seconds={!hasHandicap ? -timeLeftSendLocation: 0} />
|
||||
</Fragment>}
|
||||
</View>
|
||||
{ enemyHasHandicap &&
|
||||
<Text style={{fontSize: 18, marginTop: 6, fontWeight: "bold"}}>Position ennemie révélée en continue !</Text>
|
||||
}
|
||||
</View>
|
||||
<View style={styles.bottomContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
|
||||
<CustomMap/>
|
||||
{ gameState == GAME_STATE.PLAYING && !captured && !hasHandicap &&
|
||||
<TouchableOpacity style={styles.updatePosition} onPress={emitSendPosition}>
|
||||
<Image source={require("../src/assets/images/update_position.png")} style={{width: 40, height: 40}} resizeMode="contain"></Image>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
{ gameState == GAME_STATE.PLAYING && !captured &&
|
||||
<Drawer height={bottomContainerHeight}/>
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Interface;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
globalContainer: {
|
||||
backgroundColor: COLORS.background,
|
||||
flex: 1,
|
||||
},
|
||||
topContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
padding: 15,
|
||||
},
|
||||
topheadContainer: {
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
teamNameContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
logContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: 15
|
||||
},
|
||||
gameState: {
|
||||
borderWidth: 2,
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
backgroundColor: 'white',
|
||||
padding: 10,
|
||||
},
|
||||
infoContainer: {
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
marginTop: 15
|
||||
},
|
||||
readyIndicator: {
|
||||
width: "100%",
|
||||
maxWidth: 240,
|
||||
height: 61,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 3,
|
||||
borderRadius: 10
|
||||
},
|
||||
bottomContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
updatePosition: {
|
||||
position: 'absolute',
|
||||
right: 30,
|
||||
bottom: 80,
|
||||
width: 60,
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: 'white',
|
||||
borderWidth: 4,
|
||||
borderColor: 'black',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user