Page End + UI adjustments + cleaning

This commit is contained in:
Sebastien Riviere
2026-02-21 21:45:27 +01:00
parent 28e81894ce
commit 405b2934c8
18 changed files with 395 additions and 272 deletions

View File

@@ -1,8 +1,42 @@
// React
import { View, StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
// Expo
import { Slot } from 'expo-router';
// Components
import { IconButton } from '@/components/common/IconButton';
const AuthLayout = () => {
return <Slot/>;
const { i18n } = useTranslation();
const toggleLanguage = () => {
i18n.changeLanguage(i18n.language === 'fr' ? 'en' : 'fr');
};
return (
<View style={styles.globalContainer}>
<Slot/>
<IconButton style={styles.languageButton} source={require('@/assets/images/language.png')} onPress={toggleLanguage} />
</View>
);
};
export default AuthLayout;
const styles = StyleSheet.create({
globalContainer: {
flex: 1
},
languageButton: {
position: 'absolute',
top: 0,
right: 0,
width: 60,
height: 60,
backgroundColor: "rgb(126, 182, 199)",
borderBottomLeftRadius: 20,
padding: 5,
justifyContent: 'center',
alignItems: 'center',
}
});

View File

@@ -11,8 +11,6 @@ import { useAuth } from "@/contexts/authContext";
import { usePickImage } from '@/hooks/usePickImage';
// Services
import { uploadTeamImage } from '@/services/api/image';
// Constants
import { COLORS } from '@/constants';
const Login = () => {
const { t } = useTranslation();
@@ -53,28 +51,20 @@ const Login = () => {
};
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("login.header.title")}</Text>
</View>
<View style={styles.subContainer}>
<CustomTextInput value={teamId} inputMode="numeric" placeholder={t("login.form.team_id_input")} style={styles.input} onChangeText={setTeamId}/>
</View>
<View style={styles.subContainer}>
<Text style={{fontSize: 15}}>{t("login.form.image_label")}</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}/>
</View>
<View style={styles.subContainer}>
<View style={styles.buttonContainer}>
<TouchableHighlight style={styles.button} onPress={handleSubmit}>
<Text style={styles.buttonLabel}>{isSubmitting ? "..." : t("login.form.validate_button")}</Text>
</TouchableHighlight>
</View>
</View>
<ScrollView style={styles.outerScrollContainer} contentContainerStyle={styles.innerScrollContainer} showsVerticalScrollIndicator={false}>
<View style={styles.logoContainer}>
<Image style={styles.logoImage} source={require('@/assets/images/logo/logo_traque.png')}/>
<Text style={styles.logoText}>{t("login.header.title")}</Text>
</View>
<CustomTextInput style={styles.input} value={teamId} inputMode="numeric" placeholder={t("login.form.team_id_input")} onChangeText={setTeamId}/>
<View style={styles.imageContainer}>
<Text style={styles.imageLabel}>{t("login.form.image_label")}</Text>
<Text style={styles.imageSubLabel}>{t("login.form.image_sublabel")}</Text>
<TouchableImage source={image ? {uri: image.uri} : require('@/assets/images/missing_image.jpg')} onPress={pickImage}/>
</View>
<TouchableHighlight style={styles.button} onPress={handleSubmit}>
<Text style={styles.buttonLabel}>{isSubmitting ? "..." : t("login.form.validate_button")}</Text>
</TouchableHighlight>
</ScrollView>
);
};
@@ -82,55 +72,53 @@ const Login = () => {
export default Login;
const styles = StyleSheet.create({
container: {
flexGrow: 1,
alignItems: 'center',
paddingVertical: 20,
backgroundColor: COLORS.background
outerScrollContainer: {
flex: 1,
},
transitionContainer: {
innerScrollContainer: {
flexGrow: 1,
width: '80%',
maxWidth: 600,
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 40,
paddingHorizontal: 20,
gap: 20,
},
subContainer: {
flexGrow: 1,
width: "100%",
logoContainer: {
justifyContent: "center",
alignItems: 'center',
justifyContent: 'center',
margin: 10,
gap: 10,
},
logoImage: {
width: 130,
height: 130,
margin: 10,
},
logoText: {
fontSize: 50,
fontWeight: 'bold',
},
buttonContainer: {
width: "100%",
maxWidth: 240,
height: 80,
input: {
width: "80%",
},
imageContainer: {
justifyContent: "center",
alignItems: 'center',
justifyContent: 'center',
padding: 3,
borderWidth: 4,
borderColor: '#888',
borderRadius: 18
},
imageLabel: {
fontSize: 15
},
imageSubLabel:{
fontSize: 13
},
button: {
borderRadius: 10,
width: '100%',
height: '100%',
width: 200,
height: 70,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#555'
},
buttonLabel: {
color: '#fff',
fontSize: 16,
fontSize: 20,
},
});

View File

@@ -1,8 +1,76 @@
// React
import { View, Text, StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
// Expo
import { Slot } from 'expo-router';
// Contexts
import { useAuth } from '@/contexts/authContext';
import { useTeam } from '@/contexts/teamContext';
// Components
import { IconButton } from '@/components/common/IconButton';
// Constants
import { COLORS } from '@/constants';
const GameLayout = () => {
return <Slot/>;
const { t, i18n } = useTranslation();
const { logout } = useAuth();
const { teamInfos } = useTeam();
const { name } = teamInfos;
const toggleLanguage = () => {
i18n.changeLanguage(i18n.language === 'fr' ? 'en' : 'fr');
};
const formatText = (text, limit) => {
if (text == null) return t("common.no_value");
if (text.length > limit) {
return text.substring(0, limit) + "...";
} else {
return text;
}
};
formatText("les minions du bds qui gagne");
return (
<View style={styles.globalContainer}>
<View style={styles.headerContainer}>
<IconButton style={styles.logoutIcon} source={require('@/assets/images/logout.png')} onPress={logout} />
<Text style={styles.name} numberOfLines={1} adjustsFontSizeToFit>{formatText(name, 22)}</Text>
<IconButton style={styles.traductionIcon} source={require('@/assets/images/language.png')} onPress={toggleLanguage} />
</View>
<Slot/>
</View>
);
};
export default GameLayout;
const styles = StyleSheet.create({
globalContainer: {
flex: 1
},
headerContainer: {
backgroundColor: COLORS.background,
flexDirection: "row",
alignItems: "center",
padding: 10,
gap: 10,
elevation: 10,
},
name: {
fontSize: 30,
fontWeight: "bold",
textAlign: "center",
flex: 1
},
logoutIcon: {
width: 50,
height: 50
},
traductionIcon: {
width: 60,
height: 60
}
});

View File

@@ -1,31 +1,117 @@
// React
import { View, Text, StyleSheet } from 'react-native';
import { Text, StyleSheet, ScrollView } from 'react-native';
import { useTranslation } from 'react-i18next';
// Components
import { Header } from '@/components/game/Header';
import { TeamStats } from '@/components/game/TeamStats';
import { Show } from '@/components/common/Show';
// Hook
import { useUserState } from '@/hooks/useUserState';
// Constants
import { COLORS } from '@/constants';
import { USER_STATE } from '@/constants';
/*
const Leaderboard = ({ teams }) => {
return (
<View style={styles.leaderboardContainer}>
{teams.map((item, index) => {
const isSelected = index+1 == 5;
return (
<View key={`leaderboard-${item}-${index+1}`} style={[styles.item, isSelected && styles.selectedTeam]}>
<Text style={styles.rank}>{index + 1}</Text>
<Text style={[styles.teamName, isSelected && styles.selectedTeam]}>
{item}
</Text>
</View>
);
})}
</View>
);
};
<View style={styles.section}>
<Text style={styles.sectionTitle}>Classement</Text>
<Leaderboard teams={teamsTest}/>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>Parcours</Text>
<View style={styles.mapView}>
<MapView style={styles.map} mapType={"standard"} initialRegion={INITIAL_REGIONS.TELECOM_PARIS} toolbarEnabled={false}>
</MapView>
</View>
</View>
mapView: {
height: 300,
borderRadius: 20,
overflow: "hidden"
},
map: {
flex: 1
},
// Classement
leaderboardContainer: {
gap: 8,
},
item: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
paddingHorizontal: 20,
backgroundColor: '#f9f9f9',
borderRadius: 8,
borderWidth: 2,
gap: 20
},
selectedTeam: {
backgroundColor: 'rgb(126, 182, 199)',
},
rank: {
fontSize: 18,
fontWeight: 'bold',
color: '#000000',
},
teamName: {
fontSize: 16,
color: '#000000',
},
*/
const End = () => {
const { t } = useTranslation();
const userState = useUserState();
return (
<View style={styles.globalContainer}>
<View style={styles.topContainer}>
<Header/>
<Text>Fin de la partie !</Text>
</View>
</View>
<ScrollView style={styles.outerScrollview} contentContainerStyle={styles.innerScrollview} showsVerticalScrollIndicator={false}>
<Show when={userState == USER_STATE.CAPTURED}>
<Text style={styles.title}>{t("end.title_captured")}</Text>
</Show>
<Show when={userState == USER_STATE.FINISHED}>
<Text style={styles.title}>{t("end.title_win")}</Text>
</Show>
<Text style={styles.subtitle}>{t("end.paragraph")}</Text>
<TeamStats/>
</ScrollView>
);
};
export default End;
const styles = StyleSheet.create({
globalContainer: {
backgroundColor: COLORS.background,
flex: 1,
outerScrollview: {
flex: 1
},
topContainer: {
width: '100%',
alignItems: 'center',
padding: 15,
innerScrollview: {
padding: 20,
gap: 20,
},
title: {
fontSize: 24,
fontWeight: "bold",
textAlign: "center"
},
subtitle: {
fontSize: 15,
}
});

View File

@@ -1,6 +1,6 @@
// React
import { useState } from 'react';
import { View, Alert, StyleSheet } from 'react-native';
import { Text, View, Alert, StyleSheet } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { useTranslation } from 'react-i18next';
// Components
@@ -9,7 +9,6 @@ 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';
@@ -20,7 +19,7 @@ import { useUserState } from '@/hooks/useUserState';
// Services
import { emitSendPosition } from '@/services/socket/emitters';
// Constants
import { COLORS, USER_STATE } from '@/constants';
import { USER_STATE } from '@/constants';
const Play = () => {
const { t } = useTranslation();
@@ -31,16 +30,16 @@ const Play = () => {
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("play.info.zone_reduction_label")} date={nextZoneDate} />
<TimerMMSS style={{width: "50%"}} title={t("play.info.send_position_label")} date={locationSendDeadline} />
</View>
</Show>
</View>
<View style={styles.bottomContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
<Show when={userState == USER_STATE.PLACEMENT}>
<Text style={styles.placementTitle}>{t("play.info.placement_title")}</Text>
</Show>
<Show when={userState == USER_STATE.PLAYING}>
<View style={styles.timerContainer}>
<TimerMMSS style={styles.timer} title={t("play.info.zone_reduction_label")} date={nextZoneDate} />
<TimerMMSS style={styles.timer} title={t("play.info.send_position_label")} date={locationSendDeadline} />
</View>
</Show>
<View style={styles.mapContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
<Map>
<Show when={userState == USER_STATE.PLACEMENT}>
<StartZone/>
@@ -70,22 +69,23 @@ export default Play;
const styles = StyleSheet.create({
globalContainer: {
backgroundColor: COLORS.background,
flex: 1,
},
topContainer: {
width: '100%',
alignItems: 'center',
placementTitle: {
textAlign: "center",
padding: 15,
fontSize: 20,
fontWeight: "bold"
},
infoContainer: {
width: '100%',
timerContainer: {
padding: 15,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
marginTop: 15
},
bottomContainer: {
timer: {
width: "50%",
},
mapContainer: {
flex: 1,
borderTopLeftRadius: 30,
borderTopRightRadius: 30,

View File

@@ -1,75 +1,60 @@
// React
import { View, Text, StyleSheet, Image } from 'react-native';
import { ScrollView, View, Text, StyleSheet, Image } from 'react-native';
import { useTranslation } from 'react-i18next';
// Components
import { Header } from '@/components/game/Header';
// Constants
import { COLORS } from '@/constants';
const Section = ({ source, text }) => {
return (
<View style={styles.section}>
<Image style={styles.image} source={source} />
<Text style={styles.description}>{text}</Text>
</View>
);
};
const Wait = () => {
const { t } = useTranslation();
return (
<View style={styles.globalContainer}>
<Header/>
<View style={styles.rulesContainer}>
<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>
<ScrollView style={styles.outerScrollview} contentContainerStyle={styles.innerScrollview} showsVerticalScrollIndicator={false}>
<Text style={styles.title}>{t("wait.title")}</Text>
<Section source={require("@/assets/images/flag.png")} text={t("wait.placement_rule")} />
<Section source={require("@/assets/images/target/black.png")} text={t("wait.capture_rule")} />
<Section source={require("@/assets/images/running.png")} text={t("wait.zone_rule")} />
<Section source={require("@/assets/images/team.png")} text={t("wait.team_rule")} />
</ScrollView>
);
};
export default Wait;
const styles = StyleSheet.create({
globalContainer: {
backgroundColor: COLORS.background,
flex: 1,
padding: 20,
outerScrollview: {
flex: 1
},
rulesContainer: {
flex: 1,
innerScrollview: {
flexGrow: 1,
justifyContent: "space-between",
alignItems: 'center',
padding: 30,
gap: 30
},
title: {
backgroundColor: "white",
textAlign: 'center',
fontSize: 30,
fontWeight: "bold",
borderWidth: 2,
borderRadius: 10,
padding: 10,
fontSize: 18,
fontWeight: "bold"
},
section: {
width: '100%',
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 20,
gap: 30,
},
image: {
width: 100,
height: 100,
width: 70,
height: 70,
},
description: {
flex: 1,
fontSize: 15,
}
});

View File

@@ -1,11 +1,8 @@
// React
import { useEffect } from 'react';
import { View, StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
// Expo
import { Slot, useRouter, usePathname } from 'expo-router';
// Components
import { IconButton } from '@/components/common/IconButton';
// Contexts
import { AuthProvider } from "@/contexts/authContext";
import { TeamProvider } from "@/contexts/teamContext";
@@ -14,7 +11,7 @@ import { useUserState } from '@/hooks/useUserState';
// Services
import { startLocationTracking , stopLocationTracking } from '@/services/tasks/backgroundLocation';
// Constants
import { USER_STATE } from '@/constants';
import { USER_STATE, COLORS } from '@/constants';
// Traduction
import '@/i18n/config';
@@ -77,27 +74,14 @@ const NavigationManager = () => {
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 = () => {
return (
<AuthProvider>
<TeamProvider>
<Slot/>
<View style={styles.globalContainer}>
<Slot/>
</View>
<NavigationManager/>
<Language/>
</TeamProvider>
</AuthProvider>
);
@@ -106,14 +90,8 @@ const 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',
globalContainer: {
backgroundColor: COLORS.background,
flex: 1,
}
});

View File

@@ -6,18 +6,18 @@ import LinearGradient from 'react-native-linear-gradient';
// Constants
import { COLORS } from '@/constants';
export const Drawer = ({ height, children }) => {
export const Drawer = ({ contentContainerStyle = {}, height, children }) => {
const [collapsibleState, setCollapsibleState] = useState(true);
return (
<View style={styles.outerDrawerContainer}>
<LinearGradient colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.5)']} style={styles.gradient}/>
<View style={styles.innerDrawerContainer}>
<TouchableHighlight style={styles.collapsibleButton} underlayColor="#d9d9d9" onPress={() => setCollapsibleState(!collapsibleState)}>
<TouchableHighlight style={styles.collapsibleButton} underlayColor="#e9e9e9" onPress={() => setCollapsibleState(!collapsibleState)}>
<Image source={require('@/assets/images/arrow.png')} style={[styles.arrow, {transform: [{ scaleY: collapsibleState ? 1 : -1 }]}]} resizeMode="contain"/>
</TouchableHighlight>
<Collapsible style={[styles.collapsibleWindow, {height: height - 44}]} collapsed={collapsibleState}>
<ScrollView contentContainerStyle={styles.collapsibleContent}>
<ScrollView style={styles.outerScrollContainer} contentContainerStyle={contentContainerStyle} showsVerticalScrollIndicator={false}>
{children}
</ScrollView>
</Collapsible>
@@ -61,7 +61,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
backgroundColor: COLORS.background,
},
collapsibleContent: {
paddingHorizontal: 15,
outerScrollContainer: {
flex: 1
}
});

View File

@@ -16,7 +16,6 @@ export const CustomTextInput = ({ style = {}, value, inputMode, placeholder, onC
const styles = StyleSheet.create({
input: {
width: "100%",
padding: 15,
borderColor: '#777',
borderRadius: 12,

View File

@@ -25,7 +25,7 @@ export const Map = ({ children }) => {
return (
<View style={styles.container}>
<MapView ref={mapRef} style={styles.mapView} initialRegion={INITIAL_REGIONS.PARIS} mapType={"standard"} onTouchMove={() => setCenterMap(false)} toolbarEnabled={false}>
<MapView ref={mapRef} style={styles.mapView} initialRegion={INITIAL_REGIONS.TELECOM_PARIS} mapType={"standard"} onTouchMove={() => setCenterMap(false)} toolbarEnabled={false}>
{children}
<PositionMarker position={location} />
</MapView>
@@ -50,8 +50,8 @@ const styles = StyleSheet.create({
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: 'white',
borderWidth: 2,
borderColor: 'black'
backgroundColor: 'rgba(255, 255, 255, 0.75)',
borderWidth: 1,
borderColor: 'rgba(64, 64, 64, 0.50)'
},
});

View File

@@ -1,40 +0,0 @@
// React
import { View, Text, StyleSheet } from 'react-native';
import { useTranslation } from 'react-i18next';
// Contexts
import { useAuth } from '@/contexts/authContext';
import { useTeam } from '@/contexts/teamContext';
// Components
import { IconButton } from '@/components/common/IconButton';
export const Header = () => {
const { t } = useTranslation();
const { logout } = useAuth();
const { teamInfos } = useTeam();
const { name } = teamInfos;
return (
<View style={styles.container}>
<IconButton source={require('@/assets/images/logout.png')} onPress={logout} />
<View style={styles.nameContainer}>
<Text style={styles.name}>{name ?? t("common.no_value")}</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
width: '100%',
},
nameContainer: {
width: '100%',
alignItems: 'center',
justifyContent: 'center',
marginBottom: 20
},
name: {
fontSize: 36,
fontWeight: "bold"
}
});

View File

@@ -1,6 +1,6 @@
// React
import { useState } from 'react';
import { Keyboard, View, Text, Image, StyleSheet, TouchableOpacity, Alert } from 'react-native';
import { Keyboard, View, Text, StyleSheet, Alert } from 'react-native';
import { useTranslation } from 'react-i18next';
// Components
import { ExpandableImage } from '@/components/common/Image';
@@ -14,6 +14,7 @@ import { useTeam } from '@/contexts/teamContext';
// Services
import { emitCapture } from '@/services/socket/emitters';
import { enemyImage } from '@/services/api/image';
import { IconButton } from '../common/IconButton';
export const TargetInfoDrawer = ({ height }) => {
const { t } = useTranslation();
@@ -47,24 +48,16 @@ export const TargetInfoDrawer = ({ height }) => {
};
return (
<Drawer height={height}>
<Text style={{fontSize: 22, fontWeight: "bold", textAlign: "center"}}>
{t("play.drawer.capture_code", {name: name ?? t("common.no_value"), code: String(captureCode).padStart(4,"0")})}
</Text>
<Drawer contentContainerStyle={styles.drawer} height={height}>
<Text style={styles.teamCode}>{t("play.drawer.capture_code", {name: name ?? t("common.no_value"), code: String(captureCode).padStart(4,"0")})}</Text>
<Show when={!hasHandicap}>
<View style={styles.imageContainer}>
<Text style={{fontSize: 15, margin: 5}}>{t("play.drawer.target_name", {name: enemyName ?? t("common.no_value")})}</Text>
<View style={styles.targetContainer}>
<Text style={styles.targetName}>{t("play.drawer.target_name", {name: enemyName ?? t("common.no_value")})}</Text>
<ExpandableImage source={enemyImage(teamId)}/>
</View>
<View style={styles.actionsContainer}>
<View style={styles.actionsLeftContainer}>
<CustomTextInput value={enemyCaptureCode} inputMode="numeric" placeholder={t("play.drawer.target_code_input")} onChangeText={setEnemyCaptureCode}/>
</View>
<View style={styles.actionsRightContainer}>
<TouchableOpacity style={styles.button} onPress={handleCapture}>
<Image source={require("@/assets/images/target/white.png")} style={{width: 40, height: 40}} resizeMode="contain"/>
</TouchableOpacity>
</View>
<View style={styles.captureContainer}>
<CustomTextInput style={styles.captureInput} value={enemyCaptureCode} inputMode="numeric" placeholder={t("play.drawer.target_code_input")} onChangeText={setEnemyCaptureCode}/>
<IconButton style={styles.captureButton} source={require("@/assets/images/target/white.png")} onPress={handleCapture} />
</View>
</Show>
<TeamStats/>
@@ -73,34 +66,42 @@ export const TargetInfoDrawer = ({ height }) => {
};
const styles = StyleSheet.create({
imageContainer: {
drawer: {
flexGrow: 1,
justifyContent: "space-between",
padding: 15,
gap: 15,
},
teamCode: {
fontSize: 22,
fontWeight: "bold",
textAlign: "center"
},
targetContainer: {
width: "100%",
alignItems: "center",
justifyContent: "center",
marginTop: 15
justifyContent: "center"
},
actionsContainer: {
targetName: {
fontSize: 15,
},
captureContainer: {
flexDirection: "row",
width: "100%",
alignItems: 'center',
justifyContent: 'space-between',
marginTop: 15
gap: 15
},
actionsLeftContainer: {
flexGrow: 1,
alignItems: 'center',
justifyContent: 'center',
marginRight: 15
},
actionsRightContainer: {
width: 100,
captureInput: {
flex: 3,
alignItems: 'center',
justifyContent: 'center'
},
button: {
captureButton: {
flex: 1,
height: 70,
borderRadius: 12,
width: '100%',
height: 75,
padding: 10,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#444'

View File

@@ -37,41 +37,45 @@ export const TeamStats = () => {
return (
<View style={styles.statsContainer}>
<Text style={styles.title}>{t("play.drawer.stats_section_title")}</Text>
<View style={styles.row}>
<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/distance.png')} description={t("play.drawer.stat_distance_label")}>{Math.floor((distance ?? 0) / 100) / 10}km</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("play.drawer.stat_speed_label")}>{avgSpeed}km/h</Stat>
</View>
<View style={styles.row}>
<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("play.drawer.stat_reveal_label")}>{nSentLocation}</Stat>
<Stat source={require('@/assets/images/target/black.png')} description={t("play.drawer.stat_capture_label")}>{nCaptures ?? 0}</Stat>
<Stat source={require('@/assets/images/update_position.png')} description={t("play.drawer.stat_reveal_label")}>{nSentLocation ?? 0}</Stat>
</View>
</View>
);
};
const styles = StyleSheet.create({
statsContainer: {
width: "100%",
gap: 15,
},
title: {
fontSize: 20,
fontWeight: "bold",
textAlign: "center",
},
row: {
flexDirection: "row",
justifyContent: "space-around",
},
statContainer: {
height: 30,
flexDirection: "row",
justifyContent: 'center',
alignItems: 'center',
gap: 5,
},
image: {
width: 30,
height: 30,
marginRight: 5
},
text: {
fontSize: 15
fontSize: 15,
},
statsContainer: {
gap: 15,
width: "100%",
marginVertical: 15
},
row: {
flexDirection: "row",
justifyContent: "space-around"
}
});

View File

@@ -65,16 +65,16 @@ export const Toasts = () => {
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 5,
top: 0,
left: "50%",
transform: [{ translateX: '-50%' }],
maxWidth: "60%"
maxWidth: "60%",
padding: 10,
gap: 5
},
toast: {
margin: 5,
padding: 10,
borderRadius: 15,
backgroundColor: 'white',
elevation: 5
},
});

View File

@@ -4,6 +4,12 @@ export const INITIAL_REGIONS = {
longitude: 2.342,
latitudeDelta: 0,
longitudeDelta: 50
},
TELECOM_PARIS : {
latitude: 48.715,
longitude: 2.203,
latitudeDelta: 0,
longitudeDelta: 0.04
}
};

View File

@@ -35,7 +35,7 @@ export const useUserState = () => {
case GAME_STATE.PLAYING:
return captured ? USER_STATE.CAPTURED : USER_STATE.PLAYING;
case GAME_STATE.FINISHED:
return USER_STATE.FINISHED;
return captured ? USER_STATE.CAPTURED : USER_STATE.FINISHED;
default:
return USER_STATE.WAITING;
}

View File

@@ -18,7 +18,7 @@
}
},
"wait": {
"title": "Rules Reminder !",
"title": "While waiting for the game to start, here is a recap of the rules.",
"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.",
@@ -26,7 +26,8 @@
},
"play": {
"info": {
"zone_reduction_label": "Zone reduction in",
"placement_title": "Head to your zone !",
"zone_reduction_label": "Next zone in",
"send_position_label": "Position sent in"
},
"toast": {
@@ -47,6 +48,7 @@
"capture_code": "{{name}}'s code : {{code}}",
"target_name": "Target ({{name}})",
"target_code_input": "Target code",
"stats_section_title": "Your game statistics",
"stat_distance_label": "Distance traveled",
"stat_time_label": "Elapsed time (HH:MM:SS)",
"stat_speed_label": "Average speed",
@@ -54,13 +56,18 @@
"stat_reveal_label": "Total times your position was sent"
}
},
"end": {
"title_captured": "You have been captured...",
"title_win": "You won !",
"paragraph": "The game ends here for you ! You can head back to the starting point to grab a snack and watch the rest of the game."
},
"info": {
"default": {
"title": "Info"
},
"succes": {
"success": {
"title": "Success !",
"capture_success": "You have successfully captured your target. A new target has been assigned to you."
"capture_success": "You have successfully captured your target."
},
"failure": {
"title": "Failure...",

View File

@@ -18,7 +18,7 @@
}
},
"wait": {
"title": "Rappel des règles !",
"title": "En attendant que la partie commence, voici un récap des règles.",
"placement_rule": "Rejoignez votre zone de départ et attendez le début de la partie.",
"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.",
"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.",
@@ -26,7 +26,8 @@
},
"play": {
"info": {
"zone_reduction_label": "Réduction de la zone dans",
"placement_title": "Placez vous dans votre zone !",
"zone_reduction_label": "Prochaine zone dans",
"send_position_label": "Position envoyée dans"
},
"toast": {
@@ -47,6 +48,7 @@
"capture_code": "Code de {{name}} : {{code}}",
"target_name": "Cible ({{name}})",
"target_code_input": "Code cible",
"stats_section_title": "Statistiques de votre partie",
"stat_distance_label": "Distance parcourue",
"stat_time_label": "Temps écoulé (HH:MM:SS)",
"stat_speed_label": "Vitesse moyenne",
@@ -54,13 +56,18 @@
"stat_reveal_label": "Nombre total d'envois de votre position"
}
},
"end": {
"title_captured": "Vous avez été capturé...",
"title_win": "Vous avez gagné !",
"paragraph": "La partie se termine ici pour vous ! Vous pouvez rejoindre le point de départ pour prendre un goûter et assister au reste de la partie."
},
"info": {
"default": {
"title": "Info"
},
"succes": {
"success": {
"title": "Bravo !",
"capture_success": "Vous avez réussi à capturer votre cible. Une nouvelle cible vient de vous être attribuée."
"capture_success": "Vous avez réussi à capturer votre cible."
},
"failure": {
"title": "Raté...",