mirror of
https://git.rezel.net/LudoTech/traque.git
synced 2026-04-10 16:30:18 +02:00
Page End + UI adjustments + cleaning
This commit is contained in:
@@ -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',
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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}>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</View>
|
||||
</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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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.PLACEMENT}>
|
||||
<Text style={styles.placementTitle}>{t("play.info.placement_title")}</Text>
|
||||
</Show>
|
||||
<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 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>
|
||||
<View style={styles.bottomContainer} onLayout={(event) => setBottomContainerHeight(event.nativeEvent.layout.height)}>
|
||||
<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,
|
||||
|
||||
@@ -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}>
|
||||
<ScrollView style={styles.outerScrollview} contentContainerStyle={styles.innerScrollview} showsVerticalScrollIndicator={false}>
|
||||
<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>
|
||||
<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,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
<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,
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)'
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
});
|
||||
@@ -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'
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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é...",
|
||||
|
||||
Reference in New Issue
Block a user