mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-04-10 16:30:18 +02:00
Working EN traduction + wait page + permission page
This commit is contained in:
@@ -9,7 +9,7 @@ Météo : 8°C, pas de pluie, brouillard (visibilité <400m), vent 10km/h
|
|||||||
Problèmes
|
Problèmes
|
||||||
|
|
||||||
[ ] Une équipe perdait sans arrêt la connection avec le serveur
|
[ ] Une équipe perdait sans arrêt la connection avec le serveur
|
||||||
[ ] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif)
|
[x] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif)
|
||||||
[x] La photo d'une des équipes ne parvenait pas jusqu'au serveur. Tout semblait normal pour l'équipe. (Potentiellement un problème
|
[x] La photo d'une des équipes ne parvenait pas jusqu'au serveur. Tout semblait normal pour l'équipe. (Potentiellement un problème
|
||||||
de conversion : 046512 -> 46512)
|
de conversion : 046512 -> 46512)
|
||||||
[x] Le focus sur une équipe dans la page principale des admins est à revoir. Par exemple, le zoom seul devrait désactiver le focus
|
[x] Le focus sur une équipe dans la page principale des admins est à revoir. Par exemple, le zoom seul devrait désactiver le focus
|
||||||
@@ -19,7 +19,6 @@ voyants des équipes encore en jeu soit mis en avant.
|
|||||||
[x] La visibilité des éléments de la map en calque satellite est mauvaise. Il faut revoir les couleurs.
|
[x] La visibilité des éléments de la map en calque satellite est mauvaise. Il faut revoir les couleurs.
|
||||||
[x] Les zones polygonales s'affichent mal sur la version prod de l'app mobile. Les anciennes n'ont pas l'air de disparaitre.
|
[x] Les zones polygonales s'affichent mal sur la version prod de l'app mobile. Les anciennes n'ont pas l'air de disparaitre.
|
||||||
[x] La zone de jeu (en rouge) est mal comprise, les joueurs évitent de rester dedans alors qu'ils ont le droit.
|
[x] La zone de jeu (en rouge) est mal comprise, les joueurs évitent de rester dedans alors qu'ils ont le droit.
|
||||||
[ ] L'application n'est pas pleinement intuitive, par exemple le bouton pour actualiser sa position ou alors les icônes des stats.
|
|
||||||
|
|
||||||
|
|
||||||
À faire
|
À faire
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Cette traque est la première à se faire avec le nouveau site web et la version
|
|||||||
Problèmes
|
Problèmes
|
||||||
|
|
||||||
[ ] Une équipe perdait sans arrêt la connection avec le serveur
|
[ ] Une équipe perdait sans arrêt la connection avec le serveur
|
||||||
[ ] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif)
|
[x] La position en arrière plan, téléphone éteint par exemple, n'avait pas l'air de fonctionner (une équipe n'avait pas de notif)
|
||||||
[ ] Aux deux tiers de la partie toutes les équipes ont été déconnectée en même temps mais ont pu se reconnecter. Ça s'est produit
|
[ ] Aux deux tiers de la partie toutes les équipes ont été déconnectée en même temps mais ont pu se reconnecter. Ça s'est produit
|
||||||
sans raisons apparentes
|
sans raisons apparentes
|
||||||
[ ] Une équipe avait deux téléphones connectés sauf que le premier ne marchait pas bien. Par ailleurs, l'équipe disaient n'avoir
|
[ ] Une équipe avait deux téléphones connectés sauf que le premier ne marchait pas bien. Par ailleurs, l'équipe disaient n'avoir
|
||||||
|
|||||||
@@ -11,18 +11,13 @@
|
|||||||
- [x] Centrer la map sur la position à l'ouverture + bouton centrage
|
- [x] Centrer la map sur la position à l'ouverture + bouton centrage
|
||||||
- [x] Indiquer que l'équipe est hors zone.
|
- [x] Indiquer que l'équipe est hors zone.
|
||||||
- [x] Mettre les stats dans le tiroir (distance, temps, vitesse moy, nb captures, nb envoi)
|
- [x] Mettre les stats dans le tiroir (distance, temps, vitesse moy, nb captures, nb envoi)
|
||||||
|
- [x] Traduction anglaise
|
||||||
- [ ] Implémenter des notifs lors du background (hors zone, position envoyée, update zone)
|
- [ ] Implémenter des notifs lors du background (hors zone, position envoyée, update zone)
|
||||||
- [ ] Ajouter les logs de la partie
|
|
||||||
- [ ] Créer le menu paramètre (idées de section : langue, photo équipe, notifs, mode sombre, unitées)
|
- [ ] Créer le menu paramètre (idées de section : langue, photo équipe, notifs, mode sombre, unitées)
|
||||||
- [ ] Afficher la trajectoire passée sur la carte (désactivable)
|
- [ ] Afficher la trajectoire passée sur la carte (désactivable)
|
||||||
- [ ] Afficher les évènements passés sur la carte (captures, envois, départ) (désactivable)
|
- [ ] Afficher les évènements passés sur la carte (captures, envois, départ) (désactivable)
|
||||||
- [ ] Permettre le changement du style de la carte (schéma, satellite, relief etc)
|
- [ ] Permettre le changement du style de la carte (schéma, satellite, relief etc)
|
||||||
- [ ] Ajouter imprécision de la position au besoin (comme sur google maps)
|
- [ ] Ajouter imprécision de la position au besoin (comme sur google maps)
|
||||||
- [ ] Synchroniser les horloges sur l'interface
|
- [ ] Synchroniser les horloges sur l'interface
|
||||||
- [ ] Avoir un récap des évènement de la partie
|
- [ ] Améliorer l'intuitivité de l'interface (bouton d'actualisation / stats)
|
||||||
- [ ] Publier sur le playstore
|
- [ ] Publier sur le playstore
|
||||||
|
|
||||||
## Autres idées
|
|
||||||
|
|
||||||
- Améliorer l'accessibilité (traduction anglaise notamment).
|
|
||||||
- Améliorer l'UI.
|
|
||||||
|
|||||||
@@ -1,7 +1,39 @@
|
|||||||
import { Text } from 'react-native';
|
//React
|
||||||
|
import { StyleSheet, Text, View, Image } from 'react-native';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const LocationPermission = () => {
|
const LocationPermission = () => {
|
||||||
return <Text>{"Veuillez activer la géolocalisation en arrière plan dans les paramètres, puis relancez l'application."}</Text>;
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Image style={styles.image} source={require("@/assets/images/placement.png")} />
|
||||||
|
<Text style={styles.title}>{t("location-permission.title")}</Text>
|
||||||
|
<Text style={styles.subtitle}>{t("location-permission.subtitle")}</Text>
|
||||||
|
</View>
|
||||||
|
</>);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LocationPermission;
|
export default LocationPermission;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
padding: 20,
|
||||||
|
gap: 20
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
marginTop: -100,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: "bold",
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 15,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// React
|
// React
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { ScrollView, View, Text, StyleSheet, Image, Alert, TouchableHighlight } from 'react-native';
|
import { Keyboard, ScrollView, View, Text, StyleSheet, Image, Alert, TouchableHighlight } from 'react-native';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
// Components
|
// Components
|
||||||
import { TouchableImage } from '@/components/common/Image';
|
import { TouchableImage } from '@/components/common/Image';
|
||||||
@@ -28,7 +28,9 @@ const Login = () => {
|
|||||||
|
|
||||||
const regex = /^\d{6}$/;
|
const regex = /^\d{6}$/;
|
||||||
if (!regex.test(teamId)) {
|
if (!regex.test(teamId)) {
|
||||||
setTimeout(() => Alert.alert(t("error.title"), t("error.invalid_team_id")), 100);
|
Keyboard.dismiss();
|
||||||
|
Alert.alert(t("error.default.title"), t("error.default.invalid_team_id"));
|
||||||
|
setIsSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,10 +41,12 @@ const Login = () => {
|
|||||||
uploadTeamImage(teamId, image?.uri);
|
uploadTeamImage(teamId, image?.uri);
|
||||||
setTeamId("");
|
setTeamId("");
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => Alert.alert(t("error.title"), t("error.unknown_team_id")), 100);
|
Keyboard.dismiss();
|
||||||
|
Alert.alert(t("error.default.title"), t("error.default.unknown_team_id"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setTimeout(() => Alert.alert(t("error.title"), t("error.server_connection")), 100);
|
Keyboard.dismiss();
|
||||||
|
Alert.alert(t("error.default.title"), t("error.default.server_connection"));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@@ -53,20 +57,20 @@ const Login = () => {
|
|||||||
<View style={styles.transitionContainer}>
|
<View style={styles.transitionContainer}>
|
||||||
<View style={styles.subContainer}>
|
<View style={styles.subContainer}>
|
||||||
<Image style={styles.logoImage} source={require('@/assets/images/logo/logo_traque.png')}/>
|
<Image style={styles.logoImage} source={require('@/assets/images/logo/logo_traque.png')}/>
|
||||||
<Text style={styles.logoText}>{t("index.header.title")}</Text>
|
<Text style={styles.logoText}>{t("login.header.title")}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.subContainer}>
|
<View style={styles.subContainer}>
|
||||||
<CustomTextInput value={teamId} inputMode="numeric" placeholder={t("index.form.team_id_input")} style={styles.input} onChangeText={setTeamId}/>
|
<CustomTextInput value={teamId} inputMode="numeric" placeholder={t("login.form.team_id_input")} style={styles.input} onChangeText={setTeamId}/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.subContainer}>
|
<View style={styles.subContainer}>
|
||||||
<Text style={{fontSize: 15}}>{t("index.form.image_label")}</Text>
|
<Text style={{fontSize: 15}}>{t("login.form.image_label")}</Text>
|
||||||
<Text style={{fontSize: 13, marginBottom: 3}}>{t("index.form.image_sublabel")}</Text>
|
<Text style={{fontSize: 13, marginBottom: 3}}>{t("login.form.image_sublabel")}</Text>
|
||||||
<TouchableImage source={image ? {uri: image.uri} : require('@/assets/images/missing_image.jpg')} onPress={pickImage}/>
|
<TouchableImage source={image ? {uri: image.uri} : require('@/assets/images/missing_image.jpg')} onPress={pickImage}/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.subContainer}>
|
<View style={styles.subContainer}>
|
||||||
<View style={styles.buttonContainer}>
|
<View style={styles.buttonContainer}>
|
||||||
<TouchableHighlight style={styles.button} onPress={handleSubmit}>
|
<TouchableHighlight style={styles.button} onPress={handleSubmit}>
|
||||||
<Text style={styles.buttonLabel}>{isSubmitting ? "..." : t("index.form.validate_button")}</Text>
|
<Text style={styles.buttonLabel}>{isSubmitting ? "..." : t("login.form.validate_button")}</Text>
|
||||||
</TouchableHighlight>
|
</TouchableHighlight>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ const Play = () => {
|
|||||||
<Header/>
|
<Header/>
|
||||||
<Show when={userState == USER_STATE.PLAYING}>
|
<Show when={userState == USER_STATE.PLAYING}>
|
||||||
<View style={styles.infoContainer}>
|
<View style={styles.infoContainer}>
|
||||||
<TimerMMSS style={{width: "50%"}} title={t("interface.zone_reduction_label")} date={nextZoneDate} />
|
<TimerMMSS style={{width: "50%"}} title={t("play.info.zone_reduction_label")} date={nextZoneDate} />
|
||||||
<TimerMMSS style={{width: "50%"}} title={t("interface.send_position_label")} date={locationSendDeadline} />
|
<TimerMMSS style={{width: "50%"}} title={t("play.info.send_position_label")} date={locationSendDeadline} />
|
||||||
</View>
|
</View>
|
||||||
</Show>
|
</Show>
|
||||||
</View>
|
</View>
|
||||||
@@ -49,8 +49,8 @@ const Play = () => {
|
|||||||
<GameZone/>
|
<GameZone/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={userState == USER_STATE.PLAYING && !hasHandicap}>
|
<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={lastSentLocation} color={"grey"} onPress={() => Alert.alert(t("play.map.previous_marker_title"), t("play.map.previous_marker_description"))} />
|
||||||
<PositionMarker position={enemyLocation} color={"red"} onPress={() => Alert.alert(t("interface.map.enemy_marker_title"), t("interface.map.enemy_marker_description"))} />
|
<PositionMarker position={enemyLocation} color={"red"} onPress={() => Alert.alert(t("play.map.enemy_marker_title"), t("play.map.enemy_marker_description"))} />
|
||||||
</Show>
|
</Show>
|
||||||
</Map>
|
</Map>
|
||||||
<LinearGradient colors={['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)']} style={styles.gradient}/>
|
<LinearGradient colors={['rgba(0,0,0,0.3)', 'rgba(0,0,0,0)']} style={styles.gradient}/>
|
||||||
|
|||||||
@@ -1,16 +1,35 @@
|
|||||||
// React
|
// React
|
||||||
import { View, Text, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet, Image } from 'react-native';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
// Components
|
// Components
|
||||||
import { Header } from '@/components/game/Header';
|
import { Header } from '@/components/game/Header';
|
||||||
// Constants
|
// Constants
|
||||||
import { COLORS } from '@/constants';
|
import { COLORS } from '@/constants';
|
||||||
|
|
||||||
const Wait = () => {
|
const Wait = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.globalContainer}>
|
<View style={styles.globalContainer}>
|
||||||
<View style={styles.topContainer}>
|
<Header/>
|
||||||
<Header/>
|
<View style={styles.rulesContainer}>
|
||||||
<Text>Veuillez patienter, la partie va bientôt commencer !</Text>
|
<Text style={styles.title}>{t("wait.title")}</Text>
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Image style={styles.image} source={require("@/assets/images/flag.png")} />
|
||||||
|
<Text style={styles.description}>{t("wait.placement_rule")}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.description}>{t("wait.capture_rule")}</Text>
|
||||||
|
<Image style={styles.image} source={require("@/assets/images/target/black.png")} />
|
||||||
|
</View>
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Image style={styles.image} source={require("@/assets/images/running.png")} />
|
||||||
|
<Text style={styles.description}>{t("wait.zone_rule")}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.section}>
|
||||||
|
<Text style={styles.description}>{t("wait.team_rule")}</Text>
|
||||||
|
<Image style={styles.image} source={require("@/assets/images/team.png")} />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -22,10 +41,35 @@ const styles = StyleSheet.create({
|
|||||||
globalContainer: {
|
globalContainer: {
|
||||||
backgroundColor: COLORS.background,
|
backgroundColor: COLORS.background,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
padding: 20,
|
||||||
},
|
},
|
||||||
topContainer: {
|
rulesContainer: {
|
||||||
width: '100%',
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: 15,
|
gap: 30
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
backgroundColor: "white",
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: "bold",
|
||||||
|
borderWidth: 2,
|
||||||
|
borderRadius: 10,
|
||||||
|
padding: 10,
|
||||||
|
},
|
||||||
|
section: {
|
||||||
|
width: '100%',
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: 20,
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
flex: 1,
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
// React
|
// React
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { View, StyleSheet } from 'react-native';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
// Expo
|
// Expo
|
||||||
import { Slot, useRouter, usePathname } from 'expo-router';
|
import { Slot, useRouter, usePathname } from 'expo-router';
|
||||||
|
// Components
|
||||||
|
import { IconButton } from '@/components/common/IconButton';
|
||||||
// Contexts
|
// Contexts
|
||||||
import { AuthProvider } from "@/contexts/authContext";
|
import { AuthProvider } from "@/contexts/authContext";
|
||||||
import { TeamProvider } from "@/contexts/teamContext";
|
import { TeamProvider } from "@/contexts/teamContext";
|
||||||
|
// Hook
|
||||||
|
import { useUserState } from '@/hooks/useUserState';
|
||||||
// Services
|
// Services
|
||||||
import { startLocationTracking , stopLocationTracking } from '@/services/tasks/backgroundLocation';
|
import { startLocationTracking , stopLocationTracking } from '@/services/tasks/backgroundLocation';
|
||||||
// Constants
|
// Constants
|
||||||
import { USER_STATE } from '@/constants';
|
import { USER_STATE } from '@/constants';
|
||||||
// Traduction
|
// Traduction
|
||||||
import '@/i18n/config';
|
import '@/i18n/config';
|
||||||
import { useUserState } from '@/hooks/useUserState';
|
|
||||||
|
|
||||||
const NavigationManager = () => {
|
const NavigationManager = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -72,15 +77,43 @@ const NavigationManager = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Language = () => {
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
|
const toggleLanguage = () => {
|
||||||
|
i18n.changeLanguage(i18n.language === 'fr' ? 'en' : 'fr');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.languageButton}>
|
||||||
|
<IconButton source={require('@/assets/images/language.png')} onPress={toggleLanguage} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const RootLayout = () => {
|
const RootLayout = () => {
|
||||||
return (
|
return (
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<TeamProvider>
|
<TeamProvider>
|
||||||
<Slot/>
|
<Slot/>
|
||||||
<NavigationManager/>
|
<NavigationManager/>
|
||||||
|
<Language/>
|
||||||
</TeamProvider>
|
</TeamProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RootLayout;
|
export default RootLayout;
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
languageButton: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
backgroundColor: "rgb(126, 182, 199)",
|
||||||
|
borderBottomLeftRadius: 20,
|
||||||
|
padding: 5,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
BIN
mobile/traque-app/src/assets/images/flag.png
Normal file
BIN
mobile/traque-app/src/assets/images/flag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
mobile/traque-app/src/assets/images/language.png
Normal file
BIN
mobile/traque-app/src/assets/images/language.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
mobile/traque-app/src/assets/images/team.png
Normal file
BIN
mobile/traque-app/src/assets/images/team.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -1,5 +1,6 @@
|
|||||||
// React
|
// React
|
||||||
import { View, Text, Alert, StyleSheet } from 'react-native';
|
import { View, Text, StyleSheet } from 'react-native';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
// Contexts
|
// Contexts
|
||||||
import { useAuth } from '@/contexts/authContext';
|
import { useAuth } from '@/contexts/authContext';
|
||||||
import { useTeam } from '@/contexts/teamContext';
|
import { useTeam } from '@/contexts/teamContext';
|
||||||
@@ -7,18 +8,16 @@ import { useTeam } from '@/contexts/teamContext';
|
|||||||
import { IconButton } from '@/components/common/IconButton';
|
import { IconButton } from '@/components/common/IconButton';
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { logout } = useAuth();
|
const { logout } = useAuth();
|
||||||
const { teamInfos } = useTeam();
|
const { teamInfos } = useTeam();
|
||||||
const { name } = teamInfos;
|
const { name } = teamInfos;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.buttonsContainer}>
|
<IconButton source={require('@/assets/images/logout.png')} onPress={logout} />
|
||||||
<IconButton source={require('@/assets/images/logout.png')} onPress={logout} />
|
|
||||||
<IconButton source={require('@/assets/images/cogwheel.png')} onPress={() => Alert.alert("Settings")} />
|
|
||||||
</View>
|
|
||||||
<View style={styles.nameContainer}>
|
<View style={styles.nameContainer}>
|
||||||
<Text style={styles.name}>{name ?? "Inconnue"}</Text>
|
<Text style={styles.name}>{name ?? t("common.no_value")}</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
@@ -27,17 +26,12 @@ export const Header = () => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
buttonsContainer: {
|
|
||||||
width: "100%",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: 'space-between'
|
|
||||||
},
|
},
|
||||||
nameContainer: {
|
nameContainer: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center',
|
||||||
|
marginBottom: 20
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
fontSize: 36,
|
fontSize: 36,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const StartZone = () => {
|
|||||||
const { startingArea } = teamInfos;
|
const { startingArea } = teamInfos;
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (startingArea) return null;
|
if (!startingArea) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Circle
|
<Circle
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// React
|
// React
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { View, Text, Image, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
import { Keyboard, View, Text, Image, StyleSheet, TouchableOpacity, Alert } from 'react-native';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
// Components
|
// Components
|
||||||
import { ExpandableImage } from '@/components/common/Image';
|
import { ExpandableImage } from '@/components/common/Image';
|
||||||
@@ -31,14 +31,17 @@ export const TargetInfoDrawer = ({ height }) => {
|
|||||||
emitCapture(enemyCaptureCode)
|
emitCapture(enemyCaptureCode)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.hasCaptured) {
|
if (response.hasCaptured) {
|
||||||
Alert.alert("Bravo !", "Vous avez réussi à capturer votre cible. Une nouvelle cible vient de vous être attribuée.");
|
Keyboard.dismiss();
|
||||||
|
Alert.alert(t("info.success.title"), t("info.success.capture_success"));
|
||||||
setEnemyCaptureCode("");
|
setEnemyCaptureCode("");
|
||||||
} else {
|
} else {
|
||||||
Alert.alert("Échec !", "Le code que vous venez de rentrer n'est pas celui de votre cible.");
|
Keyboard.dismiss();
|
||||||
|
Alert.alert(t("info.failure.title"), t("info.failure.capture_failure"));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
Alert.alert(t("error.title"), t("error.server_connection"));
|
Keyboard.dismiss();
|
||||||
|
Alert.alert(t("error.default.title"), t("error.default.server_connection"));
|
||||||
})
|
})
|
||||||
.finally(() => setIsCapturing(false));
|
.finally(() => setIsCapturing(false));
|
||||||
};
|
};
|
||||||
@@ -46,16 +49,16 @@ export const TargetInfoDrawer = ({ height }) => {
|
|||||||
return (
|
return (
|
||||||
<Drawer height={height}>
|
<Drawer height={height}>
|
||||||
<Text style={{fontSize: 22, fontWeight: "bold", textAlign: "center"}}>
|
<Text style={{fontSize: 22, fontWeight: "bold", textAlign: "center"}}>
|
||||||
{t("interface.drawer.capture_code", {name: name ?? t("general.no_value"), code: String(captureCode).padStart(4,"0")})}
|
{t("play.drawer.capture_code", {name: name ?? t("common.no_value"), code: String(captureCode).padStart(4,"0")})}
|
||||||
</Text>
|
</Text>
|
||||||
<Show when={!hasHandicap}>
|
<Show when={!hasHandicap}>
|
||||||
<View style={styles.imageContainer}>
|
<View style={styles.imageContainer}>
|
||||||
<Text style={{fontSize: 15, margin: 5}}>{t("interface.drawer.target_name", {name: enemyName ?? t("general.no_value")})}</Text>
|
<Text style={{fontSize: 15, margin: 5}}>{t("play.drawer.target_name", {name: enemyName ?? t("common.no_value")})}</Text>
|
||||||
<ExpandableImage source={enemyImage(teamId)}/>
|
<ExpandableImage source={enemyImage(teamId)}/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.actionsContainer}>
|
<View style={styles.actionsContainer}>
|
||||||
<View style={styles.actionsLeftContainer}>
|
<View style={styles.actionsLeftContainer}>
|
||||||
<CustomTextInput value={enemyCaptureCode} inputMode="numeric" placeholder={t("interface.drawer.target_code_input")} onChangeText={setEnemyCaptureCode}/>
|
<CustomTextInput value={enemyCaptureCode} inputMode="numeric" placeholder={t("play.drawer.target_code_input")} onChangeText={setEnemyCaptureCode}/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.actionsRightContainer}>
|
<View style={styles.actionsRightContainer}>
|
||||||
<TouchableOpacity style={styles.button} onPress={handleCapture}>
|
<TouchableOpacity style={styles.button} onPress={handleCapture}>
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import { useTimeSinceSeconds } from '@/hooks/useTimeDelta';
|
|||||||
import { secondsToHHMMSS } from '@/utils/functions';
|
import { secondsToHHMMSS } from '@/utils/functions';
|
||||||
|
|
||||||
const Stat = ({ children, source, description }) => {
|
const Stat = ({ children, source, description }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity style={styles.statContainer} onPress={description ? () => Alert.alert("Info", description) : null}>
|
<TouchableOpacity style={styles.statContainer} onPress={description ? () => Alert.alert(t("info.default.title"), description) : null}>
|
||||||
<Image style={styles.image} source={source} resizeMode="contain"/>
|
<Image style={styles.image} source={source} resizeMode="contain"/>
|
||||||
<Text style={styles.text}>{children}</Text>
|
<Text style={styles.text}>{children}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
@@ -36,13 +38,13 @@ export const TeamStats = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={styles.statsContainer}>
|
<View style={styles.statsContainer}>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Stat source={require('@/assets/images/distance.png')} description={t("interface.drawer.stat_distance_label")}>{Math.floor(distance / 100) / 10}km</Stat>
|
<Stat source={require('@/assets/images/distance.png')} description={t("play.drawer.stat_distance_label")}>{Math.floor(distance / 100) / 10}km</Stat>
|
||||||
<Stat source={require('@/assets/images/time.png')} description={t("interface.drawer.stat_time_label")}>{secondsToHHMMSS((finishDate ? Math.floor((finishDate - startDate) / 1000) : timeSinceGameStart))}</Stat>
|
<Stat source={require('@/assets/images/time.png')} description={t("play.drawer.stat_time_label")}>{secondsToHHMMSS((finishDate ? Math.floor((finishDate - startDate) / 1000) : timeSinceGameStart))}</Stat>
|
||||||
<Stat source={require('@/assets/images/running.png')} description={t("interface.drawer.stat_speed_label")}>{avgSpeed}km/h</Stat>
|
<Stat source={require('@/assets/images/running.png')} description={t("play.drawer.stat_speed_label")}>{avgSpeed}km/h</Stat>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.row}>
|
<View style={styles.row}>
|
||||||
<Stat source={require('@/assets/images/target/black.png')} description={t("interface.drawer.stat_capture_label")}>{nCaptures}</Stat>
|
<Stat source={require('@/assets/images/target/black.png')} description={t("play.drawer.stat_capture_label")}>{nCaptures}</Stat>
|
||||||
<Stat source={require('@/assets/images/update_position.png')} description={t("interface.drawer.stat_reveal_label")}>{nSentLocation}</Stat>
|
<Stat source={require('@/assets/images/update_position.png')} description={t("play.drawer.stat_reveal_label")}>{nSentLocation}</Stat>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,28 +22,28 @@ export const Toasts = () => {
|
|||||||
{
|
{
|
||||||
condition: userState === USER_STATE.PLACEMENT,
|
condition: userState === USER_STATE.PLACEMENT,
|
||||||
id: 'placement',
|
id: 'placement',
|
||||||
text: ready ? t("interface.placed") : t("interface.not_placed"),
|
text: ready ? t("play.toast.placed") : t("play.toast.not_placed"),
|
||||||
toastColor: ready ? "rgb(25, 165, 25)" : "rgb(204, 51, 51)" ,
|
toastColor: ready ? "rgb(25, 165, 25)" : "rgb(204, 51, 51)" ,
|
||||||
textColor: "white"
|
textColor: "white"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: userState === USER_STATE.PLAYING && !outOfZone && enemyHasHandicap,
|
condition: userState === USER_STATE.PLAYING && !outOfZone && enemyHasHandicap,
|
||||||
id: 'enemy_revealed',
|
id: 'enemy_revealed',
|
||||||
text: t("interface.enemy_position_revealed"),
|
text: t("play.toast.enemy_position_revealed"),
|
||||||
toastColor: "white",
|
toastColor: "white",
|
||||||
textColor: "black"
|
textColor: "black"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: userState === USER_STATE.PLAYING && outOfZone && hasHandicap,
|
condition: userState === USER_STATE.PLAYING && outOfZone && hasHandicap,
|
||||||
id: 'out_of_zone',
|
id: 'out_of_zone',
|
||||||
text: `${t("interface.go_in_zone")}\n${t("interface.team_position_revealed")}`,
|
text: `${t("play.toast.go_in_zone")}\n${t("play.toast.team_position_revealed")}`,
|
||||||
toastColor: "white",
|
toastColor: "white",
|
||||||
textColor: "black"
|
textColor: "black"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: userState === USER_STATE.PLAYING && outOfZone && !hasHandicap,
|
condition: userState === USER_STATE.PLAYING && outOfZone && !hasHandicap,
|
||||||
id: 'has_handicap',
|
id: 'has_handicap',
|
||||||
text: `${t("interface.go_in_zone")}\n${t("interface.out_of_zone_message", {time: secondsToMMSS(outOfZoneTimeLeft)})}`,
|
text: `${t("play.toast.go_in_zone")}\n${t("play.toast.out_of_zone_message", {time: secondsToMMSS(outOfZoneTimeLeft)})}`,
|
||||||
toastColor: "white",
|
toastColor: "white",
|
||||||
textColor: "black"
|
textColor: "black"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const usePickImage = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error picking image;', error);
|
console.error('Error picking image;', error);
|
||||||
Alert.alert(t("error.title"), t("error.image_selection"));
|
Alert.alert(t("error.default.title"), t("error.default.image_selection"));
|
||||||
}
|
}
|
||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,83 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"no_value": "Unavailable"
|
||||||
|
},
|
||||||
|
"location-permission": {
|
||||||
|
"title": "Please enable background location in settings and restart the app.",
|
||||||
|
"subtitle": "Each team's location must be known by the server and organizers to ensure the game runs smoothly."
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"header": {
|
||||||
|
"title": "LA TRAQUE"
|
||||||
|
},
|
||||||
|
"form": {
|
||||||
|
"team_id_input": "Team ID",
|
||||||
|
"image_label": "Tap to change team photo",
|
||||||
|
"image_sublabel": "Upper body must be visible",
|
||||||
|
"validate_button": "Validate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wait": {
|
||||||
|
"title": "Rules Reminder !",
|
||||||
|
"placement_rule": "Go to your starting zone and wait for the game to begin.",
|
||||||
|
"capture_rule": "Track your target using their photo and the positions they reveal. To obtain your target's last known position, you must reveal your own. Once captured, enter their code in the app.",
|
||||||
|
"zone_rule": "Move on foot while making sure to stay within the game zone to avoid penalties ! If you stay outside the zone for too long, your interface will be disabled and your hunter will know your exact position.",
|
||||||
|
"team_rule": "Set up team strategies ! But always stay within earshot, do not communicate with other teams, and remain in public areas."
|
||||||
|
},
|
||||||
|
"play": {
|
||||||
|
"info": {
|
||||||
|
"zone_reduction_label": "Zone reduction in",
|
||||||
|
"send_position_label": "Position sent in"
|
||||||
|
},
|
||||||
|
"toast": {
|
||||||
|
"placed": "Placed",
|
||||||
|
"not_placed": "Not placed",
|
||||||
|
"enemy_position_revealed": "Enemy position continuously revealed !",
|
||||||
|
"go_in_zone": "Return to the zone !",
|
||||||
|
"team_position_revealed": "Position continuously revealed.",
|
||||||
|
"out_of_zone_message": "Handicap in {{time}}"
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"previous_marker_title": "Position sent",
|
||||||
|
"previous_marker_description": "This is your last position known by the server",
|
||||||
|
"enemy_marker_title": "Enemy position",
|
||||||
|
"enemy_marker_description": "This is the last known position of your enemies"
|
||||||
|
},
|
||||||
|
"drawer": {
|
||||||
|
"capture_code": "{{name}}'s code : {{code}}",
|
||||||
|
"target_name": "Target ({{name}})",
|
||||||
|
"target_code_input": "Target code",
|
||||||
|
"stat_distance_label": "Distance traveled",
|
||||||
|
"stat_time_label": "Elapsed time (HH:MM:SS)",
|
||||||
|
"stat_speed_label": "Average speed",
|
||||||
|
"stat_capture_label": "Total captures by your team",
|
||||||
|
"stat_reveal_label": "Total times your position was sent"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
"default": {
|
||||||
|
"title": "Info"
|
||||||
|
},
|
||||||
|
"succes": {
|
||||||
|
"title": "Success !",
|
||||||
|
"capture_success": "You have successfully captured your target. A new target has been assigned to you."
|
||||||
|
},
|
||||||
|
"failure": {
|
||||||
|
"title": "Failure...",
|
||||||
|
"capture_failure": "The capture failed. Please check the code and try again."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"default": {
|
||||||
|
"title": "Error",
|
||||||
|
"invalid_team_id": "Please enter a valid team ID.",
|
||||||
|
"unknown_team_id": "Unknown team ID.",
|
||||||
|
"server_connection": "Server connection failed.",
|
||||||
|
"image_selection": "An error occurred during image selection."
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"title": "Permission denied",
|
||||||
|
"storage_acces": "Enable storage or gallery access in settings."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"common": {
|
||||||
"no_value": "Indisponible"
|
"no_value": "Indisponible"
|
||||||
},
|
},
|
||||||
"index": {
|
"location-permission": {
|
||||||
|
"title": "Veuillez activer la localisation en arrière plan dans les paramètres et relancer l'application.",
|
||||||
|
"subtitle": "La localisation de chaque équipe doit être connue par le serveur et les organisateurs pour veiller au bon déroulement du jeu."
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
"header": {
|
"header": {
|
||||||
"title": "LA TRAQUE"
|
"title": "LA TRAQUE"
|
||||||
},
|
},
|
||||||
@@ -13,24 +17,27 @@
|
|||||||
"validate_button": "Valider"
|
"validate_button": "Valider"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"interface": {
|
"wait": {
|
||||||
"placed": "Placé",
|
"title": "Rappel des règles !",
|
||||||
"not_placed": "Non placé",
|
"placement_rule": "Rejoignez votre zone de départ et attendez le début de la partie.",
|
||||||
"zone_reduction_label": "Réduction de la zone dans",
|
"capture_rule": "Traquez votre cible grâce à sa photo et aux positions qu'elle révèle. Pour obtenir la dernière position connue de votre cible, vous devrez révéler la vôtre. Une fois capturée, entrez son code dans l'application.",
|
||||||
"send_position_label": "Position envoyée dans",
|
"zone_rule": "Déplacez-vous à pied tout en veillant à rester dans la zone de jeu pour ne pas être pénalisé ! Si vous restez trop longtemps en dehors de la zone, votre interface sera désactivée et votre chasseur connaîtra précisément votre position.",
|
||||||
"enemy_position_revealed": "Position ennemie révélée en continue !",
|
"team_rule": "Mettez en place des stratégies d'équipe ! Mais restez toujours à portée de voix, ne communiquez pas avec les autres équipes et restez dans les lieux publics."
|
||||||
"waiting_default_message": "Préparation de la partie",
|
},
|
||||||
"placement_default_message": "Phase de placement",
|
"play": {
|
||||||
"go_in_zone": "Retournez dans la zone !",
|
"info": {
|
||||||
"team_position_revealed": "Position révélée en continue.",
|
"zone_reduction_label": "Réduction de la zone dans",
|
||||||
"out_of_zone_message": "Handicap dans {{time}}",
|
"send_position_label": "Position envoyée dans"
|
||||||
"playing_message": "La partie est en cours",
|
},
|
||||||
"winner_message": "Vous avez gagné !",
|
"toast": {
|
||||||
"loser_message": "Vous avez perdu...",
|
"placed": "Placé",
|
||||||
"captured_message": "Vous avez été éliminé...",
|
"not_placed": "Non placé",
|
||||||
|
"enemy_position_revealed": "Position ennemie révélée en continue !",
|
||||||
|
"go_in_zone": "Retournez dans la zone !",
|
||||||
|
"team_position_revealed": "Position révélée en continue.",
|
||||||
|
"out_of_zone_message": "Handicap dans {{time}}"
|
||||||
|
},
|
||||||
"map": {
|
"map": {
|
||||||
"team_marker_title": "Position actuelle",
|
|
||||||
"team_marker_description": "Ceci est votre position",
|
|
||||||
"previous_marker_title": "Position envoyée",
|
"previous_marker_title": "Position envoyée",
|
||||||
"previous_marker_description": "Ceci est votre dernière position connue par le serveur",
|
"previous_marker_description": "Ceci est votre dernière position connue par le serveur",
|
||||||
"enemy_marker_title": "Position ennemie",
|
"enemy_marker_title": "Position ennemie",
|
||||||
@@ -41,18 +48,33 @@
|
|||||||
"target_name": "Cible ({{name}})",
|
"target_name": "Cible ({{name}})",
|
||||||
"target_code_input": "Code cible",
|
"target_code_input": "Code cible",
|
||||||
"stat_distance_label": "Distance parcourue",
|
"stat_distance_label": "Distance parcourue",
|
||||||
"stat_time_label": "Temps écoulé au format HH:MM:SS",
|
"stat_time_label": "Temps écoulé (HH:MM:SS)",
|
||||||
"stat_speed_label": "Vitesse moyenne",
|
"stat_speed_label": "Vitesse moyenne",
|
||||||
"stat_capture_label": "Nombre total de captures par votre équipe",
|
"stat_capture_label": "Nombre total de captures par votre équipe",
|
||||||
"stat_reveal_label": "Nombre total d'envois de votre position"
|
"stat_reveal_label": "Nombre total d'envois de votre position"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"info": {
|
||||||
|
"default": {
|
||||||
|
"title": "Info"
|
||||||
|
},
|
||||||
|
"succes": {
|
||||||
|
"title": "Bravo !",
|
||||||
|
"capture_success": "Vous avez réussi à capturer votre cible. Une nouvelle cible vient de vous être attribuée."
|
||||||
|
},
|
||||||
|
"failure": {
|
||||||
|
"title": "Raté...",
|
||||||
|
"capture_failure": "La capture a échoué. Vérifiez le code et réessayez."
|
||||||
|
}
|
||||||
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Erreur",
|
"default": {
|
||||||
"invalid_team_id": "Veuillez entrer un ID d'équipe valide.",
|
"title": "Erreur",
|
||||||
"unknown_team_id": "L'ID d'équipe est inconnu.",
|
"invalid_team_id": "Veuillez entrer un ID d'équipe valide.",
|
||||||
"server_connection": "La connexion au serveur a échoué.",
|
"unknown_team_id": "L'ID d'équipe est inconnu.",
|
||||||
"image_selection": "Une erreur est survenue lors de la sélection d'une image.",
|
"server_connection": "La connexion au serveur a échoué.",
|
||||||
|
"image_selection": "Une erreur est survenue lors de la sélection d'une image."
|
||||||
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
"title": "Permission refusée",
|
"title": "Permission refusée",
|
||||||
"storage_acces": "Activez l'accès au stockage ou à la gallerie dans les paramètres."
|
"storage_acces": "Activez l'accès au stockage ou à la gallerie dans les paramètres."
|
||||||
|
|||||||
Reference in New Issue
Block a user